diff --git a/doc/scdaemon.texi b/doc/scdaemon.texi index 81af28105..0c984162c 100644 --- a/doc/scdaemon.texi +++ b/doc/scdaemon.texi @@ -1,770 +1,784 @@ @c Copyright (C) 2002 Free Software Foundation, Inc. @c This is part of the GnuPG manual. @c For copying conditions, see the file gnupg.texi. @include defs.inc @node Invoking SCDAEMON @chapter Invoking the SCDAEMON @cindex SCDAEMON command options @cindex command options @cindex options, SCDAEMON command @manpage scdaemon.1 @ifset manverb .B scdaemon \- Smartcard daemon for the GnuPG system @end ifset @mansect synopsis @ifset manverb .B scdaemon .RB [ \-\-homedir .IR dir ] .RB [ \-\-options .IR file ] .RI [ options ] .B \-\-server .br .B scdaemon .RB [ \-\-homedir .IR dir ] .RB [ \-\-options .IR file ] .RI [ options ] .B \-\-daemon .RI [ command_line ] @end ifset @mansect description The @command{scdaemon} is a daemon to manage smartcards. It is usually invoked by @command{gpg-agent} and in general not used directly. @manpause @xref{Option Index}, for an index to @command{scdaemon}'s commands and options. @mancont @menu * Scdaemon Commands:: List of all commands. * Scdaemon Options:: List of all options. * Card applications:: Description of card applications. * Scdaemon Configuration:: Configuration files. * Scdaemon Examples:: Some usage examples. * Scdaemon Protocol:: The protocol the daemon uses. @end menu @mansect commands @node Scdaemon Commands @section Commands Commands are not distinguished from options except for the fact that only one command is allowed. @table @gnupgtabopt @item --version @opindex version Print the program version and licensing information. Note that you cannot abbreviate this command. @item --help, -h @opindex help Print a usage message summarizing the most useful command-line options. Note that you cannot abbreviate this command. @item --dump-options @opindex dump-options Print a list of all available options and commands. Note that you cannot abbreviate this command. @item --server @opindex server Run in server mode and wait for commands on the @code{stdin}. The default mode is to create a socket and listen for commands there. @item --multi-server @opindex multi-server Run in server mode and wait for commands on the @code{stdin} as well as on an additional Unix Domain socket. The server command @code{GETINFO} may be used to get the name of that extra socket. @item --daemon @opindex daemon Run the program in the background. This option is required to prevent it from being accidentally running in the background. @end table @mansect options @node Scdaemon Options @section Option Summary @table @gnupgtabopt @item --options @var{file} @opindex options Reads configuration from @var{file} instead of from the default per-user configuration file. The default configuration file is named @file{scdaemon.conf} and expected in the @file{.gnupg} directory directly below the home directory of the user. @include opt-homedir.texi @item -v @item --verbose @opindex v @opindex verbose Outputs additional information while running. You can increase the verbosity by giving several verbose commands to @command{gpgsm}, such as @samp{-vv}. @item --debug-level @var{level} @opindex debug-level Select the debug level for investigating problems. @var{level} may be a numeric value or a keyword: @table @code @item none No debugging at all. A value of less than 1 may be used instead of the keyword. @item basic Some basic debug messages. A value between 1 and 2 may be used instead of the keyword. @item advanced More verbose debug messages. A value between 3 and 5 may be used instead of the keyword. @item expert Even more detailed messages. A value between 6 and 8 may be used instead of the keyword. @item guru All of the debug messages you can get. A value greater than 8 may be used instead of the keyword. The creation of hash tracing files is only enabled if the keyword is used. @end table How these messages are mapped to the actual debugging flags is not specified and may change with newer releases of this program. They are however carefully selected to best aid in debugging. @quotation Note All debugging options are subject to change and thus should not be used by any application program. As the name says, they are only used as helpers to debug problems. @end quotation @item --debug @var{flags} @opindex debug This option is only useful for debugging and the behavior may change at any time without notice. FLAGS are bit encoded and may be given in usual C-Syntax. The currently defined bits are: @table @code @item 0 (1) command I/O @item 1 (2) values of big number integers @item 2 (4) low level crypto operations @item 5 (32) memory allocation @item 6 (64) caching @item 7 (128) show memory statistics @item 9 (512) write hashed data to files named @code{dbgmd-000*} @item 10 (1024) trace Assuan protocol. See also option @option{--debug-assuan-log-cats}. @item 11 (2048) trace APDU I/O to the card. This may reveal sensitive data. @item 12 (4096) trace some card reader related function calls. @end table @item --debug-all @opindex debug-all Same as @code{--debug=0xffffffff} @item --debug-wait @var{n} @opindex debug-wait When running in server mode, wait @var{n} seconds before entering the actual processing loop and print the pid. This gives time to attach a debugger. @item --debug-ccid-driver @opindex debug-wait Enable debug output from the included CCID driver for smartcards. Using this option twice will also enable some tracing of the T=1 protocol. Note that this option may reveal sensitive data. @item --debug-disable-ticker @opindex debug-disable-ticker This option disables all ticker functions like checking for card insertions. @item --debug-allow-core-dump @opindex debug-allow-core-dump For security reasons we won't create a core dump when the process aborts. For debugging purposes it is sometimes better to allow core dump. This option enables it and also changes the working directory to @file{/tmp} when running in @option{--server} mode. @item --debug-log-tid @opindex debug-log-tid This option appends a thread ID to the PID in the log output. @item --debug-assuan-log-cats @var{cats} @opindex debug-assuan-log-cats @efindex ASSUAN_DEBUG Changes the active Libassuan logging categories to @var{cats}. The value for @var{cats} is an unsigned integer given in usual C-Syntax. A value of 0 switches to a default category. If this option is not used the categories are taken from the environment variable @code{ASSUAN_DEBUG}. Note that this option has only an effect if the Assuan debug flag has also been with the option @option{--debug}. For a list of categories see the Libassuan manual. @item --no-detach @opindex no-detach Don't detach the process from the console. This is mainly useful for debugging. @item --listen-backlog @var{n} @opindex listen-backlog Set the size of the queue for pending connections. The default is 64. This option has an effect only if @option{--multi-server} is also used. @item --log-file @var{file} @opindex log-file Append all logging output to @var{file}. This is very helpful in seeing what the agent actually does. Use @file{socket://} to log to socket. @item --pcsc-driver @var{library} @opindex pcsc-driver Use @var{library} to access the smartcard reader. The current default is @file{libpcsclite.so}. Instead of using this option you might also want to install a symbolic link to the default file name (e.g. from @file{libpcsclite.so.1}). @item --ctapi-driver @var{library} @opindex ctapi-driver Use @var{library} to access the smartcard reader. The current default is @file{libtowitoko.so}. Note that the use of this interface is deprecated; it may be removed in future releases. @item --disable-ccid @opindex disable-ccid Disable the integrated support for CCID compliant readers. This allows falling back to one of the other drivers even if the internal CCID driver can handle the reader. Note, that CCID support is only available if libusb was available at build time. @item --reader-port @var{number_or_string} @opindex reader-port This option may be used to specify the port of the card terminal. A value of 0 refers to the first serial device; add 32768 to access USB devices. The default is 32768 (first USB device). PC/SC or CCID readers might need a string here; run the program in verbose mode to get a list of available readers. The default is then the first reader found. To get a list of available CCID readers you may use this command: @cartouche @smallexample echo scd getinfo reader_list \ | gpg-connect-agent --decode | awk '/^D/ @{print $2@}' @end smallexample @end cartouche @item --card-timeout @var{n} @opindex card-timeout If @var{n} is not 0 and no client is actively using the card, the card will be powered down after @var{n} seconds. Powering down the card avoids a potential risk of damaging a card when used with certain cheap readers. This also allows applications that are not aware of Scdaemon to access the card. The disadvantage of using a card timeout is that accessing the card takes longer and that the user needs to enter the PIN again after the next power up. Note that with the current version of Scdaemon the card is powered down immediately at the next timer tick for any value of @var{n} other than 0. @item --enable-pinpad-varlen @opindex enable-pinpad-varlen Please specify this option when the card reader supports variable length input for pinpad (default is no). For known readers (listed in ccid-driver.c and apdu.c), this option is not needed. Note that if your card reader doesn't supports variable length input but you want to use it, you need to specify your pinpad request on your card. @item --disable-pinpad @opindex disable-pinpad Even if a card reader features a pinpad, do not try to use it. @item --deny-admin @opindex deny-admin @opindex allow-admin This option disables the use of admin class commands for card applications where this is supported. Currently we support it for the OpenPGP card. This option is useful to inhibit accidental access to admin class command which could ultimately lock the card through wrong PIN numbers. Note that GnuPG versions older than 2.0.11 featured an @option{--allow-admin} option which was required to use such admin commands. This option has no more effect today because the default is now to allow admin commands. @item --disable-application @var{name} @opindex disable-application This option disables the use of the card application named @var{name}. This is mainly useful for debugging or if a application with lower priority should be used by default. +@item --application-priority @var{namelist} +@opindex application-priority +This option allows to change the order in which applications of a card +a tried if no specific application was requested. @var{namelist} is a +space or comma delimited list of application names. Unknown names are +simply skipped. Applications not mentioned in the list are put in the +former order at the end of the new priority list. + +To get the list of current active applications, use +@cartouche +@smallexample + gpg-connect-agent 'scd getinfo app_list' /bye +@end smallexample +@end cartouche + @end table All the long options may also be given in the configuration file after stripping off the two leading dashes. @mansect card applications @node Card applications @section Description of card applications @command{scdaemon} supports the card applications as described below. @menu * OpenPGP Card:: The OpenPGP card application * NKS Card:: The Telesec NetKey card application * DINSIG Card:: The DINSIG card application * PKCS#15 Card:: The PKCS#15 card application * Geldkarte Card:: The Geldkarte application * SmartCard-HSM:: The SmartCard-HSM application * Undefined Card:: The Undefined stub application @end menu @node OpenPGP Card @subsection The OpenPGP card application ``openpgp'' This application is currently only used by @command{gpg} but may in future also be useful with @command{gpgsm}. Version 1 and version 2 of the card is supported. @noindent The specifications for these cards are available at@* @uref{http://g10code.com/docs/openpgp-card-1.0.pdf} and@* @uref{http://g10code.com/docs/openpgp-card-2.0.pdf}. @node NKS Card @subsection The Telesec NetKey card ``nks'' This is the main application of the Telesec cards as available in Germany. It is a superset of the German DINSIG card. The card is used by @command{gpgsm}. @node DINSIG Card @subsection The DINSIG card application ``dinsig'' This is an application as described in the German draft standard @emph{DIN V 66291-1}. It is intended to be used by cards supporting the German signature law and its bylaws (SigG and SigV). @node PKCS#15 Card @subsection The PKCS#15 card application ``p15'' This is common framework for smart card applications. It is used by @command{gpgsm}. @node Geldkarte Card @subsection The Geldkarte card application ``geldkarte'' This is a simple application to display information of a German Geldkarte. The Geldkarte is a small amount debit card application which comes with almost all German banking cards. @node SmartCard-HSM @subsection The SmartCard-HSM card application ``sc-hsm'' This application adds read-only support for keys and certificates stored on a @uref{http://www.smartcard-hsm.com, SmartCard-HSM}. To generate keys and store certificates you may use @uref{https://github.com/OpenSC/OpenSC/wiki/SmartCardHSM, OpenSC} or the tools from @uref{http://www.openscdp.org, OpenSCDP}. The SmartCard-HSM cards requires a card reader that supports Extended Length APDUs. @node Undefined Card @subsection The Undefined card application ``undefined'' This is a stub application to allow the use of the APDU command even if no supported application is found on the card. This application is not used automatically but must be explicitly requested using the SERIALNO command. @c ******************************************* @c *************** **************** @c *************** FILES **************** @c *************** **************** @c ******************************************* @mansect files @node Scdaemon Configuration @section Configuration files There are a few configuration files to control certain aspects of @command{scdaemons}'s operation. Unless noted, they are expected in the current home directory (@pxref{option --homedir}). @table @file @item scdaemon.conf @cindex scdaemon.conf This is the standard configuration file read by @command{scdaemon} on startup. It may contain any valid long option; the leading two dashes may not be entered and the option may not be abbreviated. This default name may be changed on the command line (@pxref{option --options}). @item scd-event @cindex scd-event If this file is present and executable, it will be called on every card reader's status change. An example of this script is provided with the distribution @item reader_@var{n}.status This file is created by @command{scdaemon} to let other applications now about reader status changes. Its use is now deprecated in favor of @file{scd-event}. @end table @c @c Examples @c @mansect examples @node Scdaemon Examples @section Examples @c man begin EXAMPLES @example $ scdaemon --server -v @end example @c man end @c @c Assuan Protocol @c @manpause @node Scdaemon Protocol @section Scdaemon's Assuan Protocol The SC-Daemon should be started by the system to provide access to external tokens. Using Smartcards on a multi-user system does not make much sense except for system services, but in this case no regular user accounts are hosted on the machine. A client connects to the SC-Daemon by connecting to the socket named @file{@value{LOCALRUNDIR}/scdaemon/socket}, configuration information is read from @var{@value{SYSCONFDIR}/scdaemon.conf} Each connection acts as one session, SC-Daemon takes care of synchronizing access to a token between sessions. @menu * Scdaemon SERIALNO:: Return the serial number. * Scdaemon LEARN:: Read all useful information from the card. * Scdaemon READCERT:: Return a certificate. * Scdaemon READKEY:: Return a public key. * Scdaemon PKSIGN:: Signing data with a Smartcard. * Scdaemon PKDECRYPT:: Decrypting data with a Smartcard. * Scdaemon GETATTR:: Read an attribute's value. * Scdaemon SETATTR:: Update an attribute's value. * Scdaemon WRITEKEY:: Write a key to a card. * Scdaemon GENKEY:: Generate a new key on-card. * Scdaemon RANDOM:: Return random bytes generated on-card. * Scdaemon PASSWD:: Change PINs. * Scdaemon CHECKPIN:: Perform a VERIFY operation. * Scdaemon RESTART:: Restart connection * Scdaemon APDU:: Send a verbatim APDU to the card @end menu @node Scdaemon SERIALNO @subsection Return the serial number This command should be used to check for the presence of a card. It is special in that it can be used to reset the card. Most other commands will return an error when a card change has been detected and the use of this function is therefore required. Background: We want to keep the client clear of handling card changes between operations; i.e. the client can assume that all operations are done on the same card unless he call this function. @example SERIALNO @end example Return the serial number of the card using a status response like: @example S SERIALNO D27600000000000000000000 @end example The serial number is the hex encoded value identified by the @code{0x5A} tag in the GDO file (FIX=0x2F02). @node Scdaemon LEARN @subsection Read all useful information from the card @example LEARN [--force] @end example Learn all useful information of the currently inserted card. When used without the @option{--force} option, the command might do an INQUIRE like this: @example INQUIRE KNOWNCARDP @end example The client should just send an @code{END} if the processing should go on or a @code{CANCEL} to force the function to terminate with a cancel error message. The response of this command is a list of status lines formatted as this: @example S KEYPAIRINFO @var{hexstring_with_keygrip} @var{hexstring_with_id} @end example If there is no certificate yet stored on the card a single "X" is returned in @var{hexstring_with_keygrip}. @node Scdaemon READCERT @subsection Return a certificate @example READCERT @var{hexified_certid}|@var{keyid} @end example This function is used to read a certificate identified by @var{hexified_certid} from the card. With OpenPGP cards the keyid @code{OpenPGP.3} may be used to read the certificate of version 2 cards. @node Scdaemon READKEY @subsection Return a public key @example READKEY @var{hexified_certid} @end example Return the public key for the given cert or key ID as an standard S-Expression. @node Scdaemon PKSIGN @subsection Signing data with a Smartcard To sign some data the caller should use the command @example SETDATA @var{hexstring} @end example to tell @command{scdaemon} about the data to be signed. The data must be given in hex notation. The actual signing is done using the command @example PKSIGN @var{keyid} @end example where @var{keyid} is the hexified ID of the key to be used. The key id may have been retrieved using the command @code{LEARN}. If another hash algorithm than SHA-1 is used, that algorithm may be given like: @example PKSIGN --hash=@var{algoname} @var{keyid} @end example With @var{algoname} are one of @code{sha1}, @code{rmd160} or @code{md5}. @node Scdaemon PKDECRYPT @subsection Decrypting data with a Smartcard To decrypt some data the caller should use the command @example SETDATA @var{hexstring} @end example to tell @command{scdaemon} about the data to be decrypted. The data must be given in hex notation. The actual decryption is then done using the command @example PKDECRYPT @var{keyid} @end example where @var{keyid} is the hexified ID of the key to be used. If the card is aware of the apdding format a status line with padding information is send before the plaintext data. The key for this status line is @code{PADDING} with the only defined value being 0 and meaning padding has been removed. @node Scdaemon GETATTR @subsection Read an attribute's value TO BE WRITTEN. @node Scdaemon SETATTR @subsection Update an attribute's value TO BE WRITTEN. @node Scdaemon WRITEKEY @subsection Write a key to a card @example WRITEKEY [--force] @var{keyid} @end example This command is used to store a secret key on a smartcard. The allowed keyids depend on the currently selected smartcard application. The actual keydata is requested using the inquiry @code{KEYDATA} and need to be provided without any protection. With @option{--force} set an existing key under this @var{keyid} will get overwritten. The key data is expected to be the usual canonical encoded S-expression. A PIN will be requested in most cases. This however depends on the actual card application. @node Scdaemon GENKEY @subsection Generate a new key on-card TO BE WRITTEN. @node Scdaemon RANDOM @subsection Return random bytes generated on-card TO BE WRITTEN. @node Scdaemon PASSWD @subsection Change PINs @example PASSWD [--reset] [--nullpin] @var{chvno} @end example Change the PIN or reset the retry counter of the card holder verification vector number @var{chvno}. The option @option{--nullpin} is used to initialize the PIN of TCOS cards (6 byte NullPIN only). @node Scdaemon CHECKPIN @subsection Perform a VERIFY operation @example CHECKPIN @var{idstr} @end example Perform a VERIFY operation without doing anything else. This may be used to initialize a the PIN cache earlier to long lasting operations. Its use is highly application dependent: @table @strong @item OpenPGP Perform a simple verify operation for CHV1 and CHV2, so that further operations won't ask for CHV2 and it is possible to do a cheap check on the PIN: If there is something wrong with the PIN entry system, only the regular CHV will get blocked and not the dangerous CHV3. @var{idstr} is the usual card's serial number in hex notation; an optional fingerprint part will get ignored. There is however a special mode if @var{idstr} is suffixed with the literal string @code{[CHV3]}: In this case the Admin PIN is checked if and only if the retry counter is still at 3. @end table @node Scdaemon RESTART @subsection Perform a RESTART operation @example RESTART @end example Restart the current connection; this is a kind of warm reset. It deletes the context used by this connection but does not actually reset the card. This is used by gpg-agent to reuse a primary pipe connection and may be used by clients to backup from a conflict in the serial command; i.e. to select another application. @node Scdaemon APDU @subsection Send a verbatim APDU to the card @example APDU [--atr] [--more] [--exlen[=@var{n}]] [@var{hexstring}] @end example Send an APDU to the current reader. This command bypasses the high level functions and sends the data directly to the card. @var{hexstring} is expected to be a proper APDU. If @var{hexstring} is not given no commands are send to the card; However the command will implicitly check whether the card is ready for use. Using the option @code{--atr} returns the ATR of the card as a status message before any data like this: @example S CARD-ATR 3BFA1300FF813180450031C173C00100009000B1 @end example Using the option @code{--more} handles the card status word MORE_DATA (61xx) and concatenate all responses to one block. Using the option @code{--exlen} the returned APDU may use extended length up to N bytes. If N is not given a default value is used (currently 4096). @mansect see also @ifset isman @command{gpg-agent}(1), @command{gpgsm}(1), @command{gpg2}(1) @end ifset @include see-also-note.texi - diff --git a/scd/app-common.h b/scd/app-common.h index 3df896228..c53bf06ca 100644 --- a/scd/app-common.h +++ b/scd/app-common.h @@ -1,224 +1,225 @@ /* app-common.h - Common declarations for all card applications * Copyright (C) 2003, 2005, 2008 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * $Id$ */ #ifndef GNUPG_SCD_APP_COMMON_H #define GNUPG_SCD_APP_COMMON_H #include #include /* Flags used with app_change_pin. */ #define APP_CHANGE_FLAG_RESET 1 /* PIN Reset mode. */ #define APP_CHANGE_FLAG_NULLPIN 2 /* NULL PIN mode. */ #define APP_CHANGE_FLAG_CLEAR 4 /* Clear the given PIN. */ /* Flags used with app_genkey. */ #define APP_GENKEY_FLAG_FORCE 1 /* Force overwriting existing key. */ /* Flags used with app_writekey. */ #define APP_WRITEKEY_FLAG_FORCE 1 /* Force overwriting existing key. */ /* Bit flags set by the decipher function into R_INFO. */ #define APP_DECIPHER_INFO_NOPAD 1 /* Padding has been removed. */ struct app_local_s; /* Defined by all app-*.c. */ struct app_ctx_s { struct app_ctx_s *next; npth_mutex_t lock; /* Number of connections currently using this application context. If this is not 0 the application has been initialized and the function pointers may be used. Note that for unsupported operations the particular function pointer is set to NULL */ unsigned int ref_count; /* Used reader slot. */ int slot; unsigned char *serialno; /* Serialnumber in raw form, allocated. */ size_t serialnolen; /* Length in octets of serialnumber. */ const char *cardtype; /* NULL or string with the token's type. */ const char *apptype; unsigned int cardversion;/* Firmware version of the token or 0. */ unsigned int appversion; /* Version of the application or 0. */ unsigned int card_status; unsigned int reset_requested:1; unsigned int periodical_check_needed:1; unsigned int did_chv1:1; unsigned int force_chv1:1; /* True if the card does not cache CHV1. */ unsigned int did_chv2:1; unsigned int did_chv3:1; struct app_local_s *app_local; /* Local to the application. */ struct { void (*deinit) (app_t app); gpg_error_t (*learn_status) (app_t app, ctrl_t ctrl, unsigned int flags); gpg_error_t (*readcert) (app_t app, const char *certid, unsigned char **cert, size_t *certlen); gpg_error_t (*readkey) (app_t app, const char *certid, unsigned char **pk, size_t *pklen); gpg_error_t (*getattr) (app_t app, ctrl_t ctrl, const char *name); gpg_error_t (*setattr) (app_t app, const char *name, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *value, size_t valuelen); gpg_error_t (*sign) (app_t app, const char *keyidstr, int hashalgo, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ); gpg_error_t (*auth) (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen); gpg_error_t (*decipher) (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen, unsigned int *r_info); gpg_error_t (*writecert) (app_t app, ctrl_t ctrl, const char *certid, gpg_error_t (*pincb)(void*,const char *,char **), void *pincb_arg, const unsigned char *data, size_t datalen); gpg_error_t (*writekey) (app_t app, ctrl_t ctrl, const char *keyid, unsigned int flags, gpg_error_t (*pincb)(void*,const char *,char **), void *pincb_arg, const unsigned char *pk, size_t pklen); gpg_error_t (*genkey) (app_t app, ctrl_t ctrl, const char *keyref, const char *keytype, unsigned int flags, time_t createtime, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); gpg_error_t (*change_pin) (app_t app, ctrl_t ctrl, const char *chvnostr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); gpg_error_t (*check_pin) (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); } fnc; }; /*-- app-help.c --*/ unsigned int app_help_count_bits (const unsigned char *a, size_t len); gpg_error_t app_help_get_keygrip_string (ksba_cert_t cert, char *hexkeygrip); gpg_error_t app_help_pubkey_from_cert (const void *cert, size_t certlen, unsigned char **r_pk, size_t *r_pklen); size_t app_help_read_length_of_cert (int slot, int fid, size_t *r_certoff); /*-- app.c --*/ +void app_update_priority_list (const char *arg); void app_send_card_list (ctrl_t ctrl); char *app_get_serialno (app_t app); void app_dump_state (void); void application_notify_card_reset (int slot); gpg_error_t check_application_conflict (const char *name, app_t app); gpg_error_t app_reset (app_t app, ctrl_t ctrl, int send_reset); gpg_error_t select_application (ctrl_t ctrl, const char *name, app_t *r_app, int scan, const unsigned char *serialno_bin, size_t serialno_bin_len); char *get_supported_applications (void); void release_application (app_t app, int locked_already); gpg_error_t app_munge_serialno (app_t app); gpg_error_t app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags); gpg_error_t app_readcert (app_t app, ctrl_t ctrl, const char *certid, unsigned char **cert, size_t *certlen); gpg_error_t app_readkey (app_t app, ctrl_t ctrl, const char *keyid, unsigned char **pk, size_t *pklen); gpg_error_t app_getattr (app_t app, ctrl_t ctrl, const char *name); gpg_error_t app_setattr (app_t app, ctrl_t ctrl, const char *name, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *value, size_t valuelen); gpg_error_t app_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ); gpg_error_t app_auth (app_t app, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen); gpg_error_t app_decipher (app_t app, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen, unsigned int *r_info); gpg_error_t app_writecert (app_t app, ctrl_t ctrl, const char *certidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *keydata, size_t keydatalen); gpg_error_t app_writekey (app_t app, ctrl_t ctrl, const char *keyidstr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *keydata, size_t keydatalen); gpg_error_t app_genkey (app_t app, ctrl_t ctrl, const char *keynostr, const char *keytype, unsigned int flags, time_t createtime, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); gpg_error_t app_get_challenge (app_t app, ctrl_t ctrl, size_t nbytes, unsigned char *buffer); gpg_error_t app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); gpg_error_t app_check_pin (app_t app, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); /*-- app-openpgp.c --*/ gpg_error_t app_select_openpgp (app_t app); /*-- app-nks.c --*/ gpg_error_t app_select_nks (app_t app); /*-- app-dinsig.c --*/ gpg_error_t app_select_dinsig (app_t app); /*-- app-p15.c --*/ gpg_error_t app_select_p15 (app_t app); /*-- app-geldkarte.c --*/ gpg_error_t app_select_geldkarte (app_t app); /*-- app-sc-hsm.c --*/ gpg_error_t app_select_sc_hsm (app_t app); /*-- app-piv.c --*/ gpg_error_t app_select_piv (app_t app); #endif /*GNUPG_SCD_APP_COMMON_H*/ diff --git a/scd/app.c b/scd/app.c index f0f6d7ecb..59a8880db 100644 --- a/scd/app.c +++ b/scd/app.c @@ -1,1249 +1,1291 @@ /* app.c - Application selection. * Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include "scdaemon.h" #include "../common/exechelp.h" #include "app-common.h" #include "iso7816.h" #include "apdu.h" #include "../common/tlv.h" static npth_mutex_t app_list_lock; static app_t app_top; /* The list of application names and there select function. Of no * specfic application is selected the first available application on * a card is selected. */ struct app_priority_list_s { char const *name; gpg_error_t (*select_func)(app_t); }; static struct app_priority_list_s app_priority_list[] = {{ "openpgp", app_select_openpgp }, { "piv", app_select_piv }, { "nks", app_select_nks }, { "p15", app_select_p15 }, { "geldkarte", app_select_geldkarte }, { "dinsig", app_select_dinsig }, { "sc-hsm", app_select_sc_hsm }, { NULL, NULL } }; +/* Initialization function to change the default app_priority_list. + * LIST is a list of comma or space separated strings with application + * names. Unknown names will only result in warning message. + * Application not mentioned in LIST are used in their original order + * after the given once. */ +void +app_update_priority_list (const char *arg) +{ + struct app_priority_list_s save; + char **names; + int i, j, idx; + + names = strtokenize (arg, ", "); + if (!names) + log_fatal ("strtokenize failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + + idx = 0; + for (i=0; names[i]; i++) + { + ascii_strlwr (names[i]); + for (j=0; j < i; j++) + if (!strcmp (names[j], names[i])) + break; + if (j < i) + { + log_info ("warning: duplicate application '%s' in priority list\n", + names[i]); + continue; + } + + for (j=idx; app_priority_list[j].name; j++) + if (!strcmp (names[i], app_priority_list[j].name)) + break; + if (!app_priority_list[j].name) + { + log_info ("warning: unknown application '%s' in priority list\n", + names[i]); + continue; + } + save = app_priority_list[idx]; + app_priority_list[idx] = app_priority_list[j]; + app_priority_list[j] = save; + idx++; + } + log_assert (idx < DIM (app_priority_list)); + + xfree (names); + for (i=0; app_priority_list[i].name; i++) + log_info ("app priority %d: %s\n", i, app_priority_list[i].name); +} + + static void print_progress_line (void *opaque, const char *what, int pc, int cur, int tot) { ctrl_t ctrl = opaque; char line[100]; if (ctrl) { snprintf (line, sizeof line, "%s %c %d %d", what, pc, cur, tot); send_status_direct (ctrl, "PROGRESS", line); } } /* Lock the reader SLOT. This function shall be used right before calling any of the actual application functions to serialize access to the reader. We do this always even if the reader is not actually used. This allows an actual connection to assume that it never shares a reader (while performing one command). Returns 0 on success; only then the unlock_reader function must be called after returning from the handler. */ static gpg_error_t lock_app (app_t app, ctrl_t ctrl) { if (npth_mutex_lock (&app->lock)) { gpg_error_t err = gpg_error_from_syserror (); log_error ("failed to acquire APP lock for %p: %s\n", app, gpg_strerror (err)); return err; } apdu_set_progress_cb (app->slot, print_progress_line, ctrl); apdu_set_prompt_cb (app->slot, popup_prompt, ctrl); return 0; } /* Release a lock on the reader. See lock_reader(). */ static void unlock_app (app_t app) { apdu_set_progress_cb (app->slot, NULL, NULL); apdu_set_prompt_cb (app->slot, NULL, NULL); if (npth_mutex_unlock (&app->lock)) { gpg_error_t err = gpg_error_from_syserror (); log_error ("failed to release APP lock for %p: %s\n", app, gpg_strerror (err)); } } /* This function may be called to print information pertaining to the current state of this module to the log. */ void app_dump_state (void) { app_t a; npth_mutex_lock (&app_list_lock); for (a = app_top; a; a = a->next) log_info ("app_dump_state: app=%p type='%s'\n", a, a->apptype); npth_mutex_unlock (&app_list_lock); } /* Check whether the application NAME is allowed. This does not mean we have support for it though. */ static int is_app_allowed (const char *name) { strlist_t l; for (l=opt.disabled_applications; l; l = l->next) if (!strcmp (l->d, name)) return 0; /* no */ return 1; /* yes */ } static gpg_error_t check_conflict (app_t app, const char *name) { if (!app || !name || (app->apptype && !ascii_strcasecmp (app->apptype, name))) return 0; if (app->apptype && !strcmp (app->apptype, "UNDEFINED")) return 0; log_info ("application '%s' in use - can't switch\n", app->apptype? app->apptype : ""); return gpg_error (GPG_ERR_CONFLICT); } /* This function is used by the serialno command to check for an application conflict which may appear if the serialno command is used to request a specific application and the connection has already done a select_application. */ gpg_error_t check_application_conflict (const char *name, app_t app) { return check_conflict (app, name); } gpg_error_t app_reset (app_t app, ctrl_t ctrl, int send_reset) { gpg_error_t err = 0; if (send_reset) { int sw; lock_app (app, ctrl); sw = apdu_reset (app->slot); if (sw) err = gpg_error (GPG_ERR_CARD_RESET); app->reset_requested = 1; unlock_app (app); scd_kick_the_loop (); gnupg_sleep (1); } else { ctrl->app_ctx = NULL; release_application (app, 0); } return err; } static gpg_error_t app_new_register (int slot, ctrl_t ctrl, const char *name, int periodical_check_needed) { gpg_error_t err = 0; app_t app = NULL; unsigned char *result = NULL; size_t resultlen; int want_undefined; int i; /* Need to allocate a new one. */ app = xtrycalloc (1, sizeof *app); if (!app) { err = gpg_error_from_syserror (); log_info ("error allocating context: %s\n", gpg_strerror (err)); return err; } app->slot = slot; app->card_status = (unsigned int)-1; if (npth_mutex_init (&app->lock, NULL)) { err = gpg_error_from_syserror (); log_error ("error initializing mutex: %s\n", gpg_strerror (err)); xfree (app); return err; } err = lock_app (app, ctrl); if (err) { xfree (app); return err; } want_undefined = (name && !strcmp (name, "undefined")); /* Try to read the GDO file first to get a default serial number. We skip this if the undefined application has been requested. */ if (!want_undefined) { err = iso7816_select_file (slot, 0x3F00, 1); if (gpg_err_code (err) == GPG_ERR_CARD) { /* Might be SW==0x7D00. Let's test whether it is a Yubikey * by selecting its manager application and then reading the * config. */ static char const yk_aid[] = { 0xA0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17 }; /*MGR*/ static char const otp_aid[] = { 0xA0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01 }; /*OTP*/ unsigned char *buf; size_t buflen; const unsigned char *s0; unsigned char formfactor; size_t n; if (!iso7816_select_application (slot, yk_aid, sizeof yk_aid, 0x0001) && !iso7816_apdu_direct (slot, "\x00\x1d\x00\x00\x00", 5, 0, NULL, &buf, &buflen)) { app->cardtype = "yubikey"; if (opt.verbose) { log_info ("Yubico: config="); log_printhex (buf, buflen, ""); } /* We skip the first byte which seems to be the total * length of the config data. */ if (buflen > 1) { s0 = find_tlv (buf+1, buflen-1, 0x04, &n); /* Form factor */ formfactor = (s0 && n == 1)? *s0 : 0; s0 = find_tlv (buf+1, buflen-1, 0x02, &n); /* Serial */ if (s0 && n >= 4) { app->serialno = xtrymalloc (3 + 1 + n); if (app->serialno) { app->serialnolen = 3 + 1 + n; app->serialno[0] = 0xff; app->serialno[1] = 0x02; app->serialno[2] = 0x0; app->serialno[3] = formfactor; memcpy (app->serialno + 4, s0, n); /* Note that we do not clear the error * so that no further serial number * testing is done. After all we just * set the serial number. */ } } s0 = find_tlv (buf+1, buflen-1, 0x05, &n); /* version */ if (s0 && n == 3) app->cardversion = ((s0[0]<<16)|(s0[1]<<8)|s0[2]); else if (!s0) { /* No version - this is not a Yubikey 5. We now * switch to the OTP app and take the first * three bytes of the reponse as version * number. */ xfree (buf); buf = NULL; if (!iso7816_select_application_ext (slot, otp_aid, sizeof otp_aid, 1, &buf, &buflen) && buflen > 3) app->cardversion = ((buf[0]<<16)|(buf[1]<<8)|buf[2]); } } xfree (buf); } } if (!err) err = iso7816_select_file (slot, 0x2F02, 0); if (!err) err = iso7816_read_binary (slot, 0, 0, &result, &resultlen); if (!err) { size_t n; const unsigned char *p; p = find_tlv_unchecked (result, resultlen, 0x5A, &n); if (p) resultlen -= (p-result); if (p && n > resultlen && n == 0x0d && resultlen+1 == n) { /* The object it does not fit into the buffer. This is an invalid encoding (or the buffer is too short. However, I have some test cards with such an invalid encoding and therefore I use this ugly workaround to return something I can further experiment with. */ log_info ("enabling BMI testcard workaround\n"); n--; } if (p && n <= resultlen) { /* The GDO file is pretty short, thus we simply reuse it for storing the serial number. */ memmove (result, p, n); app->serialno = result; app->serialnolen = n; err = app_munge_serialno (app); if (err) goto leave; } else xfree (result); result = NULL; } } /* Figure out the application to use. */ if (want_undefined) { /* We switch to the "undefined" application only if explicitly requested. */ app->apptype = "UNDEFINED"; /* Clear the error so that we don't run through the application * selection chain. */ err = 0; } else { /* For certain error codes, there is no need to try more. */ if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT || gpg_err_code (err) == GPG_ERR_ENODEV) goto leave; /* Set a default error so that we run through the application * selection chain. */ err = gpg_error (GPG_ERR_NOT_FOUND); } /* Find the first available app if NAME is NULL or the one matching * NAME but only if that application is also enabled. */ for (i=0; err && app_priority_list[i].name; i++) { if (is_app_allowed (app_priority_list[i].name) && (!name || !strcmp (name, app_priority_list[i].name))) err = app_priority_list[i].select_func (app); } if (err && name && gpg_err_code (err) != GPG_ERR_OBJ_TERM_STATE) err = gpg_error (GPG_ERR_NOT_SUPPORTED); leave: if (err) { if (name) log_info ("can't select application '%s': %s\n", name, gpg_strerror (err)); else log_info ("no supported card application found: %s\n", gpg_strerror (err)); unlock_app (app); xfree (app); return err; } app->periodical_check_needed = periodical_check_needed; app->next = app_top; app_top = app; unlock_app (app); return 0; } /* If called with NAME as NULL, select the best fitting application and return a context; otherwise select the application with NAME and return a context. Returns an error code and stores NULL at R_APP if no application was found or no card is present. */ gpg_error_t select_application (ctrl_t ctrl, const char *name, app_t *r_app, int scan, const unsigned char *serialno_bin, size_t serialno_bin_len) { gpg_error_t err = 0; app_t a, a_prev = NULL; *r_app = NULL; npth_mutex_lock (&app_list_lock); if (scan || !app_top) { struct dev_list *l; int new_app = 0; /* Scan the devices to find new device(s). */ err = apdu_dev_list_start (opt.reader_port, &l); if (err) { npth_mutex_unlock (&app_list_lock); return err; } while (1) { int slot; int periodical_check_needed_this; slot = apdu_open_reader (l, !app_top); if (slot < 0) break; periodical_check_needed_this = apdu_connect (slot); if (periodical_check_needed_this < 0) { /* We close a reader with no card. */ err = gpg_error (GPG_ERR_ENODEV); } else { err = app_new_register (slot, ctrl, name, periodical_check_needed_this); new_app++; } if (err) apdu_close_reader (slot); } apdu_dev_list_finish (l); /* If new device(s), kick the scdaemon loop. */ if (new_app) scd_kick_the_loop (); } for (a = app_top; a; a = a->next) { lock_app (a, ctrl); if (serialno_bin == NULL) break; if (a->serialnolen == serialno_bin_len && !memcmp (a->serialno, serialno_bin, a->serialnolen)) break; unlock_app (a); a_prev = a; } if (a) { err = check_conflict (a, name); if (!err) { a->ref_count++; *r_app = a; if (a_prev) { a_prev->next = a->next; a->next = app_top; app_top = a; } } unlock_app (a); } else err = gpg_error (GPG_ERR_ENODEV); npth_mutex_unlock (&app_list_lock); return err; } char * get_supported_applications (void) { - const char *list[] = { - "openpgp", - "piv", - "nks", - "p15", - "geldkarte", - "dinsig", - "sc-hsm", - /* Note: "undefined" is not listed here because it needs special - treatment by the client. */ - NULL - }; int idx; size_t nbytes; char *buffer, *p; + const char *s; - for (nbytes=1, idx=0; list[idx]; idx++) - nbytes += strlen (list[idx]) + 1 + 1; + for (nbytes=1, idx=0; (s=app_priority_list[idx].name); idx++) + nbytes += strlen (s) + 1 + 1; buffer = xtrymalloc (nbytes); if (!buffer) return NULL; - for (p=buffer, idx=0; list[idx]; idx++) - if (is_app_allowed (list[idx])) - p = stpcpy (stpcpy (p, list[idx]), ":\n"); + for (p=buffer, idx=0; (s=app_priority_list[idx].name); idx++) + if (is_app_allowed (s)) + p = stpcpy (stpcpy (p, s), ":\n"); *p = 0; return buffer; } /* Deallocate the application. */ static void deallocate_app (app_t app) { app_t a, a_prev = NULL; for (a = app_top; a; a = a->next) if (a == app) { if (a_prev == NULL) app_top = a->next; else a_prev->next = a->next; break; } else a_prev = a; if (app->ref_count) log_error ("trying to release context used yet (%d)\n", app->ref_count); if (app->fnc.deinit) { app->fnc.deinit (app); app->fnc.deinit = NULL; } xfree (app->serialno); unlock_app (app); xfree (app); } /* Free the resources associated with the application APP. APP is allowed to be NULL in which case this is a no-op. Note that we are using reference counting to track the users of the application and actually deferring the deallocation to allow for a later reuse by a new connection. */ void release_application (app_t app, int locked_already) { if (!app) return; /* We don't deallocate app here. Instead, we keep it. This is useful so that a card does not get reset even if only one session is using the card - this way the PIN cache and other cached data are preserved. */ if (!locked_already) lock_app (app, NULL); if (!app->ref_count) log_bug ("trying to release an already released context\n"); --app->ref_count; if (!locked_already) unlock_app (app); } /* The serial number may need some cosmetics. Do it here. This function shall only be called once after a new serial number has been put into APP->serialno. Prefixes we use: FF 00 00 = For serial numbers starting with an FF FF 01 00 = Some german p15 cards return an empty serial number so the serial number from the EF(TokenInfo) is used instead. FF 02 00 = Serial number from Yubikey config FF 7F 00 = No serialno. All other serial number not starting with FF are used as they are. */ gpg_error_t app_munge_serialno (app_t app) { if (app->serialnolen && app->serialno[0] == 0xff) { /* The serial number starts with our special prefix. This requires that we put our default prefix "FF0000" in front. */ unsigned char *p = xtrymalloc (app->serialnolen + 3); if (!p) return gpg_error_from_syserror (); memcpy (p, "\xff\0", 3); memcpy (p+3, app->serialno, app->serialnolen); app->serialnolen += 3; xfree (app->serialno); app->serialno = p; } else if (!app->serialnolen) { unsigned char *p = xtrymalloc (3); if (!p) return gpg_error_from_syserror (); memcpy (p, "\xff\x7f", 3); app->serialnolen = 3; xfree (app->serialno); app->serialno = p; } return 0; } /* Retrieve the serial number of the card. The serial number is returned as a malloced string (hex encoded) in SERIAL. Caller must free SERIAL unless the function returns an error. */ char * app_get_serialno (app_t app) { char *serial; if (!app) return NULL; if (!app->serialnolen) serial = xtrystrdup ("FF7F00"); else serial = bin2hex (app->serialno, app->serialnolen, NULL); return serial; } /* Write out the application specific status lines for the LEARN command. */ gpg_error_t app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) { gpg_error_t err; if (!app) return gpg_error (GPG_ERR_INV_VALUE); if (!app->fnc.learn_status) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); /* We do not send CARD and APPTYPE if only keypairinfo is requested. */ if (!(flags &1)) { if (app->cardtype) send_status_direct (ctrl, "CARDTYPE", app->cardtype); if (app->cardversion) send_status_printf (ctrl, "CARDVERSION", "%X", app->cardversion); if (app->apptype) send_status_direct (ctrl, "APPTYPE", app->apptype); if (app->appversion) send_status_printf (ctrl, "APPVERSION", "%X", app->appversion); } err = lock_app (app, ctrl); if (err) return err; err = app->fnc.learn_status (app, ctrl, flags); unlock_app (app); return err; } /* Read the certificate with id CERTID (as returned by learn_status in the CERTINFO status lines) and return it in the freshly allocated buffer put into CERT and the length of the certificate put into CERTLEN. */ gpg_error_t app_readcert (app_t app, ctrl_t ctrl, const char *certid, unsigned char **cert, size_t *certlen) { gpg_error_t err; if (!app) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.readcert) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.readcert (app, certid, cert, certlen); unlock_app (app); return err; } /* Read the key with ID KEYID. On success a canonical encoded S-expression with the public key will get stored at PK and its length (for assertions) at PKLEN; the caller must release that buffer. On error NULL will be stored at PK and PKLEN and an error code returned. This function might not be supported by all applications. */ gpg_error_t app_readkey (app_t app, ctrl_t ctrl, const char *keyid, unsigned char **pk, size_t *pklen) { gpg_error_t err; if (pk) *pk = NULL; if (pklen) *pklen = 0; if (!app || !keyid || !pk || !pklen) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.readkey) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err= app->fnc.readkey (app, keyid, pk, pklen); unlock_app (app); return err; } /* Perform a GETATTR operation. */ gpg_error_t app_getattr (app_t app, ctrl_t ctrl, const char *name) { gpg_error_t err; if (!app || !name || !*name) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (app->cardtype && name && !strcmp (name, "CARDTYPE")) { send_status_direct (ctrl, "CARDTYPE", app->cardtype); return 0; } if (app->apptype && name && !strcmp (name, "APPTYPE")) { send_status_direct (ctrl, "APPTYPE", app->apptype); return 0; } if (name && !strcmp (name, "SERIALNO")) { char *serial; serial = app_get_serialno (app); if (!serial) return gpg_error (GPG_ERR_INV_VALUE); send_status_direct (ctrl, "SERIALNO", serial); xfree (serial); return 0; } if (!app->fnc.getattr) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.getattr (app, ctrl, name); unlock_app (app); return err; } /* Perform a SETATTR operation. */ gpg_error_t app_setattr (app_t app, ctrl_t ctrl, const char *name, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *value, size_t valuelen) { gpg_error_t err; if (!app || !name || !*name || !value) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.setattr) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.setattr (app, name, pincb, pincb_arg, value, valuelen); unlock_app (app); return err; } /* Create the signature and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ gpg_error_t app_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { gpg_error_t err; if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.sign) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.sign (app, keyidstr, hashalgo, pincb, pincb_arg, indata, indatalen, outdata, outdatalen); unlock_app (app); if (opt.verbose) log_info ("operation sign result: %s\n", gpg_strerror (err)); return err; } /* Create the signature using the INTERNAL AUTHENTICATE command and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ gpg_error_t app_auth (app_t app, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { gpg_error_t err; if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.auth) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.auth (app, keyidstr, pincb, pincb_arg, indata, indatalen, outdata, outdatalen); unlock_app (app); if (opt.verbose) log_info ("operation auth result: %s\n", gpg_strerror (err)); return err; } /* Decrypt the data in INDATA and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ gpg_error_t app_decipher (app_t app, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen, unsigned int *r_info) { gpg_error_t err; *r_info = 0; if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.decipher) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.decipher (app, keyidstr, pincb, pincb_arg, indata, indatalen, outdata, outdatalen, r_info); unlock_app (app); if (opt.verbose) log_info ("operation decipher result: %s\n", gpg_strerror (err)); return err; } /* Perform the WRITECERT operation. */ gpg_error_t app_writecert (app_t app, ctrl_t ctrl, const char *certidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *data, size_t datalen) { gpg_error_t err; if (!app || !certidstr || !*certidstr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.writecert) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.writecert (app, ctrl, certidstr, pincb, pincb_arg, data, datalen); unlock_app (app); if (opt.verbose) log_info ("operation writecert result: %s\n", gpg_strerror (err)); return err; } /* Perform the WRITEKEY operation. */ gpg_error_t app_writekey (app_t app, ctrl_t ctrl, const char *keyidstr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *keydata, size_t keydatalen) { gpg_error_t err; if (!app || !keyidstr || !*keyidstr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.writekey) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.writekey (app, ctrl, keyidstr, flags, pincb, pincb_arg, keydata, keydatalen); unlock_app (app); if (opt.verbose) log_info ("operation writekey result: %s\n", gpg_strerror (err)); return err; } /* Perform a SETATTR operation. */ gpg_error_t app_genkey (app_t app, ctrl_t ctrl, const char *keynostr, const char *keytype, unsigned int flags, time_t createtime, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; if (!app || !keynostr || !*keynostr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.genkey) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.genkey (app, ctrl, keynostr, keytype, flags, createtime, pincb, pincb_arg); unlock_app (app); if (opt.verbose) log_info ("operation genkey result: %s\n", gpg_strerror (err)); return err; } /* Perform a GET CHALLENGE operation. This function is special as it directly accesses the card without any application specific wrapper. */ gpg_error_t app_get_challenge (app_t app, ctrl_t ctrl, size_t nbytes, unsigned char *buffer) { gpg_error_t err; if (!app || !nbytes || !buffer) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); err = lock_app (app, ctrl); if (err) return err; err = iso7816_get_challenge (app->slot, nbytes, buffer); unlock_app (app); return err; } /* Perform a CHANGE REFERENCE DATA or RESET RETRY COUNTER operation. */ gpg_error_t app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; if (!app || !chvnostr || !*chvnostr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.change_pin) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.change_pin (app, ctrl, chvnostr, flags, pincb, pincb_arg); unlock_app (app); if (opt.verbose) log_info ("operation change_pin result: %s\n", gpg_strerror (err)); return err; } /* Perform a VERIFY operation without doing anything else. This may be used to initialize a the PIN cache for long lasting other operations. Its use is highly application dependent. */ gpg_error_t app_check_pin (app_t app, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; if (!app || !keyidstr || !*keyidstr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.check_pin) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.check_pin (app, keyidstr, pincb, pincb_arg); unlock_app (app); if (opt.verbose) log_info ("operation check_pin result: %s\n", gpg_strerror (err)); return err; } static void report_change (int slot, int old_status, int cur_status) { char *homestr, *envstr; char *fname; char templ[50]; FILE *fp; snprintf (templ, sizeof templ, "reader_%d.status", slot); fname = make_filename (gnupg_homedir (), templ, NULL ); fp = fopen (fname, "w"); if (fp) { fprintf (fp, "%s\n", (cur_status & 1)? "USABLE": (cur_status & 4)? "ACTIVE": (cur_status & 2)? "PRESENT": "NOCARD"); fclose (fp); } xfree (fname); homestr = make_filename (gnupg_homedir (), NULL); if (gpgrt_asprintf (&envstr, "GNUPGHOME=%s", homestr) < 0) log_error ("out of core while building environment\n"); else { gpg_error_t err; const char *args[9], *envs[2]; char numbuf1[30], numbuf2[30], numbuf3[30]; envs[0] = envstr; envs[1] = NULL; sprintf (numbuf1, "%d", slot); sprintf (numbuf2, "0x%04X", old_status); sprintf (numbuf3, "0x%04X", cur_status); args[0] = "--reader-port"; args[1] = numbuf1; args[2] = "--old-code"; args[3] = numbuf2; args[4] = "--new-code"; args[5] = numbuf3; args[6] = "--status"; args[7] = ((cur_status & 1)? "USABLE": (cur_status & 4)? "ACTIVE": (cur_status & 2)? "PRESENT": "NOCARD"); args[8] = NULL; fname = make_filename (gnupg_homedir (), "scd-event", NULL); err = gnupg_spawn_process_detached (fname, args, envs); if (err && gpg_err_code (err) != GPG_ERR_ENOENT) log_error ("failed to run event handler '%s': %s\n", fname, gpg_strerror (err)); xfree (fname); xfree (envstr); } xfree (homestr); } int scd_update_reader_status_file (void) { app_t a, app_next; int periodical_check_needed = 0; npth_mutex_lock (&app_list_lock); for (a = app_top; a; a = app_next) { int sw; unsigned int status; lock_app (a, NULL); app_next = a->next; if (a->reset_requested) status = 0; else { sw = apdu_get_status (a->slot, 0, &status); if (sw == SW_HOST_NO_READER) { /* Most likely the _reader_ has been unplugged. */ status = 0; } else if (sw) { /* Get status failed. Ignore that. */ if (a->periodical_check_needed) periodical_check_needed = 1; unlock_app (a); continue; } } if (a->card_status != status) { report_change (a->slot, a->card_status, status); send_client_notifications (a, status == 0); if (status == 0) { log_debug ("Removal of a card: %d\n", a->slot); apdu_close_reader (a->slot); deallocate_app (a); } else { a->card_status = status; if (a->periodical_check_needed) periodical_check_needed = 1; unlock_app (a); } } else { if (a->periodical_check_needed) periodical_check_needed = 1; unlock_app (a); } } npth_mutex_unlock (&app_list_lock); return periodical_check_needed; } /* This function must be called once to initialize this module. This has to be done before a second thread is spawned. We can't do the static initialization because Pth emulation code might not be able to do a static init; in particular, it is not possible for W32. */ gpg_error_t initialize_module_command (void) { gpg_error_t err; if (npth_mutex_init (&app_list_lock, NULL)) { err = gpg_error_from_syserror (); log_error ("app: error initializing mutex: %s\n", gpg_strerror (err)); return err; } return apdu_init (); } void app_send_card_list (ctrl_t ctrl) { app_t a; char buf[65]; npth_mutex_lock (&app_list_lock); for (a = app_top; a; a = a->next) { if (DIM (buf) < 2 * a->serialnolen + 1) continue; bin2hex (a->serialno, a->serialnolen, buf); send_status_direct (ctrl, "SERIALNO", buf); } npth_mutex_unlock (&app_list_lock); } diff --git a/scd/scdaemon.c b/scd/scdaemon.c index 507108db0..42efb4c37 100644 --- a/scd/scdaemon.c +++ b/scd/scdaemon.c @@ -1,1450 +1,1462 @@ /* scdaemon.c - The GnuPG Smartcard Daemon * Copyright (C) 2001-2002, 2004-2005, 2007-2009 Free Software Foundation, Inc. * Copyright (C) 2001-2002, 2004-2005, 2007-2014 Werner Koch * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include #ifndef HAVE_W32_SYSTEM #include #include #endif /*HAVE_W32_SYSTEM*/ #include #include #include #define GNUPG_COMMON_NEED_AFLOCAL #include "scdaemon.h" #include #include #include /* malloc hooks */ #include "../common/i18n.h" #include "../common/sysutils.h" #include "app-common.h" #include "iso7816.h" #include "apdu.h" #include "ccid-driver.h" #include "../common/gc-opt-flags.h" #include "../common/asshelp.h" #include "../common/exechelp.h" #include "../common/init.h" #ifndef ENAMETOOLONG # define ENAMETOOLONG EINVAL #endif enum cmd_and_opt_values { aNull = 0, oCsh = 'c', oQuiet = 'q', oSh = 's', oVerbose = 'v', oNoVerbose = 500, aGPGConfList, aGPGConfTest, oOptions, oDebug, oDebugAll, oDebugLevel, oDebugWait, oDebugAllowCoreDump, oDebugCCIDDriver, oDebugLogTid, oDebugAssuanLogCats, oNoGreeting, oNoOptions, oHomedir, oNoDetach, oNoGrab, oLogFile, oServer, oMultiServer, oDaemon, oBatch, oReaderPort, oCardTimeout, octapiDriver, opcscDriver, oDisableCCID, oDisableOpenSC, oDisablePinpad, oAllowAdmin, oDenyAdmin, oDisableApplication, + oApplicationPriority, oEnablePinpadVarlen, oListenBacklog }; static ARGPARSE_OPTS opts[] = { ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"), ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), ARGPARSE_group (301, N_("@Options:\n ")), ARGPARSE_s_n (oServer,"server", N_("run in server mode (foreground)")), ARGPARSE_s_n (oMultiServer, "multi-server", N_("run in multi server mode (foreground)")), ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")), ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")), ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_n (oDebugAll, "debug-all", "@"), ARGPARSE_s_s (oDebugLevel, "debug-level" , N_("|LEVEL|set the debugging level to LEVEL")), ARGPARSE_s_i (oDebugWait, "debug-wait", "@"), ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"), ARGPARSE_s_n (oDebugCCIDDriver, "debug-ccid-driver", "@"), ARGPARSE_s_n (oDebugLogTid, "debug-log-tid", "@"), ARGPARSE_p_u (oDebugAssuanLogCats, "debug-assuan-log-cats", "@"), ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")), ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write a log to FILE")), ARGPARSE_s_s (oReaderPort, "reader-port", N_("|N|connect to reader at port N")), ARGPARSE_s_s (octapiDriver, "ctapi-driver", N_("|NAME|use NAME as ct-API driver")), ARGPARSE_s_s (opcscDriver, "pcsc-driver", N_("|NAME|use NAME as PC/SC driver")), ARGPARSE_s_n (oDisableCCID, "disable-ccid", #ifdef HAVE_LIBUSB N_("do not use the internal CCID driver") #else "@" #endif /* end --disable-ccid */), ARGPARSE_s_u (oCardTimeout, "card-timeout", N_("|N|disconnect the card after N seconds of inactivity")), ARGPARSE_s_n (oDisablePinpad, "disable-pinpad", N_("do not use a reader's pinpad")), ARGPARSE_ignore (300, "disable-keypad"), ARGPARSE_s_n (oAllowAdmin, "allow-admin", "@"), ARGPARSE_s_n (oDenyAdmin, "deny-admin", N_("deny the use of admin card commands")), ARGPARSE_s_s (oDisableApplication, "disable-application", "@"), + ARGPARSE_s_s (oApplicationPriority, "application-priority", + N_("|LIST|Change the application priority to LIST")), ARGPARSE_s_n (oEnablePinpadVarlen, "enable-pinpad-varlen", N_("use variable length input for pinpad")), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_MPI_VALUE , "mpi" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_CACHE_VALUE , "cache" }, { DBG_MEMSTAT_VALUE, "memstat" }, { DBG_HASHING_VALUE, "hashing" }, { DBG_IPC_VALUE , "ipc" }, { DBG_CARD_IO_VALUE, "cardio" }, { DBG_READER_VALUE , "reader" }, { 0, NULL } }; /* The card driver we use by default for PC/SC. */ #if defined(HAVE_W32_SYSTEM) || defined(__CYGWIN__) #define DEFAULT_PCSC_DRIVER "winscard.dll" #elif defined(__APPLE__) #define DEFAULT_PCSC_DRIVER "/System/Library/Frameworks/PCSC.framework/PCSC" #elif defined(__GLIBC__) #define DEFAULT_PCSC_DRIVER "libpcsclite.so.1" #else #define DEFAULT_PCSC_DRIVER "libpcsclite.so" #endif /* The timer tick used to check card removal. We poll every 500ms to let the user immediately know a status change. For a card reader with an interrupt endpoint, this timer is not used with the internal CCID driver. This is not too good for power saving but given that there is no easy way to block on card status changes it is the best we can do. For PC/SC we could in theory use an extra thread to wait for status changes but that requires a native thread because there is no way to make the underlying PC/SC card change function block using a Npth mechanism. Given that a native thread could only be used under W32 we don't do that at all. */ #define TIMERTICK_INTERVAL_SEC (0) #define TIMERTICK_INTERVAL_USEC (500000) /* Flag to indicate that a shutdown was requested. */ static int shutdown_pending; /* It is possible that we are currently running under setuid permissions */ static int maybe_setuid = 1; /* Flag telling whether we are running as a pipe server. */ static int pipe_server; /* Name of the communication socket */ static char *socket_name; /* Name of the redirected socket or NULL. */ static char *redir_socket_name; /* We need to keep track of the server's nonces (these are dummies for POSIX systems). */ static assuan_sock_nonce_t socket_nonce; /* Value for the listen() backlog argument. Change at runtime with * --listen-backlog. */ static int listen_backlog = 64; #ifdef HAVE_W32_SYSTEM static HANDLE the_event; #else /* PID to notify update of usb devices. */ static pid_t main_thread_pid; #endif #ifdef HAVE_PSELECT_NO_EINTR /* FD to notify changes. */ static int notify_fd; #endif static char *create_socket_name (char *standard_name); static gnupg_fd_t create_server_socket (const char *name, char **r_redir_name, assuan_sock_nonce_t *nonce); static void *start_connection_thread (void *arg); static void handle_connections (int listen_fd); /* Pth wrapper function definitions. */ ASSUAN_SYSTEM_NPTH_IMPL; static int active_connections; static char * make_libversion (const char *libname, const char *(*getfnc)(const char*)) { const char *s; char *result; if (maybe_setuid) { gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */ maybe_setuid = 0; } s = getfnc (NULL); result = xmalloc (strlen (libname) + 1 + strlen (s) + 1); strcpy (stpcpy (stpcpy (result, libname), " "), s); return result; } static const char * my_strusage (int level) { static char *ver_gcry, *ver_ksba; const char *p; switch (level) { case 11: p = "@SCDAEMON@ (@GNUPG@)"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 20: if (!ver_gcry) ver_gcry = make_libversion ("libgcrypt", gcry_check_version); p = ver_gcry; break; case 21: if (!ver_ksba) ver_ksba = make_libversion ("libksba", ksba_check_version); p = ver_ksba; break; case 1: case 40: p = _("Usage: @SCDAEMON@ [options] (-h for help)"); break; case 41: p = _("Syntax: scdaemon [options] [command [args]]\n" "Smartcard daemon for @GNUPG@\n"); break; default: p = NULL; } return p; } static int tid_log_callback (unsigned long *rvalue) { int len = sizeof (*rvalue); npth_t thread; thread = npth_self (); if (sizeof (thread) < len) len = sizeof (thread); memcpy (rvalue, &thread, len); return 2; /* Use use hex representation. */ } /* Setup the debugging. With a LEVEL of NULL only the active debug flags are propagated to the subsystems. With LEVEL set, a specific set of debug flags is set; thus overriding all flags already set. */ static void set_debug (const char *level) { int numok = (level && digitp (level)); int numlvl = numok? atoi (level) : 0; if (!level) ; else if (!strcmp (level, "none") || (numok && numlvl < 1)) opt.debug = 0; else if (!strcmp (level, "basic") || (numok && numlvl <= 2)) opt.debug = DBG_IPC_VALUE; else if (!strcmp (level, "advanced") || (numok && numlvl <= 5)) opt.debug = DBG_IPC_VALUE; else if (!strcmp (level, "expert") || (numok && numlvl <= 8)) opt.debug = (DBG_IPC_VALUE|DBG_CACHE_VALUE|DBG_CARD_IO_VALUE); else if (!strcmp (level, "guru") || numok) { opt.debug = ~0; /* Unless the "guru" string has been used we don't want to allow hashing debugging. The rationale is that people tend to select the highest debug value and would then clutter their disk with debug files which may reveal confidential data. */ if (numok) opt.debug &= ~(DBG_HASHING_VALUE); } else { log_error (_("invalid debug-level '%s' given\n"), level); scd_exit(2); } if (opt.debug && !opt.verbose) opt.verbose = 1; if (opt.debug && opt.quiet) opt.quiet = 0; if (opt.debug & DBG_MPI_VALUE) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); if (opt.debug & DBG_CRYPTO_VALUE ) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); if (opt.debug) parse_debug_flag (NULL, &opt.debug, debug_flags); } static void cleanup (void) { if (socket_name && *socket_name) { char *name; name = redir_socket_name? redir_socket_name : socket_name; gnupg_remove (name); *socket_name = 0; } } static void setup_signal_mask (void) { #ifndef HAVE_W32_SYSTEM npth_sigev_init (); npth_sigev_add (SIGHUP); npth_sigev_add (SIGUSR1); npth_sigev_add (SIGUSR2); npth_sigev_add (SIGINT); npth_sigev_add (SIGCONT); npth_sigev_add (SIGTERM); npth_sigev_fini (); main_thread_pid = getpid (); #endif } int main (int argc, char **argv ) { ARGPARSE_ARGS pargs; int orig_argc; char **orig_argv; FILE *configfp = NULL; char *configname = NULL; const char *shell; unsigned int configlineno; int parse_debug = 0; const char *debug_level = NULL; int default_config =1; int greeting = 0; int nogreeting = 0; int multi_server = 0; int is_daemon = 0; int nodetach = 0; int csh_style = 0; char *logfile = NULL; int debug_wait = 0; int gpgconf_list = 0; const char *config_filename = NULL; int allow_coredump = 0; struct assuan_malloc_hooks malloc_hooks; int res; npth_t pipecon_handler; + const char *application_priority = NULL; early_system_init (); set_strusage (my_strusage); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); /* Please note that we may running SUID(ROOT), so be very CAREFUL when adding any stuff between here and the call to INIT_SECMEM() somewhere after the option parsing */ log_set_prefix ("scdaemon", GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID); /* Make sure that our subsystems are ready. */ i18n_init (); init_common_subsystems (&argc, &argv); ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free); malloc_hooks.malloc = gcry_malloc; malloc_hooks.realloc = gcry_realloc; malloc_hooks.free = gcry_free; assuan_set_malloc_hooks (&malloc_hooks); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); assuan_sock_init (); setup_libassuan_logging (&opt.debug, NULL); setup_libgcrypt_logging (); gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); disable_core_dumps (); /* Set default options. */ opt.allow_admin = 1; opt.pcsc_driver = DEFAULT_PCSC_DRIVER; shell = getenv ("SHELL"); if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") ) csh_style = 1; /* Check whether we have a config file on the commandline */ orig_argc = argc; orig_argv = argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */ while (arg_parse( &pargs, opts)) { if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll) parse_debug++; else if (pargs.r_opt == oOptions) { /* yes there is one, so we do not try the default one, but read the option file when it is encountered at the commandline */ default_config = 0; } else if (pargs.r_opt == oNoOptions) default_config = 0; /* --no-options */ else if (pargs.r_opt == oHomedir) gnupg_set_homedir (pargs.r.ret_str); } /* initialize the secure memory. */ gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); maybe_setuid = 0; /* Now we are working under our real uid */ if (default_config) configname = make_filename (gnupg_homedir (), SCDAEMON_NAME EXTSEP_S "conf", NULL ); argc = orig_argc; argv = orig_argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1; /* do not remove the args */ next_pass: if (configname) { configlineno = 0; configfp = fopen (configname, "r"); if (!configfp) { if (default_config) { if( parse_debug ) log_info (_("Note: no default option file '%s'\n"), configname ); } else { log_error (_("option file '%s': %s\n"), configname, strerror(errno) ); exit(2); } xfree (configname); configname = NULL; } if (parse_debug && configname ) log_info (_("reading options from '%s'\n"), configname ); default_config = 0; } while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) ) { switch (pargs.r_opt) { case aGPGConfList: gpgconf_list = 1; break; case aGPGConfTest: gpgconf_list = 2; break; case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oBatch: opt.batch=1; break; case oDebug: if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags)) { pargs.r_opt = ARGPARSE_INVALID_ARG; pargs.err = ARGPARSE_PRINT_ERROR; } break; case oDebugAll: opt.debug = ~0; break; case oDebugLevel: debug_level = pargs.r.ret_str; break; case oDebugWait: debug_wait = pargs.r.ret_int; break; case oDebugAllowCoreDump: enable_core_dumps (); allow_coredump = 1; break; case oDebugCCIDDriver: #ifdef HAVE_LIBUSB ccid_set_debug_level (ccid_set_debug_level (-1)+1); #endif /*HAVE_LIBUSB*/ break; case oDebugLogTid: log_set_pid_suffix_cb (tid_log_callback); break; case oDebugAssuanLogCats: set_libassuan_log_cats (pargs.r.ret_ulong); break; case oOptions: /* config files may not be nested (silently ignore them) */ if (!configfp) { xfree(configname); configname = xstrdup(pargs.r.ret_str); goto next_pass; } break; case oNoGreeting: nogreeting = 1; break; case oNoVerbose: opt.verbose = 0; break; case oNoOptions: break; /* no-options */ case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oNoDetach: nodetach = 1; break; case oLogFile: logfile = pargs.r.ret_str; break; case oCsh: csh_style = 1; break; case oSh: csh_style = 0; break; case oServer: pipe_server = 1; break; case oMultiServer: pipe_server = 1; multi_server = 1; break; case oDaemon: is_daemon = 1; break; case oReaderPort: opt.reader_port = pargs.r.ret_str; break; case octapiDriver: opt.ctapi_driver = pargs.r.ret_str; break; case opcscDriver: opt.pcsc_driver = pargs.r.ret_str; break; case oDisableCCID: opt.disable_ccid = 1; break; case oDisableOpenSC: break; case oDisablePinpad: opt.disable_pinpad = 1; break; case oAllowAdmin: /* Dummy because allow is now the default. */ break; case oDenyAdmin: opt.allow_admin = 0; break; case oCardTimeout: opt.card_timeout = pargs.r.ret_ulong; break; case oDisableApplication: add_to_strlist (&opt.disabled_applications, pargs.r.ret_str); break; + case oApplicationPriority: + application_priority = pargs.r.ret_str; + break; + case oEnablePinpadVarlen: opt.enable_pinpad_varlen = 1; break; case oListenBacklog: listen_backlog = pargs.r.ret_int; break; default: pargs.err = configfp? ARGPARSE_PRINT_WARNING:ARGPARSE_PRINT_ERROR; break; } } if (configfp) { fclose( configfp ); configfp = NULL; /* Keep a copy of the config name for use by --gpgconf-list. */ config_filename = configname; configname = NULL; goto next_pass; } xfree (configname); configname = NULL; if (log_get_errorcount(0)) exit(2); if (nogreeting ) greeting = 0; if (greeting) { es_fprintf (es_stderr, "%s %s; %s\n", strusage(11), strusage(13), strusage(14) ); es_fprintf (es_stderr, "%s\n", strusage(15) ); } #ifdef IS_DEVELOPMENT_VERSION log_info ("NOTE: this is a development version!\n"); #endif /* Print a warning if an argument looks like an option. */ if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) { int i; for (i=0; i < argc; i++) if (argv[i][0] == '-' && argv[i][1] == '-') log_info (_("Note: '%s' is not considered an option\n"), argv[i]); } if (atexit (cleanup)) { log_error ("atexit failed\n"); cleanup (); exit (1); } set_debug (debug_level); if (initialize_module_command ()) { log_error ("initialization failed\n"); cleanup (); exit (1); } if (gpgconf_list == 2) scd_exit (0); if (gpgconf_list) { /* List options and default values in the GPG Conf format. */ char *filename = NULL; char *filename_esc; if (config_filename) filename = xstrdup (config_filename); else filename = make_filename (gnupg_homedir (), SCDAEMON_NAME EXTSEP_S "conf", NULL); filename_esc = percent_escape (filename, NULL); es_printf ("%s-%s.conf:%lu:\"%s\n", GPGCONF_NAME, SCDAEMON_NAME, GC_OPT_FLAG_DEFAULT, filename_esc); xfree (filename_esc); xfree (filename); es_printf ("verbose:%lu:\n" "quiet:%lu:\n" "debug-level:%lu:\"none:\n" "log-file:%lu:\n", GC_OPT_FLAG_NONE, GC_OPT_FLAG_NONE, GC_OPT_FLAG_DEFAULT, GC_OPT_FLAG_NONE ); es_printf ("reader-port:%lu:\n", GC_OPT_FLAG_NONE ); es_printf ("ctapi-driver:%lu:\n", GC_OPT_FLAG_NONE ); es_printf ("pcsc-driver:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, DEFAULT_PCSC_DRIVER ); #ifdef HAVE_LIBUSB es_printf ("disable-ccid:%lu:\n", GC_OPT_FLAG_NONE ); #endif es_printf ("deny-admin:%lu:\n", GC_OPT_FLAG_NONE ); es_printf ("disable-pinpad:%lu:\n", GC_OPT_FLAG_NONE ); es_printf ("card-timeout:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, 0); es_printf ("enable-pinpad-varlen:%lu:\n", GC_OPT_FLAG_NONE ); + es_printf ("application-priority:%lu:\n", GC_OPT_FLAG_NONE ); scd_exit (0); } /* Now start with logging to a file if this is desired. */ if (logfile) { log_set_file (logfile); log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID); } if (debug_wait && pipe_server) { log_debug ("waiting for debugger - my pid is %u .....\n", (unsigned int)getpid()); gnupg_sleep (debug_wait); log_debug ("... okay\n"); } + if (application_priority) + app_update_priority_list (application_priority); + if (pipe_server) { /* This is the simple pipe based server */ ctrl_t ctrl; npth_attr_t tattr; int fd = -1; #ifndef HAVE_W32_SYSTEM { struct sigaction sa; sa.sa_handler = SIG_IGN; sigemptyset (&sa.sa_mask); sa.sa_flags = 0; sigaction (SIGPIPE, &sa, NULL); } #endif npth_init (); setup_signal_mask (); gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); /* If --debug-allow-core-dump has been given we also need to switch the working directory to a place where we can actually write. */ if (allow_coredump) { if (chdir("/tmp")) log_debug ("chdir to '/tmp' failed: %s\n", strerror (errno)); else log_debug ("changed working directory to '/tmp'\n"); } /* In multi server mode we need to listen on an additional socket. Create that socket now before starting the handler for the pipe connection. This allows that handler to send back the name of that socket. */ if (multi_server) { socket_name = create_socket_name (SCDAEMON_SOCK_NAME); fd = FD2INT(create_server_socket (socket_name, &redir_socket_name, &socket_nonce)); } res = npth_attr_init (&tattr); if (res) { log_error ("error allocating thread attributes: %s\n", strerror (res)); scd_exit (2); } npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); ctrl = xtrycalloc (1, sizeof *ctrl); if ( !ctrl ) { log_error ("error allocating connection control data: %s\n", strerror (errno) ); scd_exit (2); } ctrl->thread_startup.fd = GNUPG_INVALID_FD; res = npth_create (&pipecon_handler, &tattr, start_connection_thread, ctrl); if (res) { log_error ("error spawning pipe connection handler: %s\n", strerror (res) ); xfree (ctrl); scd_exit (2); } npth_setname_np (pipecon_handler, "pipe-connection"); npth_attr_destroy (&tattr); /* We run handle_connection to wait for the shutdown signal and to run the ticker stuff. */ handle_connections (fd); if (fd != -1) close (fd); } else if (!is_daemon) { log_info (_("please use the option '--daemon'" " to run the program in the background\n")); } else { /* Regular server mode */ int fd; #ifndef HAVE_W32_SYSTEM pid_t pid; int i; #endif /* Create the socket. */ socket_name = create_socket_name (SCDAEMON_SOCK_NAME); fd = FD2INT (create_server_socket (socket_name, &redir_socket_name, &socket_nonce)); fflush (NULL); #ifdef HAVE_W32_SYSTEM (void)csh_style; (void)nodetach; #else pid = fork (); if (pid == (pid_t)-1) { log_fatal ("fork failed: %s\n", strerror (errno) ); exit (1); } else if (pid) { /* we are the parent */ char *infostr; close (fd); /* create the info string: :: */ if (gpgrt_asprintf (&infostr, "SCDAEMON_INFO=%s:%lu:1", socket_name, (ulong) pid) < 0) { log_error ("out of core\n"); kill (pid, SIGTERM); exit (1); } *socket_name = 0; /* don't let cleanup() remove the socket - the child should do this from now on */ if (argc) { /* run the program given on the commandline */ if (putenv (infostr)) { log_error ("failed to set environment: %s\n", strerror (errno) ); kill (pid, SIGTERM ); exit (1); } execvp (argv[0], argv); log_error ("failed to run the command: %s\n", strerror (errno)); kill (pid, SIGTERM); exit (1); } else { /* Print the environment string, so that the caller can use shell's eval to set it */ if (csh_style) { *strchr (infostr, '=') = ' '; es_printf ( "setenv %s;\n", infostr); } else { es_printf ( "%s; export SCDAEMON_INFO;\n", infostr); } xfree (infostr); exit (0); } /* NOTREACHED */ } /* end parent */ /* This is the child. */ npth_init (); setup_signal_mask (); gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); /* Detach from tty and put process into a new session. */ if (!nodetach ) { /* Close stdin, stdout and stderr unless it is the log stream. */ for (i=0; i <= 2; i++) { if (!log_test_fd (i) && i != fd ) { if ( !close (i) && open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1) { log_error ("failed to open '%s': %s\n", "/dev/null", strerror (errno)); cleanup (); exit (1); } } } if (setsid() == -1) { log_error ("setsid() failed: %s\n", strerror(errno) ); cleanup (); exit (1); } } { struct sigaction sa; sa.sa_handler = SIG_IGN; sigemptyset (&sa.sa_mask); sa.sa_flags = 0; sigaction (SIGPIPE, &sa, NULL); } #endif /*!HAVE_W32_SYSTEM*/ if (gnupg_chdir (gnupg_daemon_rootdir ())) { log_error ("chdir to '%s' failed: %s\n", gnupg_daemon_rootdir (), strerror (errno)); exit (1); } handle_connections (fd); close (fd); } return 0; } void scd_exit (int rc) { apdu_prepare_exit (); #if 0 #warning no update_random_seed_file update_random_seed_file(); #endif #if 0 /* at this time a bit annoying */ if (opt.debug & DBG_MEMSTAT_VALUE) { gcry_control( GCRYCTL_DUMP_MEMORY_STATS ); gcry_control( GCRYCTL_DUMP_RANDOM_STATS ); } if (opt.debug) gcry_control (GCRYCTL_DUMP_SECMEM_STATS ); #endif gcry_control (GCRYCTL_TERM_SECMEM ); rc = rc? rc : log_get_errorcount(0)? 2 : 0; exit (rc); } static void scd_init_default_ctrl (ctrl_t ctrl) { (void)ctrl; } static void scd_deinit_default_ctrl (ctrl_t ctrl) { if (!ctrl) return; xfree (ctrl->in_data.value); ctrl->in_data.value = NULL; ctrl->in_data.valuelen = 0; } /* Return the name of the socket to be used to connect to this process. If no socket is available, return NULL. */ const char * scd_get_socket_name () { if (socket_name && *socket_name) return socket_name; return NULL; } #ifndef HAVE_W32_SYSTEM static void handle_signal (int signo) { switch (signo) { case SIGHUP: log_info ("SIGHUP received - " "re-reading configuration and resetting cards\n"); /* reread_configuration (); */ break; case SIGUSR1: log_info ("SIGUSR1 received - printing internal information:\n"); /* Fixme: We need to see how to integrate pth dumping into our logging system. */ /* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */ app_dump_state (); break; case SIGUSR2: log_info ("SIGUSR2 received - no action defined\n"); break; case SIGCONT: /* Nothing. */ log_debug ("SIGCONT received - breaking select\n"); break; case SIGTERM: if (!shutdown_pending) log_info ("SIGTERM received - shutting down ...\n"); else log_info ("SIGTERM received - still %i running threads\n", active_connections); shutdown_pending++; if (shutdown_pending > 2) { log_info ("shutdown forced\n"); log_info ("%s %s stopped\n", strusage(11), strusage(13) ); cleanup (); scd_exit (0); } break; case SIGINT: log_info ("SIGINT received - immediate shutdown\n"); log_info( "%s %s stopped\n", strusage(11), strusage(13)); cleanup (); scd_exit (0); break; default: log_info ("signal %d received - no action defined\n", signo); } } #endif /*!HAVE_W32_SYSTEM*/ /* Create a name for the socket. We check for valid characters as well as against a maximum allowed length for a unix domain socket is done. The function terminates the process in case of an error. Returns: Pointer to an allcoated string with the absolute name of the socket used. */ static char * create_socket_name (char *standard_name) { char *name; name = make_filename (gnupg_socketdir (), standard_name, NULL); if (strchr (name, PATHSEP_C)) { log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S); scd_exit (2); } return name; } /* Create a Unix domain socket with NAME. Returns the file descriptor or terminates the process in case of an error. If the socket has been redirected the name of the real socket is stored as a malloced string at R_REDIR_NAME. */ static gnupg_fd_t create_server_socket (const char *name, char **r_redir_name, assuan_sock_nonce_t *nonce) { struct sockaddr *addr; struct sockaddr_un *unaddr; socklen_t len; gnupg_fd_t fd; int rc; xfree (*r_redir_name); *r_redir_name = NULL; fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0); if (fd == GNUPG_INVALID_FD) { log_error (_("can't create socket: %s\n"), strerror (errno)); scd_exit (2); } unaddr = xmalloc (sizeof (*unaddr)); addr = (struct sockaddr*)unaddr; { int redirected; if (assuan_sock_set_sockaddr_un (name, addr, &redirected)) { if (errno == ENAMETOOLONG) log_error (_("socket name '%s' is too long\n"), name); else log_error ("error preparing socket '%s': %s\n", name, gpg_strerror (gpg_error_from_syserror ())); scd_exit (2); } if (redirected) { *r_redir_name = xstrdup (unaddr->sun_path); if (opt.verbose) log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name); } } len = SUN_LEN (unaddr); rc = assuan_sock_bind (fd, addr, len); if (rc == -1 && errno == EADDRINUSE) { gnupg_remove (unaddr->sun_path); rc = assuan_sock_bind (fd, addr, len); } if (rc != -1 && (rc=assuan_sock_get_nonce (addr, len, nonce))) log_error (_("error getting nonce for the socket\n")); if (rc == -1) { log_error (_("error binding socket to '%s': %s\n"), unaddr->sun_path, gpg_strerror (gpg_error_from_syserror ())); assuan_sock_close (fd); scd_exit (2); } if (gnupg_chmod (unaddr->sun_path, "-rwx")) log_error (_("can't set permissions of '%s': %s\n"), unaddr->sun_path, strerror (errno)); if (listen (FD2INT(fd), listen_backlog) == -1) { log_error ("listen(fd, %d) failed: %s\n", listen_backlog, gpg_strerror (gpg_error_from_syserror ())); assuan_sock_close (fd); scd_exit (2); } if (opt.verbose) log_info (_("listening on socket '%s'\n"), unaddr->sun_path); return fd; } /* This is the standard connection thread's main function. */ static void * start_connection_thread (void *arg) { ctrl_t ctrl = arg; if (ctrl->thread_startup.fd != GNUPG_INVALID_FD && assuan_sock_check_nonce (ctrl->thread_startup.fd, &socket_nonce)) { log_info (_("error reading nonce on fd %d: %s\n"), FD2INT(ctrl->thread_startup.fd), strerror (errno)); assuan_sock_close (ctrl->thread_startup.fd); xfree (ctrl); return NULL; } active_connections++; scd_init_default_ctrl (ctrl); if (opt.verbose) log_info (_("handler for fd %d started\n"), FD2INT(ctrl->thread_startup.fd)); /* If this is a pipe server, we request a shutdown if the command handler asked for it. With the next ticker event and given that no other connections are running the shutdown will then happen. */ if (scd_command_handler (ctrl, FD2INT(ctrl->thread_startup.fd)) && pipe_server) shutdown_pending = 1; if (opt.verbose) log_info (_("handler for fd %d terminated\n"), FD2INT (ctrl->thread_startup.fd)); scd_deinit_default_ctrl (ctrl); xfree (ctrl); if (--active_connections == 0) scd_kick_the_loop (); return NULL; } void scd_kick_the_loop (void) { /* Kick the select loop. */ #ifdef HAVE_W32_SYSTEM int ret = SetEvent (the_event); if (ret == 0) log_error ("SetEvent for scd_kick_the_loop failed: %s\n", w32_strerror (-1)); #elif defined(HAVE_PSELECT_NO_EINTR) write (notify_fd, "", 1); #else int ret = kill (main_thread_pid, SIGCONT); if (ret < 0) log_error ("SetEvent for scd_kick_the_loop failed: %s\n", gpg_strerror (gpg_error_from_syserror ())); #endif } /* Connection handler loop. Wait for connection requests and spawn a thread after accepting a connection. LISTEN_FD is allowed to be -1 in which case this code will only do regular timeouts and handle signals. */ static void handle_connections (int listen_fd) { npth_attr_t tattr; struct sockaddr_un paddr; socklen_t plen; fd_set fdset, read_fdset; int nfd; int ret; int fd; struct timespec timeout; struct timespec *t; int saved_errno; #ifdef HAVE_W32_SYSTEM HANDLE events[2]; unsigned int events_set; #else int signo; #endif #ifdef HAVE_PSELECT_NO_EINTR int pipe_fd[2]; ret = gnupg_create_pipe (pipe_fd); if (ret) { log_error ("pipe creation failed: %s\n", gpg_strerror (ret)); return; } notify_fd = pipe_fd[1]; #endif ret = npth_attr_init(&tattr); if (ret) { log_error ("npth_attr_init failed: %s\n", strerror (ret)); return; } npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); #ifdef HAVE_W32_SYSTEM { HANDLE h, h2; SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE}; events[0] = the_event = INVALID_HANDLE_VALUE; events[1] = INVALID_HANDLE_VALUE; h = CreateEvent (&sa, TRUE, FALSE, NULL); if (!h) log_error ("can't create scd event: %s\n", w32_strerror (-1) ); else if (!DuplicateHandle (GetCurrentProcess(), h, GetCurrentProcess(), &h2, EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0)) { log_error ("setting synchronize for scd_kick_the_loop failed: %s\n", w32_strerror (-1) ); CloseHandle (h); } else { CloseHandle (h); events[0] = the_event = h2; } } #endif FD_ZERO (&fdset); nfd = 0; if (listen_fd != -1) { FD_SET (listen_fd, &fdset); nfd = listen_fd; } for (;;) { int periodical_check; int max_fd = nfd; if (shutdown_pending) { if (active_connections == 0) break; /* ready */ /* Do not accept anymore connections but wait for existing connections to terminate. We do this by clearing out all file descriptors to wait for, so that the select will be used to just wait on a signal or timeout event. */ FD_ZERO (&fdset); listen_fd = -1; } periodical_check = scd_update_reader_status_file (); timeout.tv_sec = TIMERTICK_INTERVAL_SEC; timeout.tv_nsec = TIMERTICK_INTERVAL_USEC * 1000; if (shutdown_pending || periodical_check) t = &timeout; else t = NULL; /* POSIX says that fd_set should be implemented as a structure, thus a simple assignment is fine to copy the entire set. */ read_fdset = fdset; #ifdef HAVE_PSELECT_NO_EINTR FD_SET (pipe_fd[0], &read_fdset); if (max_fd < pipe_fd[0]) max_fd = pipe_fd[0]; #else (void)max_fd; #endif #ifndef HAVE_W32_SYSTEM ret = npth_pselect (max_fd+1, &read_fdset, NULL, NULL, t, npth_sigev_sigmask ()); saved_errno = errno; while (npth_sigev_get_pending(&signo)) handle_signal (signo); #else ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, t, events, &events_set); saved_errno = errno; if (events_set & 1) continue; #endif if (ret == -1 && saved_errno != EINTR) { log_error (_("npth_pselect failed: %s - waiting 1s\n"), strerror (saved_errno)); npth_sleep (1); continue; } if (ret <= 0) /* Timeout. Will be handled when calculating the next timeout. */ continue; #ifdef HAVE_PSELECT_NO_EINTR if (FD_ISSET (pipe_fd[0], &read_fdset)) { char buf[256]; read (pipe_fd[0], buf, sizeof buf); } #endif if (listen_fd != -1 && FD_ISSET (listen_fd, &read_fdset)) { ctrl_t ctrl; plen = sizeof paddr; fd = npth_accept (listen_fd, (struct sockaddr *)&paddr, &plen); if (fd == -1) { log_error ("accept failed: %s\n", strerror (errno)); } else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)) ) { log_error ("error allocating connection control data: %s\n", strerror (errno) ); close (fd); } else { char threadname[50]; npth_t thread; snprintf (threadname, sizeof threadname, "conn fd=%d", fd); ctrl->thread_startup.fd = INT2FD (fd); ret = npth_create (&thread, &tattr, start_connection_thread, ctrl); if (ret) { log_error ("error spawning connection handler: %s\n", strerror (ret)); xfree (ctrl); close (fd); } else npth_setname_np (thread, threadname); } } } #ifdef HAVE_W32_SYSTEM if (the_event != INVALID_HANDLE_VALUE) CloseHandle (the_event); #endif #ifdef HAVE_PSELECT_NO_EINTR close (pipe_fd[0]); close (pipe_fd[1]); #endif cleanup (); log_info (_("%s %s stopped\n"), strusage(11), strusage(13)); npth_attr_destroy (&tattr); } /* Return the number of active connections. */ int get_active_connection_count (void) { return active_connections; } diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index 2ae79d91d..272b7571e 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -1,4197 +1,4201 @@ /* gpgconf-comp.c - Configuration utility for GnuPG. * Copyright (C) 2004, 2007-2011 Free Software Foundation, Inc. * Copyright (C) 2016 Werner Koch * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GnuPG; if not, see . */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SIGNAL_H # include #endif #include #ifdef HAVE_W32_SYSTEM # define WIN32_LEAN_AND_MEAN 1 # include #else # include # include #endif /* For log_logv(), asctimestamp(), gnupg_get_time (). */ #include "../common/util.h" #include "../common/i18n.h" #include "../common/exechelp.h" #include "../common/sysutils.h" #include "../common/status.h" #include "../common/gc-opt-flags.h" #include "gpgconf.h" /* There is a problem with gpg 1.4 under Windows: --gpgconf-list returns a plain filename without escaping. As long as we have not fixed that we need to use gpg2. */ #if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM) #define GPGNAME "gpg2" #else #define GPGNAME GPG_NAME #endif /* TODO: Components: Add more components and their options. Robustness: Do more validation. Call programs to do validation for us. Add options to change backend binary path. Extract binary path for some backends from gpgsm/gpg config. */ #if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 )) void gc_error (int status, int errnum, const char *fmt, ...) \ __attribute__ ((format (printf, 3, 4))); #endif /* Output a diagnostic message. If ERRNUM is not 0, then the output is followed by a colon, a white space, and the error string for the error number ERRNUM. In any case the output is finished by a newline. The message is prepended by the program name, a colon, and a whitespace. The output may be further formatted or redirected by the jnlib logging facility. */ void gc_error (int status, int errnum, const char *fmt, ...) { va_list arg_ptr; va_start (arg_ptr, fmt); log_logv (GPGRT_LOGLVL_ERROR, fmt, arg_ptr); va_end (arg_ptr); if (errnum) log_printf (": %s\n", strerror (errnum)); else log_printf ("\n"); if (status) { log_printf (NULL); log_printf ("fatal error (exit status %i)\n", status); gpgconf_failure (gpg_error_from_errno (errnum)); } } /* Forward declaration. */ static void gpg_agent_runtime_change (int killflag); static void scdaemon_runtime_change (int killflag); static void dirmngr_runtime_change (int killflag); /* Backend configuration. Backends are used to decide how the default and current value of an option can be determined, and how the option can be changed. To every option in every component belongs exactly one backend that controls and determines the option. Some backends are programs from the GPG system. Others might be implemented by GPGConf itself. If you change this enum, don't forget to update GC_BACKEND below. */ typedef enum { /* Any backend, used for find_option (). */ GC_BACKEND_ANY, /* The Gnu Privacy Guard. */ GC_BACKEND_GPG, /* The Gnu Privacy Guard for S/MIME. */ GC_BACKEND_GPGSM, /* The GPG Agent. */ GC_BACKEND_GPG_AGENT, /* The GnuPG SCDaemon. */ GC_BACKEND_SCDAEMON, /* The GnuPG directory manager. */ GC_BACKEND_DIRMNGR, /* The LDAP server list file for the director manager. */ GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST, /* The Pinentry (not a part of GnuPG, proper). */ GC_BACKEND_PINENTRY, /* The number of the above entries. */ GC_BACKEND_NR } gc_backend_t; /* To be able to implement generic algorithms for the various backends, we collect all information about them in this struct. */ static const struct { /* The name of the backend. */ const char *name; /* The name of the program that acts as the backend. Some backends don't have an associated program, but are implemented directly by GPGConf. In this case, PROGRAM is NULL. */ char *program; /* The module name (GNUPG_MODULE_NAME_foo) as defined by ../common/util.h. This value is used to get the actual installed path of the program. 0 is used if no backend program is available. */ char module_name; /* The runtime change callback. If KILLFLAG is true the component is killed and not just reloaded. */ void (*runtime_change) (int killflag); /* The option name for the configuration filename of this backend. This must be an absolute filename. It can be an option from a different backend (but then ordering of the options might matter). Note: This must be unique among all components. */ const char *option_config_filename; /* If this is a file backend rather than a program backend, then this is the name of the option associated with the file. */ const char *option_name; } gc_backend[GC_BACKEND_NR] = { { NULL }, /* GC_BACKEND_ANY dummy entry. */ { GPG_DISP_NAME, GPGNAME, GNUPG_MODULE_NAME_GPG, NULL, GPGCONF_NAME "-" GPG_NAME ".conf" }, { GPGSM_DISP_NAME, GPGSM_NAME, GNUPG_MODULE_NAME_GPGSM, NULL, GPGCONF_NAME "-" GPGSM_NAME ".conf" }, { GPG_AGENT_DISP_NAME, GPG_AGENT_NAME, GNUPG_MODULE_NAME_AGENT, gpg_agent_runtime_change, GPGCONF_NAME"-" GPG_AGENT_NAME ".conf" }, { SCDAEMON_DISP_NAME, SCDAEMON_NAME, GNUPG_MODULE_NAME_SCDAEMON, scdaemon_runtime_change, GPGCONF_NAME"-" SCDAEMON_NAME ".conf" }, { DIRMNGR_DISP_NAME, DIRMNGR_NAME, GNUPG_MODULE_NAME_DIRMNGR, dirmngr_runtime_change, GPGCONF_NAME "-" DIRMNGR_NAME ".conf" }, { DIRMNGR_DISP_NAME " LDAP Server List", NULL, 0, NULL, "ldapserverlist-file", "LDAP Server" }, { "Pinentry", "pinentry", GNUPG_MODULE_NAME_PINENTRY, NULL, GPGCONF_NAME "-pinentry.conf" }, }; /* Option configuration. */ /* An option might take an argument, or not. Argument types can be basic or complex. Basic types are generic and easy to validate. Complex types provide more specific information about the intended use, but can be difficult to validate. If you add to this enum, don't forget to update GC_ARG_TYPE below. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ typedef enum { /* Basic argument types. */ /* No argument. */ GC_ARG_TYPE_NONE = 0, /* A String argument. */ GC_ARG_TYPE_STRING = 1, /* A signed integer argument. */ GC_ARG_TYPE_INT32 = 2, /* An unsigned integer argument. */ GC_ARG_TYPE_UINT32 = 3, /* ADD NEW BASIC TYPE ENTRIES HERE. */ /* Complex argument types. */ /* A complete filename. */ GC_ARG_TYPE_FILENAME = 32, /* An LDAP server in the format HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN. */ GC_ARG_TYPE_LDAP_SERVER = 33, /* A 40 character fingerprint. */ GC_ARG_TYPE_KEY_FPR = 34, /* A user ID or key ID or fingerprint for a certificate. */ GC_ARG_TYPE_PUB_KEY = 35, /* A user ID or key ID or fingerprint for a certificate with a key. */ GC_ARG_TYPE_SEC_KEY = 36, /* A alias list made up of a key, an equal sign and a space separated list of values. */ GC_ARG_TYPE_ALIAS_LIST = 37, /* ADD NEW COMPLEX TYPE ENTRIES HERE. */ /* The number of the above entries. */ GC_ARG_TYPE_NR } gc_arg_type_t; /* For every argument, we record some information about it in the following struct. */ static const struct { /* For every argument type exists a basic argument type that can be used as a fallback for input and validation purposes. */ gc_arg_type_t fallback; /* Human-readable name of the type. */ const char *name; } gc_arg_type[GC_ARG_TYPE_NR] = { /* The basic argument types have their own types as fallback. */ { GC_ARG_TYPE_NONE, "none" }, { GC_ARG_TYPE_STRING, "string" }, { GC_ARG_TYPE_INT32, "int32" }, { GC_ARG_TYPE_UINT32, "uint32" }, /* Reserved basic type entries for future extension. */ { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, /* The complex argument types have a basic type as fallback. */ { GC_ARG_TYPE_STRING, "filename" }, { GC_ARG_TYPE_STRING, "ldap server" }, { GC_ARG_TYPE_STRING, "key fpr" }, { GC_ARG_TYPE_STRING, "pub key" }, { GC_ARG_TYPE_STRING, "sec key" }, { GC_ARG_TYPE_STRING, "alias list" }, }; /* Every option has an associated expert level, than can be used to hide advanced and expert options from beginners. If you add to this list, don't forget to update GC_LEVEL below. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ typedef enum { /* The basic options should always be displayed. */ GC_LEVEL_BASIC, /* The advanced options may be hidden from beginners. */ GC_LEVEL_ADVANCED, /* The expert options should only be displayed to experts. */ GC_LEVEL_EXPERT, /* The invisible options should normally never be displayed. */ GC_LEVEL_INVISIBLE, /* The internal options are never exported, they mark options that are recorded for internal use only. */ GC_LEVEL_INTERNAL, /* ADD NEW ENTRIES HERE. */ /* The number of the above entries. */ GC_LEVEL_NR } gc_expert_level_t; /* A description for each expert level. */ static const struct { const char *name; } gc_level[] = { { "basic" }, { "advanced" }, { "expert" }, { "invisible" }, { "internal" } }; /* Option flags. The flags which are used by the backends are defined by gc-opt-flags.h, included above. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING FLAGS, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ /* Some entries in the option list are not options, but mark the beginning of a new group of options. These entries have the GROUP flag set. */ #define GC_OPT_FLAG_GROUP (1UL << 0) /* The ARG_OPT flag for an option indicates that the argument is optional. This is never set for GC_ARG_TYPE_NONE options. */ #define GC_OPT_FLAG_ARG_OPT (1UL << 1) /* The LIST flag for an option indicates that the option can occur several times. A comma separated list of arguments is used as the argument value. */ #define GC_OPT_FLAG_LIST (1UL << 2) /* A human-readable description for each flag. */ static const struct { const char *name; } gc_flag[] = { { "group" }, { "optional arg" }, { "list" }, { "runtime" }, { "default" }, { "default desc" }, { "no arg desc" }, { "no change" } }; /* To each option, or group marker, the information in the GC_OPTION struct is provided. If you change this, don't forget to update the option list of each component. */ struct gc_option { /* If this is NULL, then this is a terminator in an array of unknown length. Otherwise, if this entry is a group marker (see FLAGS), then this is the name of the group described by this entry. Otherwise it is the name of the option described by this entry. The name must not contain a colon. */ const char *name; /* The option flags. If the GROUP flag is set, then this entry is a group marker, not an option, and only the fields LEVEL, DESC_DOMAIN and DESC are valid. In all other cases, this entry describes a new option and all fields are valid. */ unsigned long flags; /* The expert level. This field is valid for options and groups. A group has the expert level of the lowest-level option in the group. */ gc_expert_level_t level; /* A gettext domain in which the following description can be found. If this is NULL, then DESC is not translated. Valid for groups and options. Note that we try to keep the description of groups within the gnupg domain. IMPORTANT: If you add a new domain please make sure to add a code set switching call to the function my_dgettext further below. */ const char *desc_domain; /* A gettext description for this group or option. If it starts with a '|', then the string up to the next '|' describes the argument, and the description follows the second '|'. In general enclosing these description in N_() is not required because the description should be identical to the one in the help menu of the respective program. */ const char *desc; /* The following fields are only valid for options. */ /* The type of the option argument. */ gc_arg_type_t arg_type; /* The backend that implements this option. */ gc_backend_t backend; /* The following fields are set to NULL at startup (because all option's are declared as static variables). They are at the end of the list so that they can be omitted from the option declarations. */ /* This is true if the option is supported by this version of the backend. */ int active; /* The default value for this option. This is NULL if the option is not present in the backend, the empty string if no default is available, and otherwise a quoted string. */ char *default_value; /* The default argument is only valid if the "optional arg" flag is set, and specifies the default argument (value) that is used if the argument is omitted. */ char *default_arg; /* The current value of this option. */ char *value; /* The new flags for this option. The only defined flag is actually GC_OPT_FLAG_DEFAULT, and it means that the option should be deleted. In this case, NEW_VALUE is NULL. */ unsigned long new_flags; /* The new value of this option. */ char *new_value; }; typedef struct gc_option gc_option_t; /* Use this macro to terminate an option list. */ #define GC_OPTION_NULL { NULL } #ifndef BUILD_WITH_AGENT #define gc_options_gpg_agent NULL #else /* The options of the GC_COMPONENT_GPG_AGENT component. */ static gc_option_t gc_options_gpg_agent[] = { /* The configuration file to which we write the changes. */ { GPGCONF_NAME"-" GPG_AGENT_NAME ".conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT }, { "Monitor", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the diagnostic output") }, { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "verbose", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "quiet", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "be somewhat more quiet", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "Configuration", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the configuration") }, { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", "|FILE|read options from FILE", GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT }, { "disable-scdaemon", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", "do not use the SCdaemon", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "enable-ssh-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "enable ssh support", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "ssh-fingerprint-digest", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", "|ALGO|use ALGO to show ssh fingerprints", GC_ARG_TYPE_STRING, GC_BACKEND_GPG_AGENT }, { "enable-putty-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "enable putty support", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "enable-extended-key-format", GC_OPT_FLAG_RUNTIME, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "Debug", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Options useful for debugging") }, { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", "|LEVEL|set the debugging level to LEVEL", GC_ARG_TYPE_STRING, GC_BACKEND_GPG_AGENT }, { "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", N_("|FILE|write server mode logs to FILE"), GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, { "Security", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the security") }, { "default-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "|N|expire cached PINs after N seconds", GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, { "default-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", N_("|N|expire SSH keys after N seconds"), GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, { "max-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", N_("|N|set maximum PIN cache lifetime to N seconds"), GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, { "max-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", N_("|N|set maximum SSH key lifetime to N seconds"), GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, { "ignore-cache-for-signing", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "do not use the PIN cache when signing", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "allow-emacs-pinentry", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", "allow passphrase to be prompted through Emacs", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "grab", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "no-allow-external-cache", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "disallow the use of an external password cache", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "no-allow-mark-trusted", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", "disallow clients to mark keys as \"trusted\"", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "no-allow-loopback-pinentry", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", "disallow caller to override the pinentry", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "Passphrase policy", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Options enforcing a passphrase policy") }, { "enforce-passphrase-constraints", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", N_("do not allow bypassing the passphrase policy"), GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "min-passphrase-len", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", N_("|N|set minimal required length for new passphrases to N"), GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, { "min-passphrase-nonalpha", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", N_("|N|require at least N non-alpha characters for a new passphrase"), GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, { "check-passphrase-pattern", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", N_("|FILE|check new passphrases against pattern in FILE"), GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT }, { "max-passphrase-days", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", N_("|N|expire the passphrase after N days"), GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, { "enable-passphrase-history", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", N_("do not allow the reuse of old passphrases"), GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "pinentry-timeout", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", N_("|N|set the Pinentry timeout to N seconds"), GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, GC_OPTION_NULL }; #endif /*BUILD_WITH_AGENT*/ #ifndef BUILD_WITH_SCDAEMON #define gc_options_scdaemon NULL #else /* The options of the GC_COMPONENT_SCDAEMON component. */ static gc_option_t gc_options_scdaemon[] = { /* The configuration file to which we write the changes. */ { GPGCONF_NAME"-"SCDAEMON_NAME".conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_SCDAEMON }, { "Monitor", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the diagnostic output") }, { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "verbose", GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "be somewhat more quiet", GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, { "Configuration", GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT, "gnupg", N_("Options controlling the configuration") }, { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", "|FILE|read options from FILE", GC_ARG_TYPE_FILENAME, GC_BACKEND_SCDAEMON }, { "reader-port", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "|N|connect to reader at port N", GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON }, { "ctapi-driver", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", "|NAME|use NAME as ct-API driver", GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON }, { "pcsc-driver", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", "|NAME|use NAME as PC/SC driver", GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON }, { "disable-ccid", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", "do not use the internal CCID driver", GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, { "disable-pinpad", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "do not use a reader's pinpad", GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, { "enable-pinpad-varlen", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "use variable length input for pinpad", GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, { "card-timeout", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "|N|disconnect the card after N seconds of inactivity", GC_ARG_TYPE_UINT32, GC_BACKEND_SCDAEMON }, + { "application-priority", + GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, + "gnupg", "|LIST|Change the application priority to LIST", + GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON }, { "Debug", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Options useful for debugging") }, { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", "|LEVEL|set the debugging level to LEVEL", GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON }, { "log-file", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", N_("|FILE|write a log to FILE"), GC_ARG_TYPE_FILENAME, GC_BACKEND_SCDAEMON }, { "Security", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the security") }, { "deny-admin", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "deny the use of admin card commands", GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, GC_OPTION_NULL }; #endif /*BUILD_WITH_SCDAEMON*/ #ifndef BUILD_WITH_GPG #define gc_options_gpg NULL #else /* The options of the GC_COMPONENT_GPG component. */ static gc_option_t gc_options_gpg[] = { /* The configuration file to which we write the changes. */ { GPGCONF_NAME"-"GPG_NAME".conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG }, { "Monitor", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the diagnostic output") }, { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, "gnupg", "verbose", GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "be somewhat more quiet", GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, { "Configuration", GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT, "gnupg", N_("Options controlling the configuration") }, { "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", N_("|NAME|use NAME as default secret key"), GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", N_("|NAME|encrypt to user ID NAME as well"), GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "group", GC_OPT_FLAG_LIST, GC_LEVEL_ADVANCED, "gnupg", N_("|SPEC|set up email aliases"), GC_ARG_TYPE_ALIAS_LIST, GC_BACKEND_GPG }, { "options", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG }, { "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, NULL, NULL, GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "default-new-key-algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "default_pubkey_algo", (GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_NO_CHANGE), GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "trust-model", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "Debug", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Options useful for debugging") }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED, "gnupg", "|LEVEL|set the debugging level to LEVEL", GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", N_("|FILE|write server mode logs to FILE"), GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG }, /* { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, */ /* NULL, NULL, */ /* GC_ARG_TYPE_UINT32, GC_BACKEND_GPG }, */ { "Keyserver", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Configuration for Keyservers") }, { "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", N_("|URL|use keyserver at URL"), /* Deprecated - use dirmngr */ GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "allow-pka-lookup", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", N_("allow PKA lookups (DNS requests)"), GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, { "auto-key-locate", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", N_("|MECHANISMS|use MECHANISMS to locate keys by mail address"), GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "auto-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, { "no-auto-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, { "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", N_("disable all access to the dirmngr"), GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, { "max-cert-depth", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_UINT32, GC_BACKEND_GPG }, { "completes-needed", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_UINT32, GC_BACKEND_GPG }, { "marginals-needed", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_UINT32, GC_BACKEND_GPG }, GC_OPTION_NULL }; #endif /*BUILD_WITH_GPG*/ #ifndef BUILD_WITH_GPGSM #define gc_options_gpgsm NULL #else /* The options of the GC_COMPONENT_GPGSM component. */ static gc_option_t gc_options_gpgsm[] = { /* The configuration file to which we write the changes. */ { GPGCONF_NAME"-"GPGSM_NAME".conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPGSM }, { "Monitor", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the diagnostic output") }, { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, "gnupg", "verbose", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "be somewhat more quiet", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "Configuration", GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT, "gnupg", N_("Options controlling the configuration") }, { "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", N_("|NAME|use NAME as default secret key"), GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM }, { "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", N_("|NAME|encrypt to user ID NAME as well"), GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM }, { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", "|FILE|read options from FILE", GC_ARG_TYPE_FILENAME, GC_BACKEND_GPGSM }, { "prefer-system-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", "use system's dirmngr if available", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", N_("disable all access to the dirmngr"), GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "p12-charset", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", N_("|NAME|use encoding NAME for PKCS#12 passphrases"), GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM }, { "keyserver", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, "gnupg", N_("|SPEC|use this keyserver to lookup keys"), GC_ARG_TYPE_LDAP_SERVER, GC_BACKEND_GPGSM }, { "default_pubkey_algo", (GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_NO_CHANGE), GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM }, { "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, NULL, NULL, GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM }, { "Debug", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Options useful for debugging") }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED, "gnupg", "|LEVEL|set the debugging level to LEVEL", GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", N_("|FILE|write server mode logs to FILE"), GC_ARG_TYPE_FILENAME, GC_BACKEND_GPGSM }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_UINT32, GC_BACKEND_GPGSM }, { "Security", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the security") }, { "disable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "never consult a CRL", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "enable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "disable-trusted-cert-crl-check", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", N_("do not check CRLs for root certificates"), GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "enable-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", "check validity using OCSP", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "include-certs", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", "|N|number of certificates to include", GC_ARG_TYPE_INT32, GC_BACKEND_GPGSM }, { "disable-policy-checks", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", "do not check certificate policies", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "auto-issuer-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "fetch missing issuer certificates", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "cipher-algo", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", "|NAME|use cipher algorithm NAME", GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM }, GC_OPTION_NULL }; #endif /*BUILD_WITH_GPGSM*/ #ifndef BUILD_WITH_DIRMNGR #define gc_options_dirmngr NULL #else /* The options of the GC_COMPONENT_DIRMNGR component. */ static gc_option_t gc_options_dirmngr[] = { /* The configuration file to which we write the changes. */ { GPGCONF_NAME"-"DIRMNGR_NAME".conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR }, { "Monitor", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the diagnostic output") }, { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, "dirmngr", "verbose", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "be somewhat more quiet", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "Format", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the format of the output") }, { "sh", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "sh-style command output", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "csh", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "csh-style command output", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "Configuration", GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT, "gnupg", N_("Options controlling the configuration") }, { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "dirmngr", "|FILE|read options from FILE", GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR }, { "resolver-timeout", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_INT32, GC_BACKEND_DIRMNGR }, { "nameserver", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, { "Debug", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Options useful for debugging") }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED, "dirmngr", "|LEVEL|set the debugging level to LEVEL", GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, { "no-detach", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "do not detach from the console", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", N_("|FILE|write server mode logs to FILE"), GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR }, { "debug-wait", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR }, { "Enforcement", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the interactivity and enforcement") }, { "batch", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "run without asking a user", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "force", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "force loading of outdated CRLs", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "allow-version-check", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "allow online software version check", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "Tor", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the use of Tor") }, { "use-tor", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "route all network traffic via TOR", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "Keyserver", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Configuration for Keyservers") }, { "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", N_("|URL|use keyserver at URL"), GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, { "HTTP", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Configuration for HTTP servers") }, { "disable-http", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "inhibit the use of HTTP", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "ignore-http-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "ignore HTTP CRL distribution points", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "|URL|redirect all HTTP requests to URL", GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, { "honor-http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", N_("use system's HTTP proxy setting"), GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "LDAP", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Configuration of LDAP servers to use") }, { "disable-ldap", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "inhibit the use of LDAP", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "ignore-ldap-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "ignore LDAP CRL distribution points", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "|HOST|use HOST for LDAP queries", GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, { "only-ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "do not use fallback hosts with --ldap-proxy", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "add-servers", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "add new servers discovered in CRL distribution points" " to serverlist", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "ldaptimeout", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "|N|set LDAP timeout to N seconds", GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR }, /* The following entry must not be removed, as it is required for the GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST. */ { "ldapserverlist-file", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, "dirmngr", "|FILE|read LDAP server list from FILE", GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR }, /* This entry must come after at least one entry for GC_BACKEND_DIRMNGR in this component, so that the entry for "ldapserverlist-file will be initialized before this one. */ { "LDAP Server", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, "gnupg", N_("LDAP server list"), GC_ARG_TYPE_LDAP_SERVER, GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST }, { "max-replies", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "|N|do not return more than N items in one query", GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR }, { "OCSP", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Configuration for OCSP") }, { "allow-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "allow sending OCSP requests", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "ignore-ocsp-service-url", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "ignore certificate contained OCSP service URLs", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "ocsp-responder", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "|URL|use OCSP responder at URL", GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, { "ocsp-signer", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "|FPR|OCSP response signed by FPR", GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, GC_OPTION_NULL }; #endif /*BUILD_WITH_DIRMNGR*/ /* The options of the GC_COMPONENT_PINENTRY component. */ static gc_option_t gc_options_pinentry[] = { /* A dummy option to allow gc_component_list_components to find the pinentry backend. Needs to be a conf file. */ { GPGCONF_NAME"-pinentry.conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_PINENTRY }, GC_OPTION_NULL }; /* The information associated with each component. */ static const struct { /* The name of this component. Must not contain a colon (':') character. */ const char *name; /* The gettext domain for the description DESC. If this is NULL, then the description is not translated. */ const char *desc_domain; /* The description for this domain. */ const char *desc; /* The list of options for this component, terminated by GC_OPTION_NULL. */ gc_option_t *options; } gc_component[] = { { "gpg", "gnupg", N_("OpenPGP"), gc_options_gpg }, { "gpg-agent","gnupg", N_("Private Keys"), gc_options_gpg_agent }, { "scdaemon", "gnupg", N_("Smartcards"), gc_options_scdaemon }, { "gpgsm", "gnupg", N_("S/MIME"), gc_options_gpgsm }, { "dirmngr", "gnupg", N_("Network"), gc_options_dirmngr }, { "pinentry", "gnupg", N_("Passphrase Entry"), gc_options_pinentry } }; /* Structure used to collect error output of the backend programs. */ struct error_line_s; typedef struct error_line_s *error_line_t; struct error_line_s { error_line_t next; /* Link to next item. */ const char *fname; /* Name of the config file (points into BUFFER). */ unsigned int lineno; /* Line number of the config file. */ const char *errtext; /* Text of the error message (points into BUFFER). */ char buffer[1]; /* Helper buffer. */ }; /* Initialization and finalization. */ static void gc_option_free (gc_option_t *o) { if (o == NULL || o->name == NULL) return; xfree (o->value); gc_option_free (o + 1); } static void gc_components_free (void) { int i; for (i = 0; i < DIM (gc_component); i++) gc_option_free (gc_component[i].options); } void gc_components_init (void) { atexit (gc_components_free); } /* Engine specific support. */ static void gpg_agent_runtime_change (int killflag) { gpg_error_t err = 0; const char *pgmname; const char *argv[5]; pid_t pid = (pid_t)(-1); char *abs_homedir = NULL; int i = 0; pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); if (!gnupg_default_homedir_p ()) { abs_homedir = make_absfilename_try (gnupg_homedir (), NULL); if (!abs_homedir) err = gpg_error_from_syserror (); argv[i++] = "--homedir"; argv[i++] = abs_homedir; } argv[i++] = "--no-autostart"; argv[i++] = killflag? "KILLAGENT" : "RELOADAGENT"; argv[i++] = NULL; if (!err) err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid); if (!err) err = gnupg_wait_process (pgmname, pid, 1, NULL); if (err) gc_error (0, 0, "error running '%s %s': %s", pgmname, argv[1], gpg_strerror (err)); gnupg_release_process (pid); xfree (abs_homedir); } static void scdaemon_runtime_change (int killflag) { gpg_error_t err = 0; const char *pgmname; const char *argv[9]; pid_t pid = (pid_t)(-1); char *abs_homedir = NULL; int i = 0; (void)killflag; /* For scdaemon kill and reload are synonyms. */ /* We use "GETINFO app_running" to see whether the agent is already running and kill it only in this case. This avoids an explicit starting of the agent in case it is not yet running. There is obviously a race condition but that should not harm too much. */ pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); if (!gnupg_default_homedir_p ()) { abs_homedir = make_absfilename_try (gnupg_homedir (), NULL); if (!abs_homedir) err = gpg_error_from_syserror (); argv[i++] = "--homedir"; argv[i++] = abs_homedir; } argv[i++] = "-s"; argv[i++] = "--no-autostart"; argv[i++] = "GETINFO scd_running"; argv[i++] = "/if ${! $?}"; argv[i++] = "scd killscd"; argv[i++] = "/end"; argv[i++] = NULL; if (!err) err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid); if (!err) err = gnupg_wait_process (pgmname, pid, 1, NULL); if (err) gc_error (0, 0, "error running '%s %s': %s", pgmname, argv[4], gpg_strerror (err)); gnupg_release_process (pid); xfree (abs_homedir); } static void dirmngr_runtime_change (int killflag) { gpg_error_t err = 0; const char *pgmname; const char *argv[6]; pid_t pid = (pid_t)(-1); char *abs_homedir = NULL; pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); argv[0] = "--no-autostart"; argv[1] = "--dirmngr"; argv[2] = killflag? "KILLDIRMNGR" : "RELOADDIRMNGR"; if (gnupg_default_homedir_p ()) argv[3] = NULL; else { abs_homedir = make_absfilename_try (gnupg_homedir (), NULL); if (!abs_homedir) err = gpg_error_from_syserror (); argv[3] = "--homedir"; argv[4] = abs_homedir; argv[5] = NULL; } if (!err) err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid); if (!err) err = gnupg_wait_process (pgmname, pid, 1, NULL); if (err) gc_error (0, 0, "error running '%s %s': %s", pgmname, argv[2], gpg_strerror (err)); gnupg_release_process (pid); xfree (abs_homedir); } /* Launch the gpg-agent or the dirmngr if not already running. */ gpg_error_t gc_component_launch (int component) { gpg_error_t err; const char *pgmname; const char *argv[3]; int i; pid_t pid; if (component < 0) { err = gc_component_launch (GC_COMPONENT_GPG_AGENT); if (!err) err = gc_component_launch (GC_COMPONENT_DIRMNGR); return err; } if (!(component == GC_COMPONENT_GPG_AGENT || component == GC_COMPONENT_DIRMNGR)) { es_fputs (_("Component not suitable for launching"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (0); } pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); i = 0; if (component == GC_COMPONENT_DIRMNGR) argv[i++] = "--dirmngr"; argv[i++] = "NOP"; argv[i] = NULL; err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid); if (!err) err = gnupg_wait_process (pgmname, pid, 1, NULL); if (err) gc_error (0, 0, "error running '%s%s%s': %s", pgmname, component == GC_COMPONENT_DIRMNGR? " --dirmngr":"", " NOP", gpg_strerror (err)); gnupg_release_process (pid); return err; } /* Unconditionally restart COMPONENT. */ void gc_component_kill (int component) { int runtime[GC_BACKEND_NR]; gc_option_t *option; gc_backend_t backend; /* Set a flag for the backends to be reloaded. */ for (backend = 0; backend < GC_BACKEND_NR; backend++) runtime[backend] = 0; if (component < 0) { for (component = 0; component < GC_COMPONENT_NR; component++) { option = gc_component[component].options; for (; option && option->name; option++) runtime[option->backend] = 1; } } else { assert (component < GC_COMPONENT_NR); option = gc_component[component].options; for (; option && option->name; option++) runtime[option->backend] = 1; } /* Do the restart for the selected backends. */ for (backend = 0; backend < GC_BACKEND_NR; backend++) { if (runtime[backend] && gc_backend[backend].runtime_change) (*gc_backend[backend].runtime_change) (1); } } /* Unconditionally reload COMPONENT or all components if COMPONENT is -1. */ void gc_component_reload (int component) { int runtime[GC_BACKEND_NR]; gc_option_t *option; gc_backend_t backend; /* Set a flag for the backends to be reloaded. */ for (backend = 0; backend < GC_BACKEND_NR; backend++) runtime[backend] = 0; if (component < 0) { for (component = 0; component < GC_COMPONENT_NR; component++) { option = gc_component[component].options; for (; option && option->name; option++) runtime[option->backend] = 1; } } else { assert (component < GC_COMPONENT_NR); option = gc_component[component].options; for (; option && option->name; option++) runtime[option->backend] = 1; } /* Do the reload for all selected backends. */ for (backend = 0; backend < GC_BACKEND_NR; backend++) { if (runtime[backend] && gc_backend[backend].runtime_change) (*gc_backend[backend].runtime_change) (0); } } /* More or less Robust version of dgettext. It has the side effect of switching the codeset to utf-8 because this is what we want to output. In theory it is possible to keep the original code set and switch back for regular disgnostic output (redefine "_(" for that) but given the natur of this tool, being something invoked from other pograms, it does not make much sense. */ static const char * my_dgettext (const char *domain, const char *msgid) { #ifdef USE_SIMPLE_GETTEXT if (domain) { static int switched_codeset; char *text; if (!switched_codeset) { switched_codeset = 1; gettext_use_utf8 (1); } if (!strcmp (domain, "gnupg")) domain = PACKAGE_GT; /* FIXME: we have no dgettext, thus we can't switch. */ text = (char*)gettext (msgid); return text ? text : msgid; } else return msgid; #elif defined(ENABLE_NLS) if (domain) { static int switched_codeset; char *text; if (!switched_codeset) { switched_codeset = 1; bind_textdomain_codeset (PACKAGE_GT, "utf-8"); bindtextdomain (DIRMNGR_NAME, LOCALEDIR); bind_textdomain_codeset (DIRMNGR_NAME, "utf-8"); } /* Note: This is a hack to actually use the gnupg2 domain as long we are in a transition phase where gnupg 1.x and 1.9 may coexist. */ if (!strcmp (domain, "gnupg")) domain = PACKAGE_GT; text = dgettext (domain, msgid); return text ? text : msgid; } else return msgid; #else (void)domain; return msgid; #endif } /* Percent-Escape special characters. The string is valid until the next invocation of the function. */ char * gc_percent_escape (const char *src) { static char *esc_str; static int esc_str_len; int new_len = 3 * strlen (src) + 1; char *dst; if (esc_str_len < new_len) { char *new_esc_str = realloc (esc_str, new_len); if (!new_esc_str) gc_error (1, errno, "can not escape string"); esc_str = new_esc_str; esc_str_len = new_len; } dst = esc_str; while (*src) { if (*src == '%') { *(dst++) = '%'; *(dst++) = '2'; *(dst++) = '5'; } else if (*src == ':') { /* The colon is used as field separator. */ *(dst++) = '%'; *(dst++) = '3'; *(dst++) = 'a'; } else if (*src == ',') { /* The comma is used as list separator. */ *(dst++) = '%'; *(dst++) = '2'; *(dst++) = 'c'; } else if (*src == '\n') { /* The newline is problematic in a line-based format. */ *(dst++) = '%'; *(dst++) = '0'; *(dst++) = 'a'; } else *(dst++) = *(src); src++; } *dst = '\0'; return esc_str; } /* Percent-Deescape special characters. The string is valid until the next invocation of the function. */ static char * percent_deescape (const char *src) { static char *str; static int str_len; int new_len = 3 * strlen (src) + 1; char *dst; if (str_len < new_len) { char *new_str = realloc (str, new_len); if (!new_str) gc_error (1, errno, "can not deescape string"); str = new_str; str_len = new_len; } dst = str; while (*src) { if (*src == '%') { int val = hextobyte (src + 1); if (val < 0) gc_error (1, 0, "malformed end of string %s", src); *(dst++) = (char) val; src += 3; } else *(dst++) = *(src++); } *dst = '\0'; return str; } /* List all components that are available. */ void gc_component_list_components (estream_t out) { gc_component_t component; gc_option_t *option; gc_backend_t backend; int backend_seen[GC_BACKEND_NR]; const char *desc; const char *pgmname; for (component = 0; component < GC_COMPONENT_NR; component++) { option = gc_component[component].options; if (option) { for (backend = 0; backend < GC_BACKEND_NR; backend++) backend_seen[backend] = 0; pgmname = ""; for (; option && option->name; option++) { if ((option->flags & GC_OPT_FLAG_GROUP)) continue; backend = option->backend; if (backend_seen[backend]) continue; backend_seen[backend] = 1; assert (backend != GC_BACKEND_ANY); if (gc_backend[backend].program && !gc_backend[backend].module_name) continue; pgmname = gnupg_module_name (gc_backend[backend].module_name); break; } desc = gc_component[component].desc; desc = my_dgettext (gc_component[component].desc_domain, desc); es_fprintf (out, "%s:%s:", gc_component[component].name, gc_percent_escape (desc)); es_fprintf (out, "%s\n", gc_percent_escape (pgmname)); } } } static int all_digits_p (const char *p, size_t len) { if (!len) return 0; /* No. */ for (; len; len--, p++) if (!isascii (*p) || !isdigit (*p)) return 0; /* No. */ return 1; /* Yes. */ } /* Collect all error lines from stream FP. Only lines prefixed with TAG are considered. Returns a list of error line items (which may be empty). There is no error return. */ static error_line_t collect_error_output (estream_t fp, const char *tag) { char buffer[1024]; char *p, *p2, *p3; int c, cont_line; unsigned int pos; error_line_t eitem, errlines, *errlines_tail; size_t taglen = strlen (tag); errlines = NULL; errlines_tail = &errlines; pos = 0; cont_line = 0; while ((c=es_getc (fp)) != EOF) { buffer[pos++] = c; if (pos >= sizeof buffer - 5 || c == '\n') { buffer[pos - (c == '\n')] = 0; if (cont_line) ; /*Ignore continuations of previous line. */ else if (!strncmp (buffer, tag, taglen) && buffer[taglen] == ':') { /* "gpgsm: foo:4: bla" */ /* Yep, we are interested in this line. */ p = buffer + taglen + 1; while (*p == ' ' || *p == '\t') p++; trim_trailing_spaces (p); /* Get rid of extra CRs. */ if (!*p) ; /* Empty lines are ignored. */ else if ( (p2 = strchr (p, ':')) && (p3 = strchr (p2+1, ':')) && all_digits_p (p2+1, p3 - (p2+1))) { /* Line in standard compiler format. */ p3++; while (*p3 == ' ' || *p3 == '\t') p3++; eitem = xmalloc (sizeof *eitem + strlen (p)); eitem->next = NULL; strcpy (eitem->buffer, p); eitem->fname = eitem->buffer; eitem->buffer[p2-p] = 0; eitem->errtext = eitem->buffer + (p3 - p); /* (we already checked that there are only ascii digits followed by a colon) */ eitem->lineno = 0; for (p2++; isdigit (*p2); p2++) eitem->lineno = eitem->lineno*10 + (*p2 - '0'); *errlines_tail = eitem; errlines_tail = &eitem->next; } else { /* Other error output. */ eitem = xmalloc (sizeof *eitem + strlen (p)); eitem->next = NULL; strcpy (eitem->buffer, p); eitem->fname = NULL; eitem->errtext = eitem->buffer; eitem->lineno = 0; *errlines_tail = eitem; errlines_tail = &eitem->next; } } pos = 0; /* If this was not a complete line mark that we are in a continuation. */ cont_line = (c != '\n'); } } /* We ignore error lines not terminated by a LF. */ return errlines; } /* Check the options of a single component. Returns 0 if everything is OK. */ int gc_component_check_options (int component, estream_t out, const char *conf_file) { gpg_error_t err; unsigned int result; int backend_seen[GC_BACKEND_NR]; gc_backend_t backend; gc_option_t *option; const char *pgmname; const char *argv[4]; int i; pid_t pid; int exitcode; estream_t errfp; error_line_t errlines; for (backend = 0; backend < GC_BACKEND_NR; backend++) backend_seen[backend] = 0; option = gc_component[component].options; for (; option && option->name; option++) { if ((option->flags & GC_OPT_FLAG_GROUP)) continue; backend = option->backend; if (backend_seen[backend]) continue; backend_seen[backend] = 1; assert (backend != GC_BACKEND_ANY); if (!gc_backend[backend].program) continue; if (!gc_backend[backend].module_name) continue; break; } if (! option || ! option->name) return 0; pgmname = gnupg_module_name (gc_backend[backend].module_name); i = 0; if (conf_file) { argv[i++] = "--options"; argv[i++] = conf_file; } if (component == GC_COMPONENT_PINENTRY) argv[i++] = "--version"; else argv[i++] = "--gpgconf-test"; argv[i++] = NULL; result = 0; errlines = NULL; err = gnupg_spawn_process (pgmname, argv, NULL, NULL, 0, NULL, NULL, &errfp, &pid); if (err) result |= 1; /* Program could not be run. */ else { errlines = collect_error_output (errfp, gc_component[component].name); if (gnupg_wait_process (pgmname, pid, 1, &exitcode)) { if (exitcode == -1) result |= 1; /* Program could not be run or it terminated abnormally. */ result |= 2; /* Program returned an error. */ } gnupg_release_process (pid); es_fclose (errfp); } /* If the program could not be run, we can't tell whether the config file is good. */ if (result & 1) result |= 2; if (out) { const char *desc; error_line_t errptr; desc = gc_component[component].desc; desc = my_dgettext (gc_component[component].desc_domain, desc); es_fprintf (out, "%s:%s:", gc_component[component].name, gc_percent_escape (desc)); es_fputs (gc_percent_escape (pgmname), out); es_fprintf (out, ":%d:%d:", !(result & 1), !(result & 2)); for (errptr = errlines; errptr; errptr = errptr->next) { if (errptr != errlines) es_fputs ("\n:::::", out); /* Continuation line. */ if (errptr->fname) es_fputs (gc_percent_escape (errptr->fname), out); es_putc (':', out); if (errptr->fname) es_fprintf (out, "%u", errptr->lineno); es_putc (':', out); es_fputs (gc_percent_escape (errptr->errtext), out); es_putc (':', out); } es_putc ('\n', out); } while (errlines) { error_line_t tmp = errlines->next; xfree (errlines); errlines = tmp; } return result; } /* Check all components that are available. */ void gc_check_programs (estream_t out) { gc_component_t component; for (component = 0; component < GC_COMPONENT_NR; component++) gc_component_check_options (component, out, NULL); } /* Find the component with the name NAME. Returns -1 if not found. */ int gc_component_find (const char *name) { gc_component_t idx; for (idx = 0; idx < GC_COMPONENT_NR; idx++) { if (gc_component[idx].options && !strcmp (name, gc_component[idx].name)) return idx; } return -1; } /* List the option OPTION. */ static void list_one_option (const gc_option_t *option, estream_t out) { const char *desc = NULL; char *arg_name = NULL; if (option->desc) { desc = my_dgettext (option->desc_domain, option->desc); if (*desc == '|') { const char *arg_tail = strchr (&desc[1], '|'); if (arg_tail) { int arg_len = arg_tail - &desc[1]; arg_name = xmalloc (arg_len + 1); memcpy (arg_name, &desc[1], arg_len); arg_name[arg_len] = '\0'; desc = arg_tail + 1; } } } /* YOU MUST NOT REORDER THE FIELDS IN THIS OUTPUT, AS THEIR ORDER IS PART OF THE EXTERNAL INTERFACE. YOU MUST NOT REMOVE ANY FIELDS. */ /* The name field. */ es_fprintf (out, "%s", option->name); /* The flags field. */ es_fprintf (out, ":%lu", option->flags); if (opt.verbose) { es_putc (' ', out); if (!option->flags) es_fprintf (out, "none"); else { unsigned long flags = option->flags; unsigned long flag = 0; unsigned long first = 1; while (flags) { if (flags & 1) { if (first) first = 0; else es_putc (',', out); es_fprintf (out, "%s", gc_flag[flag].name); } flags >>= 1; flag++; } } } /* The level field. */ es_fprintf (out, ":%u", option->level); if (opt.verbose) es_fprintf (out, " %s", gc_level[option->level].name); /* The description field. */ es_fprintf (out, ":%s", desc ? gc_percent_escape (desc) : ""); /* The type field. */ es_fprintf (out, ":%u", option->arg_type); if (opt.verbose) es_fprintf (out, " %s", gc_arg_type[option->arg_type].name); /* The alternate type field. */ es_fprintf (out, ":%u", gc_arg_type[option->arg_type].fallback); if (opt.verbose) es_fprintf (out, " %s", gc_arg_type[gc_arg_type[option->arg_type].fallback].name); /* The argument name field. */ es_fprintf (out, ":%s", arg_name ? gc_percent_escape (arg_name) : ""); xfree (arg_name); /* The default value field. */ es_fprintf (out, ":%s", option->default_value ? option->default_value : ""); /* The default argument field. */ es_fprintf (out, ":%s", option->default_arg ? option->default_arg : ""); /* The value field. */ if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE && (option->flags & GC_OPT_FLAG_LIST) && option->value) /* The special format "1,1,1,1,...,1" is converted to a number here. */ es_fprintf (out, ":%u", (unsigned int)((strlen (option->value) + 1) / 2)); else es_fprintf (out, ":%s", option->value ? option->value : ""); /* ADD NEW FIELDS HERE. */ es_putc ('\n', out); } /* List all options of the component COMPONENT. */ void gc_component_list_options (int component, estream_t out) { const gc_option_t *option = gc_component[component].options; while (option && option->name) { /* Do not output unknown or internal options. */ if (!(option->flags & GC_OPT_FLAG_GROUP) && (!option->active || option->level == GC_LEVEL_INTERNAL)) { option++; continue; } if (option->flags & GC_OPT_FLAG_GROUP) { const gc_option_t *group_option = option + 1; gc_expert_level_t level = GC_LEVEL_NR; /* The manual states that the group level is always the minimum of the levels of all contained options. Due to different active options, and because it is hard to maintain manually, we calculate it here. The value in the global static table is ignored. */ while (group_option->name) { if (group_option->flags & GC_OPT_FLAG_GROUP) break; if (group_option->level < level) level = group_option->level; group_option++; } /* Check if group is empty. */ if (level != GC_LEVEL_NR) { gc_option_t opt_copy; /* Fix up the group level. */ memcpy (&opt_copy, option, sizeof (opt_copy)); opt_copy.level = level; list_one_option (&opt_copy, out); } } else list_one_option (option, out); option++; } } /* Find the option NAME in component COMPONENT, for the backend BACKEND. If BACKEND is GC_BACKEND_ANY, any backend will match. */ static gc_option_t * find_option (gc_component_t component, const char *name, gc_backend_t backend) { gc_option_t *option = gc_component[component].options; while (option->name) { if (!(option->flags & GC_OPT_FLAG_GROUP) && !strcmp (option->name, name) && (backend == GC_BACKEND_ANY || option->backend == backend)) break; option++; } return option->name ? option : NULL; } /* Determine the configuration filename for the component COMPONENT and backend BACKEND. */ static char * get_config_filename (gc_component_t component, gc_backend_t backend) { char *filename = NULL; gc_option_t *option = find_option (component, gc_backend[backend].option_config_filename, GC_BACKEND_ANY); assert (option); assert (option->arg_type == GC_ARG_TYPE_FILENAME); assert (!(option->flags & GC_OPT_FLAG_LIST)); if (!option->active || !option->default_value) gc_error (1, 0, "Option %s, needed by backend %s, was not initialized", gc_backend[backend].option_config_filename, gc_backend[backend].name); if (option->value && *option->value) filename = percent_deescape (&option->value[1]); else if (option->default_value && *option->default_value) filename = percent_deescape (&option->default_value[1]); else filename = ""; #if HAVE_W32CE_SYSTEM if (!(filename[0] == '/' || filename[0] == '\\')) #elif defined(HAVE_DOSISH_SYSTEM) if (!(filename[0] && filename[1] == ':' && (filename[2] == '/' || filename[2] == '\\')) /* x:\ or x:/ */ && !((filename[0] == '\\' && filename[1] == '\\') || (filename[0] == '/' && filename[1] == '/'))) /* \\server */ #else if (filename[0] != '/') #endif gc_error (1, 0, "Option %s, needed by backend %s, is not absolute", gc_backend[backend].option_config_filename, gc_backend[backend].name); return filename; } /* Retrieve the options for the component COMPONENT from backend * BACKEND, which we already know is a program-type backend. With * ONLY_INSTALLED set components which are not installed are silently * ignored. */ static void retrieve_options_from_program (gc_component_t component, gc_backend_t backend, int only_installed) { gpg_error_t err; const char *pgmname; const char *argv[2]; estream_t outfp; int exitcode; pid_t pid; char *line = NULL; size_t line_len = 0; ssize_t length; estream_t config; char *config_filename; pgmname = (gc_backend[backend].module_name ? gnupg_module_name (gc_backend[backend].module_name) : gc_backend[backend].program ); argv[0] = "--gpgconf-list"; argv[1] = NULL; if (only_installed && access (pgmname, X_OK)) { return; /* The component is not installed. */ } err = gnupg_spawn_process (pgmname, argv, NULL, NULL, 0, NULL, &outfp, NULL, &pid); if (err) { gc_error (1, 0, "could not gather active options from '%s': %s", pgmname, gpg_strerror (err)); } while ((length = es_read_line (outfp, &line, &line_len, NULL)) > 0) { gc_option_t *option; char *linep; unsigned long flags = 0; char *default_value = NULL; /* Strip newline and carriage return, if present. */ while (length > 0 && (line[length - 1] == '\n' || line[length - 1] == '\r')) line[--length] = '\0'; linep = strchr (line, ':'); if (linep) *(linep++) = '\0'; /* Extract additional flags. Default to none. */ if (linep) { char *end; char *tail; end = strchr (linep, ':'); if (end) *(end++) = '\0'; gpg_err_set_errno (0); flags = strtoul (linep, &tail, 0); if (errno) gc_error (1, errno, "malformed flags in option %s from %s", line, pgmname); if (!(*tail == '\0' || *tail == ':' || *tail == ' ')) gc_error (1, 0, "garbage after flags in option %s from %s", line, pgmname); linep = end; } /* Extract default value, if present. Default to empty if not. */ if (linep) { char *end; end = strchr (linep, ':'); if (end) *(end++) = '\0'; if (flags & GC_OPT_FLAG_DEFAULT) default_value = linep; linep = end; } /* Look up the option in the component and install the configuration data. */ option = find_option (component, line, backend); if (option) { if (option->active) gc_error (1, errno, "option %s returned twice from %s", line, pgmname); option->active = 1; option->flags |= flags; if (default_value && *default_value) option->default_value = xstrdup (default_value); } } if (length < 0 || es_ferror (outfp)) gc_error (1, errno, "error reading from %s", pgmname); if (es_fclose (outfp)) gc_error (1, errno, "error closing %s", pgmname); err = gnupg_wait_process (pgmname, pid, 1, &exitcode); if (err) gc_error (1, 0, "running %s failed (exitcode=%d): %s", pgmname, exitcode, gpg_strerror (err)); gnupg_release_process (pid); /* At this point, we can parse the configuration file. */ config_filename = get_config_filename (component, backend); config = es_fopen (config_filename, "r"); if (!config) { if (errno != ENOENT) gc_error (0, errno, "warning: can not open config file %s", config_filename); } else { while ((length = es_read_line (config, &line, &line_len, NULL)) > 0) { char *name; char *value; gc_option_t *option; name = line; while (*name == ' ' || *name == '\t') name++; if (!*name || *name == '#' || *name == '\r' || *name == '\n') continue; value = name; while (*value && *value != ' ' && *value != '\t' && *value != '#' && *value != '\r' && *value != '\n') value++; if (*value == ' ' || *value == '\t') { char *end; *(value++) = '\0'; while (*value == ' ' || *value == '\t') value++; end = value; while (*end && *end != '#' && *end != '\r' && *end != '\n') end++; while (end > value && (end[-1] == ' ' || end[-1] == '\t')) end--; *end = '\0'; } else *value = '\0'; /* Look up the option in the component and install the configuration data. */ option = find_option (component, line, backend); if (option) { char *opt_value; if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE) { if (*value) gc_error (0, 0, "warning: ignoring argument %s for option %s", value, name); opt_value = xstrdup ("1"); } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING) opt_value = xasprintf ("\"%s", gc_percent_escape (value)); else { /* FIXME: Verify that the number is sane. */ opt_value = xstrdup (value); } /* Now enter the option into the table. */ if (!(option->flags & GC_OPT_FLAG_LIST)) { if (option->value) xfree (option->value); option->value = opt_value; } else { if (!option->value) option->value = opt_value; else { char *old = option->value; option->value = xasprintf ("%s,%s", old, opt_value); xfree (old); xfree (opt_value); } } } } if (length < 0 || es_ferror (config)) gc_error (1, errno, "error reading from %s", config_filename); if (es_fclose (config)) gc_error (1, errno, "error closing %s", config_filename); } xfree (line); } /* Retrieve the options for the component COMPONENT from backend BACKEND, which we already know is of type file list. */ static void retrieve_options_from_file (gc_component_t component, gc_backend_t backend) { gc_option_t *list_option; gc_option_t *config_option; char *list_filename; gpgrt_stream_t list_file; char *line = NULL; size_t line_len = 0; ssize_t length; char *list = NULL; list_option = find_option (component, gc_backend[backend].option_name, GC_BACKEND_ANY); assert (list_option); assert (!list_option->active); list_filename = get_config_filename (component, backend); list_file = gpgrt_fopen (list_filename, "r"); if (!list_file) gc_error (0, errno, "warning: can not open list file %s", list_filename); else { while ((length = gpgrt_read_line (list_file, &line, &line_len, NULL)) > 0) { char *start; char *end; char *new_list; start = line; while (*start == ' ' || *start == '\t') start++; if (!*start || *start == '#' || *start == '\r' || *start == '\n') continue; end = start; while (*end && *end != '#' && *end != '\r' && *end != '\n') end++; /* Walk back to skip trailing white spaces. Looks evil, but works because of the conditions on START and END imposed at this point (END is at least START + 1, and START is not a whitespace character). */ while (*(end - 1) == ' ' || *(end - 1) == '\t') end--; *end = '\0'; /* FIXME: Oh, no! This is so lame! Should use realloc and really append. */ if (list) { new_list = xasprintf ("%s,\"%s", list, gc_percent_escape (start)); xfree (list); list = new_list; } else list = xasprintf ("\"%s", gc_percent_escape (start)); } if (length < 0 || gpgrt_ferror (list_file)) gc_error (1, errno, "can not read list file %s", list_filename); } list_option->active = 1; list_option->value = list; /* Fix up the read-only flag. */ config_option = find_option (component, gc_backend[backend].option_config_filename, GC_BACKEND_ANY); if (config_option->flags & GC_OPT_FLAG_NO_CHANGE) list_option->flags |= GC_OPT_FLAG_NO_CHANGE; if (list_file && gpgrt_fclose (list_file)) gc_error (1, errno, "error closing %s", list_filename); xfree (line); } /* Retrieve the currently active options and their defaults from all involved backends for this component. Using -1 for component will retrieve all options from all installed components. */ void gc_component_retrieve_options (int component) { int process_all = 0; int backend_seen[GC_BACKEND_NR]; gc_backend_t backend; gc_option_t *option; for (backend = 0; backend < GC_BACKEND_NR; backend++) backend_seen[backend] = 0; if (component == -1) { process_all = 1; component = 0; assert (component < GC_COMPONENT_NR); } do { if (component == GC_COMPONENT_PINENTRY) continue; /* Skip this dummy component. */ option = gc_component[component].options; while (option && option->name) { if (!(option->flags & GC_OPT_FLAG_GROUP)) { backend = option->backend; if (backend_seen[backend]) { option++; continue; } backend_seen[backend] = 1; assert (backend != GC_BACKEND_ANY); if (gc_backend[backend].program) retrieve_options_from_program (component, backend, process_all); else retrieve_options_from_file (component, backend); } option++; } } while (process_all && ++component < GC_COMPONENT_NR); } /* Perform a simple validity check based on the type. Return in * NEW_VALUE_NR the value of the number in NEW_VALUE if OPTION is of * type GC_ARG_TYPE_NONE. If VERBATIM is set the profile parsing mode * is used. */ static void option_check_validity (gc_option_t *option, unsigned long flags, char *new_value, unsigned long *new_value_nr, int verbatim) { char *arg; if (!option->active) gc_error (1, 0, "option %s not supported by backend %s", option->name, gc_backend[option->backend].name); if (option->new_flags || option->new_value) gc_error (1, 0, "option %s already changed", option->name); if (flags & GC_OPT_FLAG_DEFAULT) { if (*new_value) gc_error (1, 0, "argument %s provided for deleted option %s", new_value, option->name); return; } /* GC_ARG_TYPE_NONE options have special list treatment. */ if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE) { char *tail; gpg_err_set_errno (0); *new_value_nr = strtoul (new_value, &tail, 0); if (errno) gc_error (1, errno, "invalid argument for option %s", option->name); if (*tail) gc_error (1, 0, "garbage after argument for option %s", option->name); if (!(option->flags & GC_OPT_FLAG_LIST)) { if (*new_value_nr != 1) gc_error (1, 0, "argument for non-list option %s of type 0 " "(none) must be 1", option->name); } else { if (*new_value_nr == 0) gc_error (1, 0, "argument for option %s of type 0 (none) " "must be positive", option->name); } return; } arg = new_value; do { if (*arg == '\0' || (*arg == ',' && !verbatim)) { if (!(option->flags & GC_OPT_FLAG_ARG_OPT)) gc_error (1, 0, "argument required for option %s", option->name); if (*arg == ',' && !verbatim && !(option->flags & GC_OPT_FLAG_LIST)) gc_error (1, 0, "list found for non-list option %s", option->name); } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING) { if (*arg != '"' && !verbatim) gc_error (1, 0, "string argument for option %s must begin " "with a quote (\") character", option->name); /* FIXME: We do not allow empty string arguments for now, as we do not quote arguments in configuration files, and thus no argument is indistinguishable from the empty string. */ if (arg[1] == '\0' || (arg[1] == ',' && !verbatim)) gc_error (1, 0, "empty string argument for option %s is " "currently not allowed. Please report this!", option->name); } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_INT32) { long res; gpg_err_set_errno (0); res = strtol (arg, &arg, 0); (void) res; if (errno) gc_error (1, errno, "invalid argument for option %s", option->name); if (*arg != '\0' && (*arg != ',' || verbatim)) gc_error (1, 0, "garbage after argument for option %s", option->name); } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_UINT32) { unsigned long res; gpg_err_set_errno (0); res = strtoul (arg, &arg, 0); (void) res; if (errno) gc_error (1, errno, "invalid argument for option %s", option->name); if (*arg != '\0' && (*arg != ',' || verbatim)) gc_error (1, 0, "garbage after argument for option %s", option->name); } arg = verbatim? strchr (arg, ',') : NULL; if (arg) arg++; } while (arg && *arg); } #ifdef HAVE_W32_SYSTEM int copy_file (const char *src_name, const char *dst_name) { #define BUF_LEN 4096 char buffer[BUF_LEN]; int len; gpgrt_stream_t src; gpgrt_stream_t dst; src = gpgrt_fopen (src_name, "r"); if (src == NULL) return -1; dst = gpgrt_fopen (dst_name, "w"); if (dst == NULL) { int saved_err = errno; gpgrt_fclose (src); gpg_err_set_errno (saved_err); return -1; } do { int written; len = gpgrt_fread (buffer, 1, BUF_LEN, src); if (len == 0) break; written = gpgrt_fwrite (buffer, 1, len, dst); if (written != len) break; } while (! gpgrt_feof (src) && ! gpgrt_ferror (src) && ! gpgrt_ferror (dst)); if (gpgrt_ferror (src) || gpgrt_ferror (dst) || ! gpgrt_feof (src)) { int saved_errno = errno; gpgrt_fclose (src); gpgrt_fclose (dst); unlink (dst_name); gpg_err_set_errno (saved_errno); return -1; } if (gpgrt_fclose (dst)) gc_error (1, errno, "error closing %s", dst_name); if (gpgrt_fclose (src)) gc_error (1, errno, "error closing %s", src_name); return 0; } #endif /* HAVE_W32_SYSTEM */ /* Create and verify the new configuration file for the specified * backend and component. Returns 0 on success and -1 on error. This * function may store pointers to malloced strings in SRC_FILENAMEP, * DEST_FILENAMEP, and ORIG_FILENAMEP. Those must be freed by the * caller. The strings refer to three versions of the configuration * file: * * SRC_FILENAME: The updated configuration is written to this file. * DEST_FILENAME: Name of the configuration file read by the * component. * ORIG_FILENAME: A backup of the previous configuration file. * * To apply the configuration change, rename SRC_FILENAME to * DEST_FILENAME. To revert to the previous configuration, rename * ORIG_FILENAME to DEST_FILENAME. */ static int change_options_file (gc_component_t component, gc_backend_t backend, char **src_filenamep, char **dest_filenamep, char **orig_filenamep) { static const char marker[] = "###+++--- " GPGCONF_DISP_NAME " ---+++###"; /* True if we are within the marker in the config file. */ int in_marker = 0; gc_option_t *option; char *line = NULL; size_t line_len; ssize_t length; int res; int fd; gpgrt_stream_t src_file = NULL; gpgrt_stream_t dest_file = NULL; char *src_filename; char *dest_filename; char *orig_filename; char *arg; char *cur_arg = NULL; option = find_option (component, gc_backend[backend].option_name, GC_BACKEND_ANY); assert (option); assert (option->active); assert (gc_arg_type[option->arg_type].fallback != GC_ARG_TYPE_NONE); /* FIXME. Throughout the function, do better error reporting. */ /* Note that get_config_filename() calls percent_deescape(), so we call this before processing the arguments. */ dest_filename = xstrdup (get_config_filename (component, backend)); src_filename = xasprintf ("%s.%s.%i.new", dest_filename, GPGCONF_NAME, (int)getpid ()); orig_filename = xasprintf ("%s.%s.%i.bak", dest_filename, GPGCONF_NAME, (int)getpid ()); arg = option->new_value; if (arg && arg[0] == '\0') arg = NULL; else if (arg) { char *end; arg++; end = strchr (arg, ','); if (end) *end = '\0'; cur_arg = percent_deescape (arg); if (end) { *end = ','; arg = end + 1; } else arg = NULL; } #ifdef HAVE_W32_SYSTEM res = copy_file (dest_filename, orig_filename); #else res = link (dest_filename, orig_filename); #endif if (res < 0 && errno != ENOENT) { xfree (dest_filename); xfree (src_filename); xfree (orig_filename); return -1; } if (res < 0) { xfree (orig_filename); orig_filename = NULL; } /* We now initialize the return strings, so the caller can do the cleanup for us. */ *src_filenamep = src_filename; *dest_filenamep = dest_filename; *orig_filenamep = orig_filename; /* Use open() so that we can use O_EXCL. */ fd = open (src_filename, O_CREAT | O_EXCL | O_WRONLY, 0644); if (fd < 0) return -1; src_file = gpgrt_fdopen (fd, "w"); res = errno; if (!src_file) { gpg_err_set_errno (res); return -1; } /* Only if ORIG_FILENAME is not NULL did the configuration file exist already. In this case, we will copy its content into the new configuration file, changing it to our liking in the process. */ if (orig_filename) { dest_file = gpgrt_fopen (dest_filename, "r"); if (!dest_file) goto change_file_one_err; while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0) { int disable = 0; char *start; if (!strncmp (marker, line, sizeof (marker) - 1)) { if (!in_marker) in_marker = 1; else break; } start = line; while (*start == ' ' || *start == '\t') start++; if (*start && *start != '\r' && *start != '\n' && *start != '#') { char *end; char *endp; char saved_end; endp = start; end = endp; /* Search for the end of the line. */ while (*endp && *endp != '#' && *endp != '\r' && *endp != '\n') { endp++; if (*endp && *endp != ' ' && *endp != '\t' && *endp != '\r' && *endp != '\n' && *endp != '#') end = endp + 1; } saved_end = *end; *end = '\0'; if ((option->new_flags & GC_OPT_FLAG_DEFAULT) || !cur_arg || strcmp (start, cur_arg)) disable = 1; else { /* Find next argument. */ if (arg) { char *arg_end; arg++; arg_end = strchr (arg, ','); if (arg_end) *arg_end = '\0'; cur_arg = percent_deescape (arg); if (arg_end) { *arg_end = ','; arg = arg_end + 1; } else arg = NULL; } else cur_arg = NULL; } *end = saved_end; } if (disable) { if (!in_marker) { gpgrt_fprintf (src_file, "# %s disabled this option here at %s\n", GPGCONF_DISP_NAME, asctimestamp (gnupg_get_time ())); if (gpgrt_ferror (src_file)) goto change_file_one_err; gpgrt_fprintf (src_file, "# %s", line); if (gpgrt_ferror (src_file)) goto change_file_one_err; } } else { gpgrt_fprintf (src_file, "%s", line); if (gpgrt_ferror (src_file)) goto change_file_one_err; } } if (length < 0 || gpgrt_ferror (dest_file)) goto change_file_one_err; } if (!in_marker) { /* There was no marker. This is the first time we edit the file. We add our own marker at the end of the file and proceed. Note that we first write a newline, this guards us against files which lack the newline at the end of the last line, while it doesn't hurt us in all other cases. */ gpgrt_fprintf (src_file, "\n%s\n", marker); if (gpgrt_ferror (src_file)) goto change_file_one_err; } /* At this point, we have copied everything up to the end marker into the new file, except for the arguments we are going to add. Now, dump the new arguments and write the end marker, possibly followed by the rest of the original file. */ while (cur_arg) { gpgrt_fprintf (src_file, "%s\n", cur_arg); /* Find next argument. */ if (arg) { char *end; arg++; end = strchr (arg, ','); if (end) *end = '\0'; cur_arg = percent_deescape (arg); if (end) { *end = ','; arg = end + 1; } else arg = NULL; } else cur_arg = NULL; } gpgrt_fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ())); if (gpgrt_ferror (src_file)) goto change_file_one_err; if (!in_marker) { gpgrt_fprintf (src_file, "# %s edited this configuration file.\n", GPGCONF_DISP_NAME); if (gpgrt_ferror (src_file)) goto change_file_one_err; gpgrt_fprintf (src_file, "# It will disable options before this marked " "block, but it will\n"); if (gpgrt_ferror (src_file)) goto change_file_one_err; gpgrt_fprintf (src_file, "# never change anything below these lines.\n"); if (gpgrt_ferror (src_file)) goto change_file_one_err; } if (dest_file) { while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0) { gpgrt_fprintf (src_file, "%s", line); if (gpgrt_ferror (src_file)) goto change_file_one_err; } if (length < 0 || gpgrt_ferror (dest_file)) goto change_file_one_err; } xfree (line); line = NULL; res = gpgrt_fclose (src_file); if (res) { res = errno; close (fd); if (dest_file) gpgrt_fclose (dest_file); gpg_err_set_errno (res); return -1; } close (fd); if (dest_file) { res = gpgrt_fclose (dest_file); if (res) return -1; } return 0; change_file_one_err: xfree (line); res = errno; if (src_file) { gpgrt_fclose (src_file); close (fd); } if (dest_file) gpgrt_fclose (dest_file); gpg_err_set_errno (res); return -1; } /* Create and verify the new configuration file for the specified * backend and component. Returns 0 on success and -1 on error. If * VERBATIM is set the profile mode is used. This function may store * pointers to malloced strings in SRC_FILENAMEP, DEST_FILENAMEP, and * ORIG_FILENAMEP. Those must be freed by the caller. The strings * refer to three versions of the configuration file: * * SRC_FILENAME: The updated configuration is written to this file. * DEST_FILENAME: Name of the configuration file read by the * component. * ORIG_FILENAME: A backup of the previous configuration file. * * To apply the configuration change, rename SRC_FILENAME to * DEST_FILENAME. To revert to the previous configuration, rename * ORIG_FILENAME to DEST_FILENAME. */ static int change_options_program (gc_component_t component, gc_backend_t backend, char **src_filenamep, char **dest_filenamep, char **orig_filenamep, int verbatim) { static const char marker[] = "###+++--- " GPGCONF_DISP_NAME " ---+++###"; /* True if we are within the marker in the config file. */ int in_marker = 0; gc_option_t *option; char *line = NULL; size_t line_len; ssize_t length; int res; int fd; gpgrt_stream_t src_file = NULL; gpgrt_stream_t dest_file = NULL; char *src_filename; char *dest_filename; char *orig_filename; /* Special hack for gpg, see below. */ int utf8strings_seen = 0; /* FIXME. Throughout the function, do better error reporting. */ dest_filename = xstrdup (get_config_filename (component, backend)); src_filename = xasprintf ("%s.%s.%i.new", dest_filename, GPGCONF_NAME, (int)getpid ()); orig_filename = xasprintf ("%s.%s.%i.bak", dest_filename, GPGCONF_NAME, (int)getpid ()); #ifdef HAVE_W32_SYSTEM res = copy_file (dest_filename, orig_filename); #else res = link (dest_filename, orig_filename); #endif if (res < 0 && errno != ENOENT) { xfree (dest_filename); xfree (src_filename); xfree (orig_filename); return -1; } if (res < 0) { xfree (orig_filename); orig_filename = NULL; } /* We now initialize the return strings, so the caller can do the cleanup for us. */ *src_filenamep = src_filename; *dest_filenamep = dest_filename; *orig_filenamep = orig_filename; /* Use open() so that we can use O_EXCL. */ fd = open (src_filename, O_CREAT | O_EXCL | O_WRONLY, 0644); if (fd < 0) return -1; src_file = gpgrt_fdopen (fd, "w"); res = errno; if (!src_file) { gpg_err_set_errno (res); return -1; } /* Only if ORIG_FILENAME is not NULL did the configuration file exist already. In this case, we will copy its content into the new configuration file, changing it to our liking in the process. */ if (orig_filename) { dest_file = gpgrt_fopen (dest_filename, "r"); if (!dest_file) goto change_one_err; while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0) { int disable = 0; char *start; if (!strncmp (marker, line, sizeof (marker) - 1)) { if (!in_marker) in_marker = 1; else break; } else if (backend == GC_BACKEND_GPG && in_marker && ! strcmp ("utf8-strings\n", line)) { /* Strip duplicated entries. */ if (utf8strings_seen) disable = 1; else utf8strings_seen = 1; } start = line; while (*start == ' ' || *start == '\t') start++; if (*start && *start != '\r' && *start != '\n' && *start != '#') { char *end; char saved_end; end = start; while (*end && *end != ' ' && *end != '\t' && *end != '\r' && *end != '\n' && *end != '#') end++; saved_end = *end; *end = '\0'; option = find_option (component, start, backend); *end = saved_end; if (option && ((option->new_flags & GC_OPT_FLAG_DEFAULT) || option->new_value)) disable = 1; } if (disable) { if (!in_marker) { gpgrt_fprintf (src_file, "# %s disabled this option here at %s\n", GPGCONF_DISP_NAME, asctimestamp (gnupg_get_time ())); if (gpgrt_ferror (src_file)) goto change_one_err; gpgrt_fprintf (src_file, "# %s", line); if (gpgrt_ferror (src_file)) goto change_one_err; } } else { gpgrt_fprintf (src_file, "%s", line); if (gpgrt_ferror (src_file)) goto change_one_err; } } if (length < 0 || gpgrt_ferror (dest_file)) goto change_one_err; } if (!in_marker) { /* There was no marker. This is the first time we edit the file. We add our own marker at the end of the file and proceed. Note that we first write a newline, this guards us against files which lack the newline at the end of the last line, while it doesn't hurt us in all other cases. */ gpgrt_fprintf (src_file, "\n%s\n", marker); if (gpgrt_ferror (src_file)) goto change_one_err; } /* At this point, we have copied everything up to the end marker into the new file, except for the options we are going to change. Now, dump the changed options (except for those we are going to revert to their default), and write the end marker, possibly followed by the rest of the original file. */ /* We have to turn on UTF8 strings for GnuPG. */ if (backend == GC_BACKEND_GPG && ! utf8strings_seen) gpgrt_fprintf (src_file, "utf8-strings\n"); option = gc_component[component].options; while (option->name) { if (!(option->flags & GC_OPT_FLAG_GROUP) && option->backend == backend && option->new_value) { char *arg = option->new_value; do { if (*arg == '\0' || *arg == ',') { gpgrt_fprintf (src_file, "%s\n", option->name); if (gpgrt_ferror (src_file)) goto change_one_err; } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE) { assert (*arg == '1'); gpgrt_fprintf (src_file, "%s\n", option->name); if (gpgrt_ferror (src_file)) goto change_one_err; arg++; } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING) { char *end; if (!verbatim) { log_assert (*arg == '"'); arg++; end = strchr (arg, ','); if (end) *end = '\0'; } else end = NULL; gpgrt_fprintf (src_file, "%s %s\n", option->name, verbatim? arg : percent_deescape (arg)); if (gpgrt_ferror (src_file)) goto change_one_err; if (end) *end = ','; arg = end; } else { char *end; end = strchr (arg, ','); if (end) *end = '\0'; gpgrt_fprintf (src_file, "%s %s\n", option->name, arg); if (gpgrt_ferror (src_file)) goto change_one_err; if (end) *end = ','; arg = end; } assert (arg == NULL || *arg == '\0' || *arg == ','); if (arg && *arg == ',') arg++; } while (arg && *arg); } option++; } gpgrt_fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ())); if (gpgrt_ferror (src_file)) goto change_one_err; if (!in_marker) { gpgrt_fprintf (src_file, "# %s edited this configuration file.\n", GPGCONF_DISP_NAME); if (gpgrt_ferror (src_file)) goto change_one_err; gpgrt_fprintf (src_file, "# It will disable options before this marked " "block, but it will\n"); if (gpgrt_ferror (src_file)) goto change_one_err; gpgrt_fprintf (src_file, "# never change anything below these lines.\n"); if (gpgrt_ferror (src_file)) goto change_one_err; } if (dest_file) { while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0) { gpgrt_fprintf (src_file, "%s", line); if (gpgrt_ferror (src_file)) goto change_one_err; } if (length < 0 || gpgrt_ferror (dest_file)) goto change_one_err; } xfree (line); line = NULL; res = gpgrt_fclose (src_file); if (res) { res = errno; close (fd); if (dest_file) gpgrt_fclose (dest_file); gpg_err_set_errno (res); return -1; } close (fd); if (dest_file) { res = gpgrt_fclose (dest_file); if (res) return -1; } return 0; change_one_err: xfree (line); res = errno; if (src_file) { gpgrt_fclose (src_file); close (fd); } if (dest_file) gpgrt_fclose (dest_file); gpg_err_set_errno (res); return -1; } /* Common code for gc_component_change_options and * gc_process_gpgconf_conf. If VERBATIM is set the profile parsing * mode is used. */ static void change_one_value (gc_option_t *option, int *runtime, unsigned long flags, char *new_value, int verbatim) { unsigned long new_value_nr = 0; option_check_validity (option, flags, new_value, &new_value_nr, verbatim); if (option->flags & GC_OPT_FLAG_RUNTIME) runtime[option->backend] = 1; option->new_flags = flags; if (!(flags & GC_OPT_FLAG_DEFAULT)) { if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE && (option->flags & GC_OPT_FLAG_LIST)) { char *str; /* We convert the number to a list of 1's for convenient list handling. */ assert (new_value_nr > 0); option->new_value = xmalloc ((2 * (new_value_nr - 1) + 1) + 1); str = option->new_value; *(str++) = '1'; while (--new_value_nr > 0) { *(str++) = ','; *(str++) = '1'; } *(str++) = '\0'; } else option->new_value = xstrdup (new_value); } } /* Read the modifications from IN and apply them. If IN is NULL the modifications are expected to already have been set to the global table. If VERBATIM is set the profile mode is used. */ void gc_component_change_options (int component, estream_t in, estream_t out, int verbatim) { int err = 0; int block = 0; int runtime[GC_BACKEND_NR]; char *src_filename[GC_BACKEND_NR]; char *dest_filename[GC_BACKEND_NR]; char *orig_filename[GC_BACKEND_NR]; gc_backend_t backend; gc_option_t *option; char *line = NULL; size_t line_len = 0; ssize_t length; if (component == GC_COMPONENT_PINENTRY) return; /* Dummy component for now. */ for (backend = 0; backend < GC_BACKEND_NR; backend++) { runtime[backend] = 0; src_filename[backend] = NULL; dest_filename[backend] = NULL; orig_filename[backend] = NULL; } if (in) { /* Read options from the file IN. */ while ((length = es_read_line (in, &line, &line_len, NULL)) > 0) { char *linep; unsigned long flags = 0; char *new_value = ""; /* Strip newline and carriage return, if present. */ while (length > 0 && (line[length - 1] == '\n' || line[length - 1] == '\r')) line[--length] = '\0'; linep = strchr (line, ':'); if (linep) *(linep++) = '\0'; /* Extract additional flags. Default to none. */ if (linep) { char *end; char *tail; end = strchr (linep, ':'); if (end) *(end++) = '\0'; gpg_err_set_errno (0); flags = strtoul (linep, &tail, 0); if (errno) gc_error (1, errno, "malformed flags in option %s", line); if (!(*tail == '\0' || *tail == ':' || *tail == ' ')) gc_error (1, 0, "garbage after flags in option %s", line); linep = end; } /* Don't allow setting of the no change flag. */ flags &= ~GC_OPT_FLAG_NO_CHANGE; /* Extract default value, if present. Default to empty if not. */ if (linep) { char *end; end = strchr (linep, ':'); if (end) *(end++) = '\0'; new_value = linep; linep = end; } option = find_option (component, line, GC_BACKEND_ANY); if (!option) gc_error (1, 0, "unknown option %s", line); if ((option->flags & GC_OPT_FLAG_NO_CHANGE)) { gc_error (0, 0, "ignoring new value for option %s", option->name); continue; } change_one_value (option, runtime, flags, new_value, 0); } if (length < 0 || gpgrt_ferror (in)) gc_error (1, errno, "error reading stream 'in'"); } /* Now that we have collected and locally verified the changes, write them out to new configuration files, verify them externally, and then commit them. */ option = gc_component[component].options; while (option && option->name) { /* Go on if we have already seen this backend, or if there is nothing to do. */ if (src_filename[option->backend] || !(option->new_flags || option->new_value)) { option++; continue; } if (gc_backend[option->backend].program) { err = change_options_program (component, option->backend, &src_filename[option->backend], &dest_filename[option->backend], &orig_filename[option->backend], verbatim); if (! err) { /* External verification. */ err = gc_component_check_options (component, out, src_filename[option->backend]); if (err) { gc_error (0, 0, _("External verification of component %s failed"), gc_component[component].name); gpg_err_set_errno (EINVAL); } } } else err = change_options_file (component, option->backend, &src_filename[option->backend], &dest_filename[option->backend], &orig_filename[option->backend]); if (err) break; option++; } /* We are trying to atomically commit all changes. Unfortunately, we cannot rely on gnupg_rename_file to manage the signals for us, doing so would require us to pass NULL as BLOCK to any subsequent call to it. Instead, we just manage the signal handling manually. */ block = 1; gnupg_block_all_signals (); if (! err && ! opt.dry_run) { int i; for (i = 0; i < GC_BACKEND_NR; i++) { if (src_filename[i]) { /* FIXME: Make a verification here. */ assert (dest_filename[i]); if (orig_filename[i]) err = gnupg_rename_file (src_filename[i], dest_filename[i], NULL); else { #ifdef HAVE_W32_SYSTEM /* We skip the unlink if we expect the file not to be there. */ err = gnupg_rename_file (src_filename[i], dest_filename[i], NULL); #else /* HAVE_W32_SYSTEM */ /* This is a bit safer than rename() because we expect DEST_FILENAME not to be there. If it happens to be there, this will fail. */ err = link (src_filename[i], dest_filename[i]); if (!err) err = unlink (src_filename[i]); #endif /* !HAVE_W32_SYSTEM */ } if (err) break; xfree (src_filename[i]); src_filename[i] = NULL; } } } if (err || opt.dry_run) { int i; int saved_errno = errno; /* An error occurred or a dry-run is requested. */ for (i = 0; i < GC_BACKEND_NR; i++) { if (src_filename[i]) { /* The change was not yet committed. */ unlink (src_filename[i]); if (orig_filename[i]) unlink (orig_filename[i]); } else { /* The changes were already committed. FIXME: This is a tad dangerous, as we don't know if we don't overwrite a version of the file that is even newer than the one we just installed. */ if (orig_filename[i]) gnupg_rename_file (orig_filename[i], dest_filename[i], NULL); else unlink (dest_filename[i]); } } if (err) gc_error (1, saved_errno, "could not commit changes"); /* Fall-through for dry run. */ goto leave; } /* If it all worked, notify the daemons of the changes. */ if (opt.runtime) for (backend = 0; backend < GC_BACKEND_NR; backend++) { if (runtime[backend] && gc_backend[backend].runtime_change) (*gc_backend[backend].runtime_change) (0); } /* Move the per-process backup file into its place. */ for (backend = 0; backend < GC_BACKEND_NR; backend++) if (orig_filename[backend]) { char *backup_filename; assert (dest_filename[backend]); backup_filename = xasprintf ("%s.%s.bak", dest_filename[backend], GPGCONF_NAME); gnupg_rename_file (orig_filename[backend], backup_filename, NULL); xfree (backup_filename); } leave: if (block) gnupg_unblock_all_signals (); xfree (line); for (backend = 0; backend < GC_BACKEND_NR; backend++) { xfree (src_filename[backend]); xfree (dest_filename[backend]); xfree (orig_filename[backend]); } } /* Check whether USER matches the current user of one of its group. This function may change USER. Returns true is there is a match. */ static int key_matches_user_or_group (char *user) { char *group; if (*user == '*' && user[1] == 0) return 1; /* A single asterisk matches all users. */ group = strchr (user, ':'); if (group) *group++ = 0; #ifdef HAVE_W32_SYSTEM /* Under Windows we don't support groups. */ if (group && *group) gc_error (0, 0, _("Note that group specifications are ignored\n")); #ifndef HAVE_W32CE_SYSTEM if (*user) { static char *my_name; if (!my_name) { char tmp[1]; DWORD size = 1; GetUserNameA (tmp, &size); my_name = xmalloc (size); if (!GetUserNameA (my_name, &size)) gc_error (1,0, "error getting current user name: %s", w32_strerror (-1)); } if (!strcmp (user, my_name)) return 1; /* Found. */ } #endif /*HAVE_W32CE_SYSTEM*/ #else /*!HAVE_W32_SYSTEM*/ /* First check whether the user matches. */ if (*user) { static char *my_name; if (!my_name) { struct passwd *pw = getpwuid ( getuid () ); if (!pw) gc_error (1, errno, "getpwuid failed for current user"); my_name = xstrdup (pw->pw_name); } if (!strcmp (user, my_name)) return 1; /* Found. */ } /* If that failed, check whether a group matches. */ if (group && *group) { static char *my_group; static char **my_supgroups; int n; if (!my_group) { struct group *gr = getgrgid ( getgid () ); if (!gr) gc_error (1, errno, "getgrgid failed for current user"); my_group = xstrdup (gr->gr_name); } if (!strcmp (group, my_group)) return 1; /* Found. */ if (!my_supgroups) { int ngids; gid_t *gids; ngids = getgroups (0, NULL); gids = xcalloc (ngids+1, sizeof *gids); ngids = getgroups (ngids, gids); if (ngids < 0) gc_error (1, errno, "getgroups failed for current user"); my_supgroups = xcalloc (ngids+1, sizeof *my_supgroups); for (n=0; n < ngids; n++) { struct group *gr = getgrgid ( gids[n] ); if (!gr) gc_error (1, errno, "getgrgid failed for supplementary group"); my_supgroups[n] = xstrdup (gr->gr_name); } xfree (gids); } for (n=0; my_supgroups[n]; n++) if (!strcmp (group, my_supgroups[n])) return 1; /* Found. */ } #endif /*!HAVE_W32_SYSTEM*/ return 0; /* No match. */ } /* Read and process the global configuration file for gpgconf. This optional file is used to update our internal tables at runtime and may also be used to set new default values. If FNAME is NULL the default name will be used. With UPDATE set to true the internal tables are actually updated; if not set, only a syntax check is done. If DEFAULTS is true the global options are written to the configuration files. If LISTFP is set, no changes are done but the configuration file is printed to LISTFP in a colon separated format. Returns 0 on success or if the config file is not present; -1 is returned on error. */ int gc_process_gpgconf_conf (const char *fname_arg, int update, int defaults, estream_t listfp) { int result = 0; char *line = NULL; size_t line_len = 0; ssize_t length; gpgrt_stream_t config; int lineno = 0; int in_rule = 0; int got_match = 0; int runtime[GC_BACKEND_NR]; int backend_id, component_id; char *fname; if (fname_arg) fname = xstrdup (fname_arg); else fname = make_filename (gnupg_sysconfdir (), GPGCONF_NAME EXTSEP_S "conf", NULL); for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++) runtime[backend_id] = 0; config = gpgrt_fopen (fname, "r"); if (!config) { /* Do not print an error if the file is not available, except when running in syntax check mode. */ if (errno != ENOENT || !update) { gc_error (0, errno, "can not open global config file '%s'", fname); result = -1; } xfree (fname); return result; } while ((length = gpgrt_read_line (config, &line, &line_len, NULL)) > 0) { char *key, *component, *option, *flags, *value; char *empty; gc_option_t *option_info = NULL; char *p; int is_continuation; lineno++; key = line; while (*key == ' ' || *key == '\t') key++; if (!*key || *key == '#' || *key == '\r' || *key == '\n') continue; is_continuation = (key != line); /* Parse the key field. */ if (!is_continuation && got_match) break; /* Finish after the first match. */ else if (!is_continuation) { in_rule = 0; for (p=key+1; *p && !strchr (" \t\r\n", *p); p++) ; if (!*p) { gc_error (0, 0, "missing rule at '%s', line %d", fname, lineno); result = -1; gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "missing rule", GPG_ERR_SYNTAX, fname, lineno); continue; } *p++ = 0; component = p; } else if (!in_rule) { gc_error (0, 0, "continuation but no rule at '%s', line %d", fname, lineno); result = -1; continue; } else { component = key; key = NULL; } in_rule = 1; /* Parse the component. */ while (*component == ' ' || *component == '\t') component++; for (p=component; *p && !strchr (" \t\r\n", *p); p++) ; if (p == component) { gc_error (0, 0, "missing component at '%s', line %d", fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " " missing component", GPG_ERR_NO_NAME, fname, lineno); result = -1; continue; } empty = p; *p++ = 0; option = p; component_id = gc_component_find (component); if (component_id < 0) { gc_error (0, 0, "unknown component at '%s', line %d", fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "unknown component", GPG_ERR_UNKNOWN_NAME, fname, lineno); result = -1; } /* Parse the option name. */ while (*option == ' ' || *option == '\t') option++; for (p=option; *p && !strchr (" \t\r\n", *p); p++) ; if (p == option) { gc_error (0, 0, "missing option at '%s', line %d", fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "missing option", GPG_ERR_INV_NAME, fname, lineno); result = -1; continue; } *p++ = 0; flags = p; if ( component_id != -1) { option_info = find_option (component_id, option, GC_BACKEND_ANY); if (!option_info) { gc_error (0, 0, "unknown option at '%s', line %d", fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "unknown option", GPG_ERR_UNKNOWN_OPTION, fname, lineno); result = -1; } } /* Parse the optional flags. */ while (*flags == ' ' || *flags == '\t') flags++; if (*flags == '[') { flags++; p = strchr (flags, ']'); if (!p) { gc_error (0, 0, "syntax error in rule at '%s', line %d", fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "syntax error in rule", GPG_ERR_SYNTAX, fname, lineno); result = -1; continue; } *p++ = 0; value = p; } else /* No flags given. */ { value = flags; flags = NULL; } /* Parse the optional value. */ while (*value == ' ' || *value == '\t') value++; for (p=value; *p && !strchr ("\r\n", *p); p++) ; if (p == value) value = empty; /* No value given; let it point to an empty string. */ else { /* Strip trailing white space. */ *p = 0; for (p--; p > value && (*p == ' ' || *p == '\t'); p--) *p = 0; } /* Check flag combinations. */ if (!flags) ; else if (!strcmp (flags, "default")) { if (*value) { gc_error (0, 0, "flag \"default\" may not be combined " "with a value at '%s', line %d", fname, lineno); result = -1; } } else if (!strcmp (flags, "change")) ; else if (!strcmp (flags, "no-change")) ; else { gc_error (0, 0, "unknown flag at '%s', line %d", fname, lineno); result = -1; } /* In list mode we print out all records. */ if (listfp && !result) { /* If this is a new ruleset, print a key record. */ if (!is_continuation) { char *group = strchr (key, ':'); if (group) { *group++ = 0; if ((p = strchr (group, ':'))) *p = 0; /* We better strip any extra stuff. */ } es_fprintf (listfp, "k:%s:", gc_percent_escape (key)); es_fprintf (listfp, "%s\n", group? gc_percent_escape (group):""); } /* All other lines are rule records. */ es_fprintf (listfp, "r:::%s:%s:%s:", gc_component[component_id].name, option_info->name? option_info->name : "", flags? flags : ""); if (value != empty) es_fprintf (listfp, "\"%s", gc_percent_escape (value)); es_putc ('\n', listfp); } /* Check whether the key matches but do this only if we are not running in syntax check mode. */ if ( update && !result && !listfp && (got_match || (key && key_matches_user_or_group (key))) ) { int newflags = 0; got_match = 1; /* Apply the flags from gpgconf.conf. */ if (!flags) ; else if (!strcmp (flags, "default")) newflags |= GC_OPT_FLAG_DEFAULT; else if (!strcmp (flags, "no-change")) option_info->flags |= GC_OPT_FLAG_NO_CHANGE; else if (!strcmp (flags, "change")) option_info->flags &= ~GC_OPT_FLAG_NO_CHANGE; if (defaults) { /* Here we explicitly allow updating the value again. */ if (newflags) { option_info->new_flags = 0; } if (*value) { xfree (option_info->new_value); option_info->new_value = NULL; } change_one_value (option_info, runtime, newflags, value, 0); } } } if (length < 0 || gpgrt_ferror (config)) { gc_error (0, errno, "error reading from '%s'", fname); result = -1; } if (gpgrt_fclose (config)) gc_error (0, errno, "error closing '%s'", fname); xfree (line); /* If it all worked, process the options. */ if (!result && update && defaults && !listfp) { /* We need to switch off the runtime update, so that we can do it later all at once. */ int save_opt_runtime = opt.runtime; opt.runtime = 0; for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++) { gc_component_change_options (component_id, NULL, NULL, 0); } opt.runtime = save_opt_runtime; if (opt.runtime) { for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++) if (runtime[backend_id] && gc_backend[backend_id].runtime_change) (*gc_backend[backend_id].runtime_change) (0); } } xfree (fname); return result; } /* * Apply the profile FNAME to all known configure files. */ gpg_error_t gc_apply_profile (const char *fname) { gpg_error_t err; char *fname_buffer = NULL; char *line = NULL; size_t line_len = 0; ssize_t length; estream_t fp; int lineno = 0; int runtime[GC_BACKEND_NR]; int backend_id; int component_id = -1; int skip_section = 0; int error_count = 0; int newflags; if (!fname) fname = "-"; for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++) runtime[backend_id] = 0; if (!(!strcmp (fname, "-") || strchr (fname, '/') #ifdef HAVE_W32_SYSTEM || strchr (fname, '\\') #endif || strchr (fname, '.'))) { /* FNAME looks like a standard profile name. Check whether one * is installed and use that instead of the given file name. */ fname_buffer = xstrconcat (gnupg_datadir (), DIRSEP_S, fname, ".prf", NULL); if (!access (fname_buffer, F_OK)) fname = fname_buffer; } fp = !strcmp (fname, "-")? es_stdin : es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); log_error ("can't open '%s': %s\n", fname, gpg_strerror (err)); return err; } if (opt.verbose) log_info ("applying profile '%s'\n", fname); err = 0; while ((length = es_read_line (fp, &line, &line_len, NULL)) > 0) { char *name, *flags, *value; gc_option_t *option_info = NULL; char *p; lineno++; name = line; while (*name == ' ' || *name == '\t') name++; if (!*name || *name == '#' || *name == '\r' || *name == '\n') continue; trim_trailing_spaces (name); /* Check whether this is a new section. */ if (*name == '[') { name++; skip_section = 0; /* New section: Get the name of the component. */ p = strchr (name, ']'); if (!p) { error_count++; log_info ("%s:%d:%d: error: syntax error in section tag\n", fname, lineno, (int)(name - line)); skip_section = 1; continue; } *p++ = 0; if (*p) log_info ("%s:%d:%d: warning: garbage after section tag\n", fname, lineno, (int)(p - line)); trim_spaces (name); component_id = gc_component_find (name); if (component_id < 0) { log_info ("%s:%d:%d: warning: skipping unknown section '%s'\n", fname, lineno, (int)(name - line), name ); skip_section = 1; } continue; } if (skip_section) continue; if (component_id < 0) { error_count++; log_info ("%s:%d:%d: error: not in a valid section\n", fname, lineno, (int)(name - line)); skip_section = 1; continue; } /* Parse the option name. */ for (p = name; *p && !spacep (p); p++) ; *p++ = 0; value = p; option_info = find_option (component_id, name, GC_BACKEND_ANY); if (!option_info) { error_count++; log_info ("%s:%d:%d: error: unknown option '%s' in section '%s'\n", fname, lineno, (int)(name - line), name, gc_component[component_id].name); continue; } /* Parse the optional flags. */ trim_spaces (value); flags = value; if (*flags == '[') { flags++; p = strchr (flags, ']'); if (!p) { log_info ("%s:%d:%d: warning: invalid flag specification\n", fname, lineno, (int)(p - line)); continue; } *p++ = 0; value = p; trim_spaces (value); } else /* No flags given. */ flags = NULL; /* Set required defaults. */ if (gc_arg_type[option_info->arg_type].fallback == GC_ARG_TYPE_NONE && !*value) value = "1"; /* Check and save this option. */ newflags = 0; if (flags && !strcmp (flags, "default")) newflags |= GC_OPT_FLAG_DEFAULT; if (newflags) option_info->new_flags = 0; if (*value) { xfree (option_info->new_value); option_info->new_value = NULL; } change_one_value (option_info, runtime, newflags, value, 1); } if (length < 0 || es_ferror (fp)) { err = gpg_error_from_syserror (); error_count++; log_error (_("%s:%u: read error: %s\n"), fname, lineno, gpg_strerror (err)); } if (es_fclose (fp)) log_error (_("error closing '%s'\n"), fname); if (error_count) log_error (_("error parsing '%s'\n"), fname); xfree (line); /* If it all worked, process the options. */ if (!err) { /* We need to switch off the runtime update, so that we can do it later all at once. */ int save_opt_runtime = opt.runtime; opt.runtime = 0; for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++) { gc_component_change_options (component_id, NULL, NULL, 1); } opt.runtime = save_opt_runtime; if (opt.runtime) { for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++) if (runtime[backend_id] && gc_backend[backend_id].runtime_change) (*gc_backend[backend_id].runtime_change) (0); } } xfree (fname_buffer); return err; }