diff options
Diffstat (limited to 'tools')
53 files changed, 29040 insertions, 0 deletions
diff --git a/tools/ChangeLog-2011 b/tools/ChangeLog-2011 new file mode 100644 index 0000000..6821a87 --- /dev/null +++ b/tools/ChangeLog-2011 @@ -0,0 +1,1284 @@ +2011-12-01 Werner Koch <wk@g10code.com> + + NB: ChangeLog files are no longer manually maintained. Starting + on December 1st, 2011 we put change information only in the GIT + commit log, and generate a top-level ChangeLog file from logs at + "make dist". See doc/HACKING for details. + +2011-08-26 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (gc_component): Mark for translation. Suggested + by Yuri Chornoivan. + +2011-03-08 Werner Koch <wk@g10code.com> + + * symcryptrun.c [HAVE_UTMP_H]: Include utmp.h. + +2011-02-23 Werner Koch <wk@g10code.com> + + * gpgconf.c: Add command --kill. + * gpgconf-comp.c (gc_component_kill): New. + (gpg_agent_runtime_change, scdaemon_runtime_change): Add kill flag. + +2011-02-03 Werner Koch <wk@g10code.com> + + * watchgnupg.c (print_version): Update copyright year. + +2010-12-14 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (gc_options_gpg_agent, gc_options_scdaemon) + (gc_options_gpg, gc_options_gpgsm, gc_options_dirmngr): Define to + NULL if corresponding BUILD_WITH_foo is not defined. + +2010-12-02 Werner Koch <wk@g10code.com> + + * no-libgcrypt.c (gcry_cipher_algo_name): New. + +2010-11-23 Werner Koch <wk@g10code.com> + + * Makefile.am (gpgconf_LDFLAGS): Add extra_bin_ldflags. + +2010-11-17 Marcus Brinkmann <mb@g10code.com> + + * gogconf.c: Revert accidental debug output commit. + +2010-10-27 Werner Koch <wk@g10code.com> + + * symcryptrun.c (confucius_mktmpdir): Use TMPDIR. + +2010-10-14 Werner Koch <wk@g10code.com> + + * gpg-connect-agent.c: Add option --agent-program. + + * gpg-connect-agent.c (start_agent): Rewrite using the + start_new_gpg_agent function. + + * gpgconf-comp.c (gpg_agent_runtime_change): Use gpg-connect-agent + on all platforms. + +2010-10-06 Werner Koch <wk@g10code.com> + + * watchgnupg.c (print_version): Add option --time-only. + +2010-10-05 Werner Koch <wk@g10code.com> + + * watchgnupg.c (main): Support TCP and local socket listening. + (main): Factor some code out to .. + (setup_client): this. + (err): New. + (client_list): New. + +2010-08-25 Werner Koch <wk@g10code.com> + + * gpgtar-extract.c (create_directory): Add .p7m as known + extension. + + * gpgtar.c: Add -t as short option for --list-archive. + * gpgtar-extract.c (gpgtar_extract): Use filename "-" for stdin. + Fix dirprefix setting. + * gpgtar-list.c (gpgtar_list): Ditto. + +2010-08-24 Werner Koch <wk@g10code.com> + + * gpgtar.c (opts): Fix --cms and --openpgp names. + +2010-08-23 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (GPGNAME) [W32CE]: s/gpg2/gpg/. + (get_config_filename) [W32CE]: Adjust absolute file name check. + + * gpgconf-comp.c (retrieve_options_from_program) + (retrieve_options_from_file, retrieve_options_from_program) + (copy_file, gc_process_gpgconf_conf): Do not use es_ferror after a + failed es_fclose. Note that the stream is in any case invalid + after calling es_fclose and that es_fclose does set ERRNO. + + * Makefile.am (maybe_commonpth_libs): New. + (gpgconf_LDADD): Use it. + +2010-08-20 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (collect_error_output): Remove extra CRs. + +2010-08-19 Werner Koch <wk@g10code.com> + + * gpgconf.c (main): Fix --check-options. + + * gpgconf-comp.c (gc_component_check_options): Replace + gnupg_spawn_process_fd by gnupg_spawn_process. + (retrieve_options_from_program): Ditto. + (collect_error_output): Change to use estream. + + * gpgconf-comp.c: Add new backend and component for PINENTRY. + (gc_component_check_options): Use --version to test the pinentry. + (gc_component_retrieve_options, gc_component_change_options): + Ignore the pinentry component. + +2010-08-16 Werner Koch <wk@g10code.com> + + * gpgconf.c (get_outfp): Change to use estream. + (main): Replace fprintf by es_fprintf. + * gpgconf-comp.c (gc_component_list_components) + (gc_check_programs, gc_component_list_options) + (gc_component_change_options, gc_component_check_options) + (list_one_option, gc_process_gpgconf_conf): Replace FILE* args by + estream_t. + +2010-08-13 Werner Koch <wk@g10code.com> + + * Makefile.am (gpgkey2ssh_LDADD): Add NETLIBS. + +2010-08-11 Werner Koch <wk@g10code.com> + + * gpgtar-create.c (gpgtar_create): Allow "-" for stdout in + opt.outfile. Switch es_stdout to binary mode. + +2010-08-09 Werner Koch <wk@g10code.com> + + * watchgnupg.c: Inlcude in.h and inet.h. + (main): Support tcp connections. + + * gpgtar.c (main): Add options -T and --null. + * gpgtar-create.c (gpgtar_create): Implement option --null. + +2010-07-16 Werner Koch <wk@g10code.com> + + * gpgtar-create.c: Rewrite to better support W32. + +2010-07-01 Werner Koch <wk@g10code.com> + + * gpgtar.c: Add option --set-filename. + +2010-06-24 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (gpg_agent_runtime_change) + (scdaemon_runtime_change, retrieve_options_from_program): Use HANG + option for gnupg_wait_progress. Fixes regression from 2010-06-09. + +2010-06-07 Werner Koch <wk@g10code.com> + + * gpgtar.c, gpgtar.h, gpgtar-list.c, gpgtar-create.c + * gpgtar-extract.c: New. + * Makefile.am (commonpth_libs): New. + (gpgtar_SOURCES, gpgtar_CFLAGS, gpgtar_LDADD): New. + (bin_PROGRAMS) [!W32CE]: Add gpgtar. + +2010-04-20 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (option_check_validity): Use dummy variables to + silence gcc warning. + +2010-04-14 Werner Koch <wk@g10code.com> + + * Makefile.am (bin_PROGRAMS) [W32CE]: Exclude gpgkey2ssh. + (noinst_PROGRAMS) [W32CE]: Don't build them. + (pwquery_libs) [W32CE]: Set to empty. + +2010-03-25 Werner Koch <wk@g10code.com> + + * Makefile.am (opt_libassuan_libs) [W32CE]: New. + (gpgconf_LDADD): Use it. + + * gpgconf-comp.c: Include signal.h only if available. Use + gpg_err_set_errno. + (key_matches_user_or_group) [W32CE]: Do not match any user. + +2010-03-15 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (my_dgettext): + s/gettext_select_utf8/gettext_use_utf8/. + +2010-03-10 Werner Koch <wk@g10code.com> + + * Makefile.am (common_libs): Remove libjnlib.a. + +2010-03-08 Werner Koch <wk@g10code.com> + + * no-libgcrypt.c (gcry_create_nonce): New. + +2010-02-26 Werner Koch <wk@g10code.com> + + * gpg-connect-agent.c (main): New option --tcp-socket. + +2010-01-10 Werner Koch <wk@g10code.com> + + * symcryptrun.c (utmp.h): Remove header; it is not used. + +2009-12-18 Werner Koch <wk@g10code.com> + + * applygnupgdefaults (errorfile): Use mktemp. Fixes bug#1146. + +2009-12-08 Marcus Brinkmann <marcus@g10code.de> + + * gpg-connect-agent.c (main): Convert posix fd to assuan fd. + +2009-12-07 Werner Koch <wk@g10code.com> + + * no-libgcrypt.c (gcry_strdup): Actually copy the string. + +2009-11-23 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (gc_options_gpg): Add default_pubkey_algo. + +2009-11-05 Marcus Brinkmann <marcus@g10code.de> + + * gpg-connect-agent.c (start_agent): Update use of + assuan_socket_connect and assuan_pipe_connect. + +2009-11-04 Werner Koch <wk@g10code.com> + + * gpg-connect-agent.c (read_and_print_response): Add arg WITHHASH. + (main): Pass true for WITHHASH for the HELP command. + +2009-09-23 Marcus Brinkmann <marcus@g10code.de> + + * gpg-connect-agent.c (getinfo_pid_cb, read_and_print_response) + (main): Update to new Assuan API. + +2009-07-21 Werner Koch <wk@g10code.com> + + * gpgsplit.c (my_strusage): Remove i18n stuff. + +2009-07-07 Werner Koch <wk@g10code.com> + + * gpg-connect-agent.c (start_agent): Adjust for changed args of + send_pinentry_environment. + +2009-06-30 Werner Koch <wk@g10code.com> + + * ccidmon.c (parse_line_sniffusb): Take also TAB as delimiter. + +2009-06-29 Werner Koch <wk@g10code.com> + + * ccidmon.c (parse_line_sniffusb): New. + (main): Add option --sniffusb. + +2009-06-08 Werner Koch <wk@g10code.com> + + * gpgconf.c (main): Call gnupg_reopen_std. Should fix bug#1072. + +2009-05-19 Werner Koch <wk@g10code.com> + + * watchgnupg.c: Include jnlib/mischelp.h if required. + (main): Use SUN_LEN. + +2009-04-17 Werner Koch <wk@g10code.com> + + * ccidmon.c: New. + +2009-03-03 Werner Koch <wk@g10code.com> + + * gpgconf.c: New command --reload. + + * gpgconf-comp.c (gc_component_reload): New. + +2009-03-02 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (scdaemon_runtime_change): Killsc d only if it is + not running. + +2009-02-27 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (gpg_agent_runtime_change): Declare static. + (scdaemon_runtime_change): New. + (gc_backend_scdaemon): Register new function. + (gc_options_scdaemon): Make most options runtime changable. + +2009-01-20 Werner Koch <wk@g10code.com> + + * gpgconf.c (main): Print more directories. + +2008-12-09 Werner Koch <wk@g10code.com> + + * gpg-check-pattern.c (main): Call i18n_init before + init_common_subsystems. + * gpg-connect-agent.c (main): Ditto. + * gpgconf.c (main): Ditto. + * symcryptrun.c (main): Ditto. + +2008-12-08 Werner Koch <wk@g10code.com> + + * gpgkey2ssh.c (main): Change order of output for RSA. Change name + of DSA identifier. Reported by Daniel Kahn Gillmor. This is + bug#901. + +2008-12-05 Werner Koch <wk@g10code.com> + + * gpg-connect-agent.c (opts): Use ARGPARSE_ macros. + (start_agent) [W32]: Start agent if not running. + +2008-12-03 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c <scdaemon>: Add option --card-timeout. Remove + unused option --disable-opensc. + +2008-10-20 Werner Koch <wk@g10code.com> + + * gpgsplit.c (write_part): Remove unused arg FNAME. Change caller. + (do_split): Ditto. + + * no-libgcrypt.c (gcry_control): Mark unused arg. + * gpg-connect-agent.c (do_recvfd): Ditto. + * gpgparsemail.c (mime_signed_begin, mime_encrypted_begin): Ditto. + (pkcs7_begin): Ditto. + +2008-10-01 Werner Koch <wk@g10code.com> + + * gpg-connect-agent.c (main): New command datafile. + (read_and_print_response): Print to the defined datafile. + +2008-09-30 Werner Koch <wk@g10code.com> + + * gpgconf.c (main) <aListDirs>: Print the bindir. + +2008-08-06 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (gc_options_gpgsm): Change type of keyserver + option to GC_ARG_TYPE_LDAP_SERVER. + + * gpgconf-comp.c (retrieve_options_from_file): Transfer the + NO_CHANGE flag from the file name option to the list option. + +2008-06-19 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (GC_ARG_TYPE_ALIAS_LIST): New. + (gc_arg_type): Add fallback type. + (gc_options_gpg): Add option "group". + +2008-06-12 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (gc_options_gpgsm): Add option keyserver. + +2008-05-26 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c: Replace pathname by filename everywhere. + + * gpgconf.c (enum cmd_and_opt_values): Add aListDirs. + (opts): Add aListDirs option. + (main): Handle aListDirs. + * gpgconf.h (gc_percent_escape): New declaration. + * gpgconf-comp.c (my_percent_escape): Make non-static and rename + to ... + (gc_percent_escape): ... this. Change all callers. + +2008-05-26 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (gpg_agent_runtime_change) [W32]: Issue + "reloadagent" command to gpg-agent. + + * gpg-connect-agent.c (main): Allow server command on the command + line. + +2008-05-20 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf.h (gc_component_check_programs): Rename to ... + (gc_check_programs): ... this. + (gc_component_change_options): Add argument OUT. + (gc_component_check_options): New function. + * gpgconf.c (enum cmd_and_opt_values): New option aCheckOptions. + (opts): Add new option aCheckOptions (aka --check-options). + (main): Handle new option aCheckOptions. + * gpgconf-comp.c (gc_component_check_programs): Rename to ... + (gc_check_programs): ... this. Refactor core of it to ... + (gc_component_check_options): ... this new function. + (gc_component_change_options): Add new argument OUT. Externally + verify all changes. Implement option --dry-run. + +2008-05-09 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (my_dgettext) [USE_SIMPLE_GETTEXT]: Hack to + parly support translations. + +2008-04-08 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (gc_options_gpg): Add --auto-key-locate. + +2008-03-26 Werner Koch <wk@g10code.com> + + * make-dns-cert.c: Include unistd.h. Use config.h if requested. + (cert_key): Protect read against EINTR. + (main): Print SVN revision for standalone version. + +2008-03-05 Werner Koch <wk@g10code.com> + + * gpg-connect-agent.c (arithmetic_op): Add logical not, or and and. + (get_var_ext): Add functions errcode, errsource and errstring. + (read_and_print_response): Store server reply in $? variable. + (main): Implement IF command. + +2008-02-27 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (option_check_validity): For now, error out on + empty strings. + (enum): Add GC_ARG_TYPE_PUB_KEY and GC_ARG_TYPE_SEC_KEY. + +2008-02-01 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (gc_component_list_options): Fix memcpy. + Reported by Marc Mutz. + +2008-01-22 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c: Use gnupg domain for honor-http-proxy. Make + "LDAP server list" group title translatable. + +2008-01-17 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (change_options_program): Strip duplicated + utf8-strings entries for gnupg backend. Don't create them either. + +2007-12-10 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (gc_component_list_options): Fix up expert level + of group. + +2007-12-04 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (gc_component_list_components): Do not print a + trailing semi-colon to ensure forward compatibility, as this would + indicate another empty field. + (gc_process_gpgconf_conf): Likewise. + +2007-11-15 Werner Koch <wk@g10code.com> + + * gpg-connect-agent.c (start_agent): Adjust changed + send_pinentry_environment. + +2007-10-24 Werner Koch <wk@g10code.com> + + * gpg-connect-agent.c (substitute_line): Restore temporary nul + marker. + (main): Add /while command. + +2007-10-23 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (gc_process_gpgconf_conf): Add arg + LISTFP. Changed all callers. + * gpgconf.h: Add gc_error. + * gpgconf.c: Add command --list-config. + (get_outfp): New. + (main): Make --output work. + + * gpgconf-comp.c (gc_options_gpg_agent): Replace accidently used + GC_BACKEND_SCDAEMON. We should consider to create these tables + from plain files. + +2007-10-22 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (retrieve_options_from_program): Replace use of + popen by our gnupg_spawn_process_fd. This is required because + popen under Windows can't handle long filenames. + +2007-10-19 Werner Koch <wk@g10code.com> + + * symcryptrun.c (confucius_get_pass): Use utf8 switching functions. + + * gpg-connect-agent.c (get_var_ext): New. + (substitute_line): Use it. + (assign_variable): Implement /slet in terms of get_var_ext. + (main): New option -s/--subst. + (add_definq): Add arg IS_VAR. Change all callers. + (main): Add command /definq. + (handle_inquire): Implement new command. + (substitute_line_copy): New. + (unescape_string, unpercent_string): New. + * no-libgcrypt.c (gcry_set_outofcore_handler) + (gcry_set_fatalerror_handler, gcry_set_log_handler): New. + * Makefile.am (gpg_connect_agent_LDADD): Link to libreadline. + + * gpgconf-comp.c (retrieve_options_from_file): Don't call fclose + with NULL. Fixes bug 842. + +2007-10-12 Werner Koch <wk@g10code.com> + + * gpg-connect-agent.c (substitute_line): Allow ${foo} syntax. + +2007-10-11 Werner Koch <wk@g10code.com> + + * gpg-connect-agent.c (get_var): Expand environment variables. + Suggested by Marc Mutz. + (set_var): Return the value. + (assign_variable): Add arg syslet. + (main): New command /slet. + (gnu_getcwd): New. + (assign_variable): Add tag cwd, and *dir. + +2007-10-02 Werner Koch <wk@g10code.com> + + * no-libgcrypt.c (gcry_malloc_secure): New. + + * gpg-connect-agent.c (set_var, set_int_var, get_var) + (substitute_line, show_variables, assign_variable) + (do_open, do_close, do_showopen): New. + (main): Add new commands /nosubst, /subst, /let, /showvar, /open, + /close and /showopen. + (main): New commands /run and /bye. + +2007-10-01 Werner Koch <wk@g10code.com> + + * gpg-connect-agent.c (do_sendfd): Use INT2FD for assuan_sendfd. + +2007-09-26 Werner Koch <wk@g10code.com> + + * gpg-connect-agent.c (main): Print the first response from the + server. + +2007-09-14 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c: Make a string translatable. + +2007-09-04 Moritz Schulte <moritz@g10code.com> + + * gpgsm-gencert.sh: Use printf instead of echo. + +2007-09-04 Moritz Schulte <moritz@g10code.com> + + * gpgkey2ssh.c: Include sysutils.h so that gnupg_tmpfile() is + declared. + +2007-08-31 Werner Koch <wk@g10code.com> + + * gpgparsemail.c: Support PGP/MIME signed messages. + + * gpgconf-comp.c (gc_component_list_components): List the programs + names. + +2007-08-29 Werner Koch <wk@g10code.com> + + * gpgconf.c: New command --check-programs. + * gpgconf-comp.c (gc_component_check_programs): New. + (gc_backend): Add member MODULE_NAME and add these module names. + (retrieve_options_from_program): Use module name so that we use an + absolute file name and don't rely on $PATH. + (collect_error_output): New. + * no-libgcrypt.c (gcry_control): New. + +2007-08-28 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c <gpg-agent>: Add options --max-passphrase-days + and --enable-passphrase-history. + +2007-08-27 Werner Koch <wk@g10code.com> + + * gpg-check-pattern.c: New + * Makefile.am (libexec_PROGRAMS): Add unless DISABLE_REGEX. + +2007-08-24 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c <gpg-agent>: Add options --check-passphrase-pattern, + --min-passphrase-nonalpha and --enforce-passphrase-constraints and + move them into a new "passphrase policy" group. + (gc_component) [W32]: Enable dirmngr. + +2007-08-21 Werner Koch <wk@g10code.com> + + * gpgkey2ssh.c (key_to_blob): Use gnupg_tmpfile(). + +2007-08-02 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c: Factor the public GC_OPT_FLAG constants out and + include gc-opt-flags.h. + +2007-07-17 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c: Add --encrypt-to and --default-key to gpg and + gpgsm. + +2007-07-16 Marcus Brinkmann <marcus@g10code.de> + + * gpg-connect-agent.c (main): Bail out if write fails. + +2007-07-05 Marcus Brinkmann <marcus@g10code.de> + + * symcryptrun.c (confucius_get_pass): Define orig_codeset if + [ENABLE_NLS], not [HAVE_LANGINFO_CODESET]. + +2007-06-26 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (key_matches_user_or_group) [W32]: Implement user + name matching. + (GPGNAME): New. Use it instead of "gpg". + (gc_component) [W32]: Disable dirmngr for now. + (gc_component_retrieve_options): Ignore components without options. + (gc_component_change_options): Ditto. + (gc_component_list_options): Ditto. + (gc_component_find, gc_component_list_components): Ditto. + +2007-06-19 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (percent_escape): Rename to my_percent_escape. + Changed all callers. + +2007-06-18 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (retrieve_options_from_file): Close LIST_FILE. + (copy_file): In error case, save/restore errno. Close SRC and DST. + (gc_component_change_options): Catch error from unlink(). Remove + target backup file before rename(). + +2007-06-15 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (copy_file) [HAVE_W32_SYSTEM]: New function. + (change_options_file, change_options_program) [HAVE_W32_SYSTEM]: + Copy backup file. + (gc_component_change_options) [HAVE_W32_SYSTEM]: Non-atomic replace. + (gc_process_gpgconf_conf): Rename fname to fname_arg and + fname_buffer to fname, initialize fname with fname_arg, discarding + const qualifier. + +2007-06-15 Werner Koch <wk@g10code.com> + + * Makefile.am (symcryptrun_LDADD): It is LIBICONV and not LIBINCONV. + (gpgconf_LDADD, symcryptrun_LDADD): Add W32SOCKLIBS. + +2007-06-14 Werner Koch <wk@g10code.com> + + * symcryptrun.c (main): Setup default socket name for + simple-pwquery. + (MAP_SPWQ_ERROR_IMPL): New. Use it for all spwq error returns. + +2007-06-12 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (gc_process_gpgconf_conf): Replace + GNUPG_SYSCONFDIR by a function call. + + * gpg-connect-agent.c (main): Replace some calls by + init_common_subsystems. + * gpgconf.c (main): Ditto. + * symcryptrun.c (main): Ditto. + +2007-06-11 Werner Koch <wk@g10code.com> + + * symcryptrun.c (main) [W32]: Call pth_init. + * gpgconf.c (main) [W32]: Call pth_init + * gpg-connect-agent.c (main) [W32]: Call pth_init. + +2007-06-06 Werner Koch <wk@g10code.com> + + * Makefile.am (bin_PROGRAMS) [W32]: Do not build gpgparsemail. + + * gpgconf-comp.c [W32]: Do not include pwd.h and grp.h. + (key_matches_user_or_group) [W32]: For now always return false. + + * symcryptrun.c (i18n_init): Remove. + * gpgconf.c (i18n_init): Remove. + * gpg-connect-agent.c (i18n_init): Remove. + +2007-05-19 Marcus Brinkmann <marcus@g10code.de> + + * symcryptrun.c (confucius_get_pass): Free ORIG_CODESET on error. + +2007-05-08 Werner Koch <wk@g10code.com> + + * sockprox.c: New. It needs to be build manually. By Moritz + Schulte. + +2007-04-20 Werner Koch <wk@g10code.com> + + * symcryptrun.c (my_gcry_logger): Removed. + (main): Call setup_libgcrypt_logging. + +2007-04-03 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c: Allow changing of --allow-mark-trusted. + + * gpg-connect-agent.c (main): New option --decode and commands + decode and undecode. + (read_and_print_response): Implement option. + +2007-03-20 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (gc_options_gpgsm): Add p12-charset. + +2007-03-07 Werner Koch <wk@g10code.com> + + * applygnupgdefaults: New. + * Makefile.am (sbin_SCRIPTS): Add it + +2007-03-06 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c: Include pwd.h and grp.h. + (GC_OPT_FLAG_NO_CHANGE): New. + (gc_component_change_options): Implement it. + (gc_options_gpg_agent): Add options for all ttl values and + min-passphrase-length. Apply new flag to some of them. + (gc_process_gpgconf_conf, key_matches_user_or_group): New. + (gc_component_change_options): Factor some code out to .. + (change_one_value): .. new. + (gc_component_retrieve_options): Allow -1 for COMPONENT to iterate + over al components. + * gpgconf.c (main): New commands --check-config and + --apply-defaults. Call gc_process_gpgconf_conf. + +2007-01-31 Werner Koch <wk@g10code.com> + + * Makefile.am (symcryptrun_LDADD): Add LIBICONV. + (gpgkey2ssh_LDADD): Ditto. + +2006-12-13 David Shaw <dshaw@jabberwocky.com> + + * Makefile.am (gpgsplit_LDADD): Link to LIBINTL if we're using the + built-in code. + +2006-12-07 David Shaw <dshaw@jabberwocky.com> + + * Makefile.am: Link to iconv for jnlib dependency. + +2006-11-23 Werner Koch <wk@g10code.com> + + * Makefile.am (gpg_connect_agent_LDADD): Add NETLIBS. + +2006-11-21 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (list_one_option): Cast print size_t arg. + +2006-11-17 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c: Made disable-keypad a basic option. + +2006-11-03 Werner Koch <wk@g10code.com> + + * symcryptrun.c: Include signal.h and include pth.h only if test + asserts that it exists. + +2006-10-23 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c <gpgsm>: Add --cipher-algo. + +2006-10-20 Werner Koch <wk@g10code.com> + + * gpgsm-gencert.sh: Enhanced the main menu. + +2006-10-12 Werner Koch <wk@g10code.com> + + * Makefile.am (gpg-zip, gpgsplit): Do not install due to a + conflict with gpg1. + +2006-10-11 Werner Koch <wk@g10code.com> + + * gpgsm-gencert.sh: Allow generation of card keys. + +2006-10-08 Werner Koch <wk@g10code.com> + + * Makefile.am (gpgkey2ssh_LDADD): Add LIBINTL. Suggested by + Andreas Metzler. + +2006-09-22 Werner Koch <wk@g10code.com> + + * no-libgcrypt.c: Changed license to a simple all permissive one. + +2006-09-20 Werner Koch <wk@g10code.com> + + * Makefile.am: Changes to allow parallel make runs. + +2006-09-12 Werner Koch <wk@g10code.com> + + Replaced all call gpg_error_from_errno(errno) by + gpg_error_from_syserror(). + + * gpg-connect-agent.c (read_and_print_response): With verbosity + level 2 also print comment lines. + +2006-09-06 Werner Koch <wk@g10code.com> + + * gpg-connect-agent.c: Switch everything to new Assuan error code + style. + + * no-libgcrypt.c (out_of_core): Reanmed to ... + (out_of_memory): .. this to avoid name clash with util.h. + +2006-08-21 Werner Koch <wk@g10code.com> + + * gpgsplit.c: New. Taken from 1.4. Adjusted to GnuPG2. + + * Makefile.am (noinst_PROGRAMS): New. + +2006-06-09 Marcus Brinkmann <marcus@g10code.de> + + * Makefile.am (gpgconf_LDADD): Add $(GPG_ERROR_LIBS). + (gpgkey2ssh_LDADD): Add ../jnlib/libjnlib.a. + +2006-05-23 Werner Koch <wk@g10code.com> + + * gpgparsemail.c: Include config.h if available + (stpcpy): Conditional include it. + + * gpgconf-comp.c (hextobyte): Removed as it is now availble in + jnlib. + +2005-12-20 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (gc_options_gpg): Add allow-pka-lookup. + +2005-12-14 Werner Koch <wk@g10code.com> + + * Makefile.am (bin_PROGRAMS): Build gpgparsemail. + + * gpgparsemail.c (pkcs7_begin): New. + (parse_message, message_cb): Add support of direct pkcs signatures. + +2005-10-19 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (gc_options_scdaemon): New option --disable-keypad. + +2005-09-22 Werner Koch <wk@g10code.com> + + * rfc822parse.c (parse_field): Tread Content-Disposition special. + +2005-10-08 Marcus Brinkmann <marcus@g10code.de> + + * Makefile.am (watchgnupg_LDADD): New variable. + + * Makefile.am (gpgconf_LDADD): Add ../gl/libgnu.a after + ../common/libcommon.a. + (symcryptrun_LDADD, gpg_connect_agent_LDADD, gpgkey2ssh_LDADD): + Likewise. + +2005-09-29 Marcus Brinkmann <marcus@g10code.de> + + * Makefile.am (AM_CFLAGS): Add $(LIBGCRYPT_CFLAGS). + +2005-09-06 Werner Koch <wk@g10code.com> + + * rfc822parse.c, rfc822parse.h: Changed license to LGPL. + +2005-08-01 Werner Koch <wk@g10code.com> + + * gpgsm-gencert.sh: Allow entering a keygrip to generate a CSR from + an existing key. + +2005-07-21 Werner Koch <wk@g10code.com> + + * gpgsm-gencert.sh: Reworked to allow for multiple email addresses + as well as DNsanmes and URi. Present the parameter file before + creating the certificate. + +2005-07-04 Marcus Brinkmann <marcus@g10code.de> + + * symcryptrun.c (SYMC_BAD_PASSPHRASE, SYMC_CANCELED): New symbols, + use instead constants. + (hash_string): New function copied from simple-gettext.c. + (confucius_get_pass): Take new argument CACHEID. + (confucius_process): Calculate cacheid and pass it to + confucius_get_pass. Clear passphrase from cache if necessary. + +2005-06-16 Werner Koch <wk@g10code.com> + + * gpg-connect-agent.c (read_and_print_response): Made LINELEN a + size_t. + +2005-06-04 Marcus Brinkmann <marcus@g10code.de> + + * symcryptrun.c (main): Allow any number of arguments, don't use + first argument as input file name. Pass extra arguments to + confucius_main. + (confucius_main): Accept new arguments argc and argv and pass them + to confucius_process. + (confucius_process): Accept new arguments argc and argv and pass + them to the confucius process. + +2005-06-01 Werner Koch <wk@g10code.com> + + * symcryptrun.c: Include mkdtemp.h. + +2005-05-31 Werner Koch <wk@g10code.com> + + * watchgnupg.c: Make sure that PF_LCOAL and AF_LOCAL are defines. + Noted by Ray Link. + +2005-05-28 Moritz Schulte <moritz@g10code.com> + + * gpgkey2ssh.c: New file. + * Makefile.am (bin_PROGRAMS): Added gpgkey2ssh. + +2005-05-20 Werner Koch <wk@g10code.com> + + * gpg-connect-agent.c (add_definq, show_definq, clear_definq) + (handle_inquire): New. + (read_and_print_response): Handle INQUIRE command. + (main): Implement control commands. + +2005-04-21 Werner Koch <wk@g10code.com> + + * symcryptrun.c (main): Optionally allow the input file as command + line argument. + + * gpgconf-comp.c: Add gpgsm option disable-trusted-cert-crl-check. + +2005-04-20 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c: Add gpg-agent:disable-scdaemon. + +2005-04-19 Marcus Brinkmann <marcus@g10code.de> + + * symcryptrun.c: Add --input option. + +2005-04-15 Marcus Brinkmann <marcus@g10code.de> + + * symcryptrun.c (TEMP_FAILURE_RETRY): Define if not defined. + + * symcryptrun.c (remove_file): New function. + (confucius_copy_file): Accept new argument PLAIN and shred the + file if it is set on error. + + * Makefile.am: Define symcryptrun make variable depending on + BUILD_SYMCRYPTUN. + (bin_PROGRAMS): Add ${symcryptrun} instead symcryptrun. + (symcryptrun_LDADD): Use $(LIBUTIL_LIBS) instead of -lutil. + +2005-04-11 Werner Koch <wk@g10code.com> + + * symcryptrun.c (confucius_mktmpdir): Changed to use mkdtmp(3). + +2005-04-11 Marcus Brinkmann <marcus@g10code.de> + + * symcryptrun.c: Implement config file parsing. + + * Makefile.am (bin_PROGRAMS): Add symcryptrun. + (symcryptrun_SOURCES, symcryptrun_LDADD): New variables. + * symcryptrun.c: New file. + +2005-03-31 Werner Koch <wk@g10code.com> + + * gpg-connect-agent.c (start_agent): Use PATHSEP_C instead of ':'. + +2005-03-09 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c <dirmngr>: Add honor-http-proxy. + +2005-02-25 Werner Koch <wk@g10code.com> + + * no-libgcrypt.c (gcry_strdup): New. + +2005-02-24 Werner Koch <wk@g10code.com> + + * gpg-connect-agent.c: New. + * Makefile.am: Add it. + +2004-12-21 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (get_config_pathname) [DOSISH]: Detect absolute + pathnames with a drive letter. + +2004-12-15 Werner Koch <wk@g10code.com> + + * Makefile.am (bin_PROGRAMS) [W32]: Do not build watchgnupg. + + * gpgconf-comp.c (gpg_agent_runtime_change) [W32]: No way yet to + send a signal. Disable. + (change_options_file, change_options_program) [W32]: No link(2), + so we disable it. + (gc_component_change_options): Use rename instead of link. + +2004-12-13 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c <ignore-ocsp-service-url>: Fixed typo. + +2004-11-24 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c <dirmngr>: Add --ignore-http-dp, --ignore-ldap-dp + and --ignore-ocsp-service-url. + +2004-11-23 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c <dirmngr>: Add the proxy options. + <gpgsm>: Add --prefer-system-daemon. + +2004-11-11 Werner Koch <wk@g10code.com> + + * watchgnupg.c (main): Fixed test for read error. + +2004-10-22 Werner Koch <wk@g10code.com> + + * Makefile.am (bin_SCRIPTS): Add gpgsm-gencert.sh + + * gpgsm-gencert.sh: Fixed copyright; its part of GnuPG thus FSF. + +2004-10-01 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c: Made all strings for --log-file read the same. + +2004-10-01 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c (my_dgettext): Also switch codeset and directory + for the other used domains (i.e. dirmngr). + + * gpgconf.c (main): Fixed translation markers. + +2004-09-30 Werner Koch <wk@g10code.com> + + * gpgconf.c (i18n_init): Always use LC_ALL. + + * Makefile.am: Adjusted for gettext 0.14. + +2004-09-29 Werner Koch <wk@g10code.com> + + * gpgconf-comp.c: Made the entries fro GROUPs translatable. + Include i18n.h. + (my_dgettext): Hack to use the gnupg2 domain. + +2004-08-09 Moritz Schulte <moritz@g10code.com> + + * gpgsm-gencert.sh: New file. + +2004-06-16 Werner Koch <wk@gnupg.org> + + * rfc822parse.c (rfc822parse_get_field): Add arg VALUEOFF. + +2004-06-14 Werner Koch <wk@gnupg.org> + + * no-libgcrypt.c (gcry_realloc, gcry_xmalloc, gcry_xcalloc): New. + + * gpgconf-comp.c (retrieve_options_from_program) + (retrieve_options_from_file, change_options_file) + (change_options_program, gc_component_change_options): Replaced + getline by read_line and test for allocation failure. + +2004-05-21 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (gc_options_dirmngr): Remove CRL group, put its + only option "max-replies" into LDAP group. + (gc_component): Change description of dirmngr to "Directory + Manager". + + * gpgconf-comp.c (gc_component_change_options): Move the + per-process backup file into a standard location. + +2004-05-03 Werner Koch <wk@gnupg.org> + + * gpgconf-comp.c: Add --allow-mark-trusted for the gpg-agent. + +2004-04-30 Werner Koch <wk@gnupg.org> + + * gpgconf-comp.c: Added more runtime flags for the gpg-agent + backend. + +2004-04-29 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (change_options_program): Turn on utf8-strings in + the gpgconf specific part of the config file for the GnuPG + backend. + +2004-04-28 Werner Koch <wk@gnupg.org> + + * gpgconf-comp.c: Add --ocsp-signer for the dirmngr backend. + +2004-04-20 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (gc_options_gpg_agent): Change type of + ignore-cache-for-signing option to GC_ARG_TYPE_NONE. + +2004-04-07 Werner Koch <wk@gnupg.org> + + * gpgconf-comp.c (my_dgettext): Switch the codeset once to utf-8. + Allow building with out NLS. + +2004-03-23 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (gc_options_dirmngr): Set GC_OPT_FLAG_ARG_OPT for + "LDAP Server". + (change_options_file): Remove assertion that tests that this flag + is not present. Handle an empty string in OPTION->new_value. + + * gpgconf.c (main): Remove obsolete warning. + +2004-03-23 Werner Koch <wk@gnupg.org> + + * gpgconf-comp.c (gc_options_gpg): New. + (gc_component_t, gc_component): Add GC_BACKEND_GPG. + (gc_options_dirmngr): Add allow-ocsp. + +2004-03-23 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (gc_flag): Add missing flags. + + * gpgconf-comp.c: Include <signal.h>. + (gc_backend): Add new member runtime_change. + (gpg_agent_runtime_change): New function. + (gc_component_change_options): New variable runtime. Initialize + it. If an option is changed that has the GC_OPT_FLAG_RUNTIME bit + set, also set the corresponding runtime variable. Finally, call + the runtime_change callback of the backend if needed. + +2004-03-16 Werner Koch <wk@gnupg.org> + + * gpgconf-comp.c (gc_options_gpg_agent): Implemented. + (gc_options_gpgsm, gc_options_scdaemon): Implemented. + (gc_backend_t): Add GC_BACKEND_SCDAEMON. + +2004-03-12 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (gc_component_change_options): Set the filenames + of the option's backend, not of the component. + Also use GC_BACKEND_NR, not GC_COMPONENT_NR. + +2004-03-09 Werner Koch <wk@gnupg.org> + + * gpgconf-comp.c [_riscos_]: Removed special code for RISC OS; we + don't want to clutter our code with system dependent stuff. + +2004-03-08 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (retrieve_options_from_file): Quote each string + in the list, not only the first. + +2004-02-26 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (gc_component_list_options): Do not print empty + groups. + + * gpgconf-comp.c (option_check_validity): Check if option is + active. + (change_options_file): Implement. + + * gpgconf-comp.c (retrieve_options_from_program): Remove broken + string handling. + + * gpgconf-comp.c (change_options_program): Support all types of + options, including list types. + + * README.gpgconf: Fix description of arguments. + * gpgconf-comp.c (option_check_validity): Rewritten to properly + support optional arguments in lists. + + * README.gpgconf: Add info about optional arg and arg type 0. + * gpgconf-comp.c (gc_component_change_options): Parse list of + arg type 0 options. + (option_check_validity): Add new argument NEW_VALUE_NR. Perform + rigorous validity checks. + (change_options_program): Disable an option also if we have a new + value for it. + +2004-02-25 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (gc_component_list_options): Correct output for + lists of arg type none. + (struct gc_option): Add new member new_flags. + (option_check_validity): Check OPTION->new_flags beside + OPTION->new_value. Add new argument FLAGS. + (gc_component_change_options): Support default flag correctly. + (change_options_program): Likewise. + +2004-02-24 Marcus Brinkmann <marcus@g10code.de> + + * README.gpgconf: Revert last change. Add new flags "default", + "default desc" and "no arg desc". Add new field ARGDEF. Add new + field FLAG to backend interface. + * gpgconf-comp.c (struct gc_option): Make flags of type unsigned + long. + (gc_component_list_options): Adjust type for flags. + Add default argument field. + (retrieve_options_from_program): Use "1" as value for non-option + arguments, not "Y". + (gc_component_change_options): Read in flags from input. + +2004-02-23 Marcus Brinkmann <marcus@g10code.de> + + * README.gpgconf: Change meaning of type 0 options value if it is + the empty string or "0". + + * gpgconf.h (struct): Add member runtime. + * gpgconf.c: Add new option oRuntime. + (main): Same here. + + * gpgconf-comp.c (hextobyte): New function. + (percent_deescape): New function. + (get_config_pathname): Percent deescape pathname if taken from + option (default) value. Use default value only if it exists and + is not empty. Use empty string otherwise. Don't include leading + quote in pathname. + (change_options_program): Percent deescape string before writing + it out. + + * gpgconf-comp.c (gc_component_list_options): Do not skip groups + on output. + +2004-02-18 Werner Koch <wk@gnupg.org> + + * gpgconf-comp.c: Added empty components for gpgsm and scdaemon. + +2004-02-12 Werner Koch <wk@gnupg.org> + + * watchgnupg.c (main): Implement option "--". + (print_version): New. + + * Makefile.am: Include cmacros.am for common flags. + +2004-02-03 Werner Koch <wk@gnupg.org> + + * addgnupghome: Try to use getent, so that it also works for NIS + setups. + +2004-01-31 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c: Some bug fixes, parse only defaults from the + program, and read the current values from the configuration file + directly. + +2004-01-30 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-comp.c (gc_error): New function, use it instead of + error() throughout. + + * gpgconf-comp.c: Use xmalloc, libcommon's asctimestamp and + gnupg_get_time, fix error() invocation and use getline() + consistently. + +2004-01-30 Werner Koch <wk@gnupg.org> + + * addgnupghome: Also set the group of copied files. + +2004-01-30 Werner Koch <wk@gnupg.org> + + * Makefile.am (sbin_SCRIPTS): New, to install addgnupghome. + (EXTRA_DIST): Added rfc822parse.c rfc822parse.h gpgparsemail.c + which might be useful for debugging. + +2004-01-29 Werner Koch <wk@gnupg.org> + + * addgnupghome: New. + +2004-01-29 Marcus Brinkmann <marcus@g10code.de> + + * gpgconf-list.c: File removed. + * README.gpgconf: New file. + * gpgconf-comp.c: New file. + * Makefile.am (gpgconf_SOURCES): Remove gpgconf-list.c, add + gpgconf-comp.c. + +2004-01-16 Werner Koch <wk@gnupg.org> + + * watchgnupg.c (main): Need to use FD_ISSET for the client + descriptors too; aiiih. Set the listening socket to non-blocking. + +2004-01-10 Werner Koch <wk@gnupg.org> + + * Makefile.am: Use GPG_ERROR_CFLAGS + +2004-01-05 Werner Koch <wk@gnupg.org> + + * Manifest: New. + * gpgconf.c, gpgconf.h, gpgconf-list.c: New. A skeleton for now. + * no-libgcrypt.c: New. + * Makefile.am: Add above. + +2003-12-23 Werner Koch <wk@gnupg.org> + + * Makefile.am: New. + * watchgnupg.c: New. + + + Copyright 2003, 2004, 2005, 2006, 2007, 2008, + 2009, 2010, 2011 Free Software Foundation, Inc. + + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +Local Variables: +buffer-read-only: t +End: diff --git a/tools/Makefile.am b/tools/Makefile.am new file mode 100644 index 0000000..acc649c --- /dev/null +++ b/tools/Makefile.am @@ -0,0 +1,198 @@ +# Makefile.am - Tools directory +# Copyright (C) 2003, 2007 Free Software Foundation, Inc. +# +# This file is part of GnuPG. +# +# GnuPG is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# GnuPG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses/>. + +EXTRA_DIST = \ + Manifest watchgnupg.c no-libgcrypt.c \ + addgnupghome applygnupgdefaults \ + lspgpot mail-signed-keys convert-from-106 sockprox.c \ + ccidmon.c ChangeLog-2011 \ + gpgconf-w32info.rc gpgconf.w32-manifest.in \ + gpgtar-w32info.rc gpgtar.w32-manifest.in \ + gpg-connect-agent-w32info.rc gpg-connect-agent.w32-manifest.in \ + gpg-check-pattern-w32info.rc gpg-check-pattern.w32-manifest.in \ + gpg-wks-client-w32info.rc gpg-wks-client.w32-manifest.in + + +AM_CPPFLAGS = +include $(top_srcdir)/am/cmacros.am + +if HAVE_W32_SYSTEM +gpgconf_robjs = $(resource_objs) gpgconf-w32info.o +gpgtar_robjs = $(resource_objs) gpgtar-w32info.o +gpg_connect_agent_robjs = $(resource_objs) gpg-connect-agent-w32info.o +gpg_check_pattern_robjs = $(resource_objs) gpg-check-pattern-w32info.o +gpg_wks_client_robjs = $(resource_objs) gpg-wks-client-w32info.o + +gpgconf-w32info.o: gpgconf.w32-manifest +gpgtar-w32info.o: gpgtar.w32-manifest +gpg-connect-agent-w32info.o: gpg-connect-agent.w32-manifest +gpg-check-pattern-w32info.o: gpg-check-pattern.w32-manifest +gpg-wks-client-w32info.o: gpg-wks-client.w32-manifest +else +gpg_connect_agent_robjs = +gpgconf_robjs = +gpg_check_pattern_robjs = +gpgtar_robjs = +gpg_wks_client_robjs = +endif + +AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(LIBASSUAN_CFLAGS) + +sbin_SCRIPTS = addgnupghome applygnupgdefaults + +if HAVE_USTAR +# bin_SCRIPTS += gpg-zip +noinst_SCRIPTS = gpg-zip +endif + +if BUILD_WKS_TOOLS + gpg_wks_server = gpg-wks-server +else + gpg_wks_server = +endif + +libexec_PROGRAMS = gpg-wks-client + +bin_PROGRAMS = gpgconf gpg-connect-agent +if !HAVE_W32_SYSTEM +bin_PROGRAMS += watchgnupg gpgparsemail ${gpg_wks_server} gpgsplit +else +bin_PROGRAMS += gpgconf-w32 +endif + +libexec_PROGRAMS += gpg-check-pattern + +if !HAVE_W32CE_SYSTEM +noinst_PROGRAMS = clean-sat make-dns-cert +endif + +if !HAVE_W32CE_SYSTEM +if BUILD_GPGTAR + bin_PROGRAMS += gpgtar +else + noinst_PROGRAMS += gpgtar +endif +endif + +common_libs = $(libcommon) +commonpth_libs = $(libcommonpth) + +# Some modules require PTH under W32CE. +if HAVE_W32CE_SYSTEM +maybe_commonpth_libs = $(commonpth_libs) +else +maybe_commonpth_libs = $(common_libs) +endif + +if HAVE_W32CE_SYSTEM +pwquery_libs = +else +pwquery_libs = ../common/libsimple-pwquery.a +endif + +if HAVE_W32CE_SYSTEM +opt_libassuan_libs = $(LIBASSUAN_LIBS) +endif + +regexp_libs = ../regexp/libregexp.a + + +gpgsplit_LDADD = $(common_libs) \ + $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ + $(ZLIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV) + +gpgconf_SOURCES = gpgconf.c gpgconf.h gpgconf-comp.c + +# common sucks in gpg-error, will they, nil they (some compilers +# do not eliminate the supposed-to-be-unused-inline-functions). +gpgconf_LDADD = $(maybe_commonpth_libs) $(opt_libassuan_libs) \ + $(LIBINTL) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(NETLIBS) \ + $(LIBICONV) $(W32SOCKLIBS) \ + $(gpgconf_robjs) +gpgconf_LDFLAGS = $(extra_bin_ldflags) + +gpgconf_w32_SOURCES = $(gpgconf_SOURCES) +gpgconf_w32_LDADD = $(gpgconf_LDADD) +gpgconf_w32_LDFLAGS = $(gpgconf_LDFLAGS) -Wl,-subsystem,windows + +gpgparsemail_SOURCES = gpgparsemail.c rfc822parse.c rfc822parse.h +gpgparsemail_LDADD = + +watchgnupg_SOURCES = watchgnupg.c +watchgnupg_LDADD = $(NETLIBS) + +gpg_connect_agent_SOURCES = gpg-connect-agent.c +gpg_connect_agent_LDADD = ../common/libgpgrl.a $(common_libs) \ + $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ + $(GPG_ERROR_LIBS) \ + $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ + $(gpg_connect_agent_robjs) + +gpg_check_pattern_SOURCES = gpg-check-pattern.c +gpg_check_pattern_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(INCICONV) +gpg_check_pattern_LDADD = $(common_libs) $(regexp_libs) $(LIBGCRYPT_LIBS) \ + $(GPG_ERROR_LIBS) \ + $(LIBINTL) $(NETLIBS) $(LIBICONV) $(W32SOCKLIBS) \ + $(LIBICONV) $(gpg_check_pattern_robjs) + +gpgtar_SOURCES = \ + gpgtar.c gpgtar.h \ + gpgtar-create.c \ + gpgtar-extract.c \ + gpgtar-list.c +gpgtar_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) +gpgtar_LDADD = $(libcommon) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBINTL) $(NETLIBS) $(LIBICONV) $(W32SOCKLIBS) \ + $(gpgtar_robjs) + +gpg_wks_server_SOURCES = \ + gpg-wks-server.c \ + gpg-wks.h \ + wks-util.c \ + wks-receive.c \ + rfc822parse.c rfc822parse.h \ + mime-parser.c mime-parser.h \ + mime-maker.c mime-maker.h \ + send-mail.c send-mail.h + +gpg_wks_server_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(INCICONV) +gpg_wks_server_LDADD = $(libcommon) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBINTL) $(LIBICONV) + +gpg_wks_client_SOURCES = \ + gpg-wks-client.c \ + gpg-wks.h \ + wks-util.c \ + wks-receive.c \ + rfc822parse.c rfc822parse.h \ + mime-parser.c mime-parser.h \ + mime-maker.h mime-maker.c \ + send-mail.c send-mail.h \ + call-dirmngr.c call-dirmngr.h + +gpg_wks_client_CFLAGS = $(LIBASSUAN_CFLAGS) $(LIBGCRYPT_CFLAGS) \ + $(GPG_ERROR_CFLAGS) $(INCICONV) +gpg_wks_client_LDADD = $(libcommon) \ + $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBINTL) $(LIBICONV) $(NETLIBS) \ + $(gpg_wks_client_robjs) + + +# Make sure that all libs are build before we use them. This is +# important for things like make -j2. +$(PROGRAMS): $(common_libs) $(pwquery_libs) ../common/libgpgrl.a diff --git a/tools/Makefile.in b/tools/Makefile.in new file mode 100644 index 0000000..e1b7da9 --- /dev/null +++ b/tools/Makefile.in @@ -0,0 +1,1589 @@ +# Makefile.in generated by automake 1.16.3 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2020 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# Makefile.am - Tools directory +# Copyright (C) 2003, 2007 Free Software Foundation, Inc. +# +# This file is part of GnuPG. +# +# GnuPG is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# GnuPG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses/>. + +# cmacros.am - C macro definitions +# Copyright (C) 2004 Free Software Foundation, Inc. +# +# This file is part of GnuPG. +# +# GnuPG is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# GnuPG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses/>. + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +@HAVE_DOSISH_SYSTEM_FALSE@am__append_1 = -DGNUPG_BINDIR="\"$(bindir)\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_LIBEXECDIR="\"$(libexecdir)\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_LIBDIR="\"$(libdir)/@PACKAGE@\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_DATADIR="\"$(datadir)/@PACKAGE@\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_SYSCONFDIR="\"$(sysconfdir)/@PACKAGE@\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_LOCALSTATEDIR="\"$(localstatedir)\"" + + +# If a specific protect tool program has been defined, pass its name +# to cc. Note that these macros should not be used directly but via +# the gnupg_module_name function. +@GNUPG_AGENT_PGM_TRUE@am__append_2 = -DGNUPG_DEFAULT_AGENT="\"@GNUPG_AGENT_PGM@\"" +@GNUPG_PINENTRY_PGM_TRUE@am__append_3 = -DGNUPG_DEFAULT_PINENTRY="\"@GNUPG_PINENTRY_PGM@\"" +@GNUPG_SCDAEMON_PGM_TRUE@am__append_4 = -DGNUPG_DEFAULT_SCDAEMON="\"@GNUPG_SCDAEMON_PGM@\"" +@GNUPG_DIRMNGR_PGM_TRUE@am__append_5 = -DGNUPG_DEFAULT_DIRMNGR="\"@GNUPG_DIRMNGR_PGM@\"" +@GNUPG_PROTECT_TOOL_PGM_TRUE@am__append_6 = -DGNUPG_DEFAULT_PROTECT_TOOL="\"@GNUPG_PROTECT_TOOL_PGM@\"" +@GNUPG_DIRMNGR_LDAP_PGM_TRUE@am__append_7 = -DGNUPG_DEFAULT_DIRMNGR_LDAP="\"@GNUPG_DIRMNGR_LDAP_PGM@\"" +libexec_PROGRAMS = gpg-wks-client$(EXEEXT) gpg-check-pattern$(EXEEXT) +bin_PROGRAMS = gpgconf$(EXEEXT) gpg-connect-agent$(EXEEXT) \ + $(am__EXEEXT_2) $(am__EXEEXT_3) $(am__EXEEXT_4) +@HAVE_W32_SYSTEM_FALSE@am__append_8 = watchgnupg gpgparsemail ${gpg_wks_server} gpgsplit +@HAVE_W32_SYSTEM_TRUE@am__append_9 = gpgconf-w32 +@HAVE_W32CE_SYSTEM_FALSE@noinst_PROGRAMS = clean-sat$(EXEEXT) \ +@HAVE_W32CE_SYSTEM_FALSE@ make-dns-cert$(EXEEXT) \ +@HAVE_W32CE_SYSTEM_FALSE@ $(am__EXEEXT_5) +@BUILD_GPGTAR_TRUE@@HAVE_W32CE_SYSTEM_FALSE@am__append_10 = gpgtar +@BUILD_GPGTAR_FALSE@@HAVE_W32CE_SYSTEM_FALSE@am__append_11 = gpgtar +subdir = tools +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/autobuild.m4 \ + $(top_srcdir)/m4/codeset.m4 $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/gpg-error.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/isc-posix.m4 $(top_srcdir)/m4/ksba.m4 \ + $(top_srcdir)/m4/lcmessage.m4 $(top_srcdir)/m4/ldap.m4 \ + $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \ + $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libassuan.m4 \ + $(top_srcdir)/m4/libgcrypt.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/npth.m4 $(top_srcdir)/m4/ntbtls.m4 \ + $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/m4/po.m4 \ + $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/m4/readline.m4 \ + $(top_srcdir)/m4/socklen.m4 $(top_srcdir)/m4/sys_socket_h.m4 \ + $(top_srcdir)/m4/tar-ustar.m4 $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = gpg-zip gpgconf.w32-manifest gpgtar.w32-manifest \ + gpg-connect-agent.w32-manifest gpg-check-pattern.w32-manifest \ + gpg-wks-client.w32-manifest +CONFIG_CLEAN_VPATH_FILES = +@BUILD_WKS_TOOLS_TRUE@am__EXEEXT_1 = gpg-wks-server$(EXEEXT) +@HAVE_W32_SYSTEM_FALSE@am__EXEEXT_2 = watchgnupg$(EXEEXT) \ +@HAVE_W32_SYSTEM_FALSE@ gpgparsemail$(EXEEXT) $(am__EXEEXT_1) \ +@HAVE_W32_SYSTEM_FALSE@ gpgsplit$(EXEEXT) +@HAVE_W32_SYSTEM_TRUE@am__EXEEXT_3 = gpgconf-w32$(EXEEXT) +@BUILD_GPGTAR_TRUE@@HAVE_W32CE_SYSTEM_FALSE@am__EXEEXT_4 = \ +@BUILD_GPGTAR_TRUE@@HAVE_W32CE_SYSTEM_FALSE@ gpgtar$(EXEEXT) +am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(libexecdir)" \ + "$(DESTDIR)$(sbindir)" +@BUILD_GPGTAR_FALSE@@HAVE_W32CE_SYSTEM_FALSE@am__EXEEXT_5 = \ +@BUILD_GPGTAR_FALSE@@HAVE_W32CE_SYSTEM_FALSE@ gpgtar$(EXEEXT) +PROGRAMS = $(bin_PROGRAMS) $(libexec_PROGRAMS) $(noinst_PROGRAMS) +clean_sat_SOURCES = clean-sat.c +clean_sat_OBJECTS = clean-sat.$(OBJEXT) +clean_sat_LDADD = $(LDADD) +am_gpg_check_pattern_OBJECTS = \ + gpg_check_pattern-gpg-check-pattern.$(OBJEXT) +gpg_check_pattern_OBJECTS = $(am_gpg_check_pattern_OBJECTS) +am__DEPENDENCIES_1 = +@HAVE_W32_SYSTEM_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) \ +@HAVE_W32_SYSTEM_TRUE@ gpg-check-pattern-w32info.o +gpg_check_pattern_DEPENDENCIES = $(common_libs) $(regexp_libs) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2) +gpg_check_pattern_LINK = $(CCLD) $(gpg_check_pattern_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +am_gpg_connect_agent_OBJECTS = gpg-connect-agent.$(OBJEXT) +gpg_connect_agent_OBJECTS = $(am_gpg_connect_agent_OBJECTS) +@HAVE_W32_SYSTEM_TRUE@am__DEPENDENCIES_3 = $(am__DEPENDENCIES_1) \ +@HAVE_W32_SYSTEM_TRUE@ gpg-connect-agent-w32info.o +gpg_connect_agent_DEPENDENCIES = ../common/libgpgrl.a $(common_libs) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_3) +am_gpg_wks_client_OBJECTS = gpg_wks_client-gpg-wks-client.$(OBJEXT) \ + gpg_wks_client-wks-util.$(OBJEXT) \ + gpg_wks_client-wks-receive.$(OBJEXT) \ + gpg_wks_client-rfc822parse.$(OBJEXT) \ + gpg_wks_client-mime-parser.$(OBJEXT) \ + gpg_wks_client-mime-maker.$(OBJEXT) \ + gpg_wks_client-send-mail.$(OBJEXT) \ + gpg_wks_client-call-dirmngr.$(OBJEXT) +gpg_wks_client_OBJECTS = $(am_gpg_wks_client_OBJECTS) +@HAVE_W32_SYSTEM_TRUE@am__DEPENDENCIES_4 = $(am__DEPENDENCIES_1) \ +@HAVE_W32_SYSTEM_TRUE@ gpg-wks-client-w32info.o +gpg_wks_client_DEPENDENCIES = $(libcommon) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_4) +gpg_wks_client_LINK = $(CCLD) $(gpg_wks_client_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +am_gpg_wks_server_OBJECTS = gpg_wks_server-gpg-wks-server.$(OBJEXT) \ + gpg_wks_server-wks-util.$(OBJEXT) \ + gpg_wks_server-wks-receive.$(OBJEXT) \ + gpg_wks_server-rfc822parse.$(OBJEXT) \ + gpg_wks_server-mime-parser.$(OBJEXT) \ + gpg_wks_server-mime-maker.$(OBJEXT) \ + gpg_wks_server-send-mail.$(OBJEXT) +gpg_wks_server_OBJECTS = $(am_gpg_wks_server_OBJECTS) +gpg_wks_server_DEPENDENCIES = $(libcommon) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +gpg_wks_server_LINK = $(CCLD) $(gpg_wks_server_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +am_gpgconf_OBJECTS = gpgconf.$(OBJEXT) gpgconf-comp.$(OBJEXT) +gpgconf_OBJECTS = $(am_gpgconf_OBJECTS) +@HAVE_W32CE_SYSTEM_TRUE@am__DEPENDENCIES_5 = $(am__DEPENDENCIES_1) +@HAVE_W32_SYSTEM_TRUE@am__DEPENDENCIES_6 = $(am__DEPENDENCIES_1) \ +@HAVE_W32_SYSTEM_TRUE@ gpgconf-w32info.o +gpgconf_DEPENDENCIES = $(maybe_commonpth_libs) $(am__DEPENDENCIES_5) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_6) +gpgconf_LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(gpgconf_LDFLAGS) \ + $(LDFLAGS) -o $@ +am__objects_1 = gpgconf.$(OBJEXT) gpgconf-comp.$(OBJEXT) +am_gpgconf_w32_OBJECTS = $(am__objects_1) +gpgconf_w32_OBJECTS = $(am_gpgconf_w32_OBJECTS) +am__DEPENDENCIES_7 = $(maybe_commonpth_libs) $(am__DEPENDENCIES_5) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_6) +gpgconf_w32_DEPENDENCIES = $(am__DEPENDENCIES_7) +gpgconf_w32_LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(gpgconf_w32_LDFLAGS) $(LDFLAGS) -o $@ +am_gpgparsemail_OBJECTS = gpgparsemail.$(OBJEXT) rfc822parse.$(OBJEXT) +gpgparsemail_OBJECTS = $(am_gpgparsemail_OBJECTS) +gpgparsemail_DEPENDENCIES = +gpgsplit_SOURCES = gpgsplit.c +gpgsplit_OBJECTS = gpgsplit.$(OBJEXT) +gpgsplit_DEPENDENCIES = $(common_libs) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +am_gpgtar_OBJECTS = gpgtar-gpgtar.$(OBJEXT) \ + gpgtar-gpgtar-create.$(OBJEXT) gpgtar-gpgtar-extract.$(OBJEXT) \ + gpgtar-gpgtar-list.$(OBJEXT) +gpgtar_OBJECTS = $(am_gpgtar_OBJECTS) +@HAVE_W32_SYSTEM_TRUE@am__DEPENDENCIES_8 = $(am__DEPENDENCIES_1) \ +@HAVE_W32_SYSTEM_TRUE@ gpgtar-w32info.o +gpgtar_DEPENDENCIES = $(libcommon) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_8) +gpgtar_LINK = $(CCLD) $(gpgtar_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ + $(LDFLAGS) -o $@ +make_dns_cert_SOURCES = make-dns-cert.c +make_dns_cert_OBJECTS = make-dns-cert.$(OBJEXT) +make_dns_cert_LDADD = $(LDADD) +am_watchgnupg_OBJECTS = watchgnupg.$(OBJEXT) +watchgnupg_OBJECTS = $(am_watchgnupg_OBJECTS) +watchgnupg_DEPENDENCIES = $(am__DEPENDENCIES_1) +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +SCRIPTS = $(noinst_SCRIPTS) $(sbin_SCRIPTS) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/clean-sat.Po \ + ./$(DEPDIR)/gpg-connect-agent.Po \ + ./$(DEPDIR)/gpg_check_pattern-gpg-check-pattern.Po \ + ./$(DEPDIR)/gpg_wks_client-call-dirmngr.Po \ + ./$(DEPDIR)/gpg_wks_client-gpg-wks-client.Po \ + ./$(DEPDIR)/gpg_wks_client-mime-maker.Po \ + ./$(DEPDIR)/gpg_wks_client-mime-parser.Po \ + ./$(DEPDIR)/gpg_wks_client-rfc822parse.Po \ + ./$(DEPDIR)/gpg_wks_client-send-mail.Po \ + ./$(DEPDIR)/gpg_wks_client-wks-receive.Po \ + ./$(DEPDIR)/gpg_wks_client-wks-util.Po \ + ./$(DEPDIR)/gpg_wks_server-gpg-wks-server.Po \ + ./$(DEPDIR)/gpg_wks_server-mime-maker.Po \ + ./$(DEPDIR)/gpg_wks_server-mime-parser.Po \ + ./$(DEPDIR)/gpg_wks_server-rfc822parse.Po \ + ./$(DEPDIR)/gpg_wks_server-send-mail.Po \ + ./$(DEPDIR)/gpg_wks_server-wks-receive.Po \ + ./$(DEPDIR)/gpg_wks_server-wks-util.Po \ + ./$(DEPDIR)/gpgconf-comp.Po ./$(DEPDIR)/gpgconf.Po \ + ./$(DEPDIR)/gpgparsemail.Po ./$(DEPDIR)/gpgsplit.Po \ + ./$(DEPDIR)/gpgtar-gpgtar-create.Po \ + ./$(DEPDIR)/gpgtar-gpgtar-extract.Po \ + ./$(DEPDIR)/gpgtar-gpgtar-list.Po ./$(DEPDIR)/gpgtar-gpgtar.Po \ + ./$(DEPDIR)/make-dns-cert.Po ./$(DEPDIR)/rfc822parse.Po \ + ./$(DEPDIR)/watchgnupg.Po +am__mv = mv -f +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = clean-sat.c $(gpg_check_pattern_SOURCES) \ + $(gpg_connect_agent_SOURCES) $(gpg_wks_client_SOURCES) \ + $(gpg_wks_server_SOURCES) $(gpgconf_SOURCES) \ + $(gpgconf_w32_SOURCES) $(gpgparsemail_SOURCES) gpgsplit.c \ + $(gpgtar_SOURCES) make-dns-cert.c $(watchgnupg_SOURCES) +DIST_SOURCES = clean-sat.c $(gpg_check_pattern_SOURCES) \ + $(gpg_connect_agent_SOURCES) $(gpg_wks_client_SOURCES) \ + $(gpg_wks_server_SOURCES) $(gpgconf_SOURCES) \ + $(gpgconf_w32_SOURCES) $(gpgparsemail_SOURCES) gpgsplit.c \ + $(gpgtar_SOURCES) make-dns-cert.c $(watchgnupg_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(srcdir)/gpg-check-pattern.w32-manifest.in \ + $(srcdir)/gpg-connect-agent.w32-manifest.in \ + $(srcdir)/gpg-wks-client.w32-manifest.in $(srcdir)/gpg-zip.in \ + $(srcdir)/gpgconf.w32-manifest.in \ + $(srcdir)/gpgtar.w32-manifest.in $(top_srcdir)/am/cmacros.am \ + $(top_srcdir)/build-aux/depcomp \ + $(top_srcdir)/build-aux/mkinstalldirs +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +AWK_HEX_NUMBER_OPTION = @AWK_HEX_NUMBER_OPTION@ +BUILD_FILEVERSION = @BUILD_FILEVERSION@ +BUILD_HOSTNAME = @BUILD_HOSTNAME@ +BUILD_INCLUDED_LIBINTL = @BUILD_INCLUDED_LIBINTL@ +BUILD_REVISION = @BUILD_REVISION@ +BUILD_TIMESTAMP = @BUILD_TIMESTAMP@ +BUILD_VERSION = @BUILD_VERSION@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CC_FOR_BUILD = @CC_FOR_BUILD@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DL_LIBS = @DL_LIBS@ +DNSLIBS = @DNSLIBS@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ENCFS = @ENCFS@ +EXEEXT = @EXEEXT@ +FUSERMOUNT = @FUSERMOUNT@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GNUPG_AGENT_PGM = @GNUPG_AGENT_PGM@ +GNUPG_DIRMNGR_LDAP_PGM = @GNUPG_DIRMNGR_LDAP_PGM@ +GNUPG_DIRMNGR_PGM = @GNUPG_DIRMNGR_PGM@ +GNUPG_PINENTRY_PGM = @GNUPG_PINENTRY_PGM@ +GNUPG_PROTECT_TOOL_PGM = @GNUPG_PROTECT_TOOL_PGM@ +GNUPG_SCDAEMON_PGM = @GNUPG_SCDAEMON_PGM@ +GPGKEYS_LDAP = @GPGKEYS_LDAP@ +GPGRT_CONFIG = @GPGRT_CONFIG@ +GPG_ERROR_CFLAGS = @GPG_ERROR_CFLAGS@ +GPG_ERROR_CONFIG = @GPG_ERROR_CONFIG@ +GPG_ERROR_LIBS = @GPG_ERROR_LIBS@ +GPG_ERROR_MT_CFLAGS = @GPG_ERROR_MT_CFLAGS@ +GPG_ERROR_MT_LIBS = @GPG_ERROR_MT_LIBS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +KSBA_CFLAGS = @KSBA_CFLAGS@ +KSBA_CONFIG = @KSBA_CONFIG@ +KSBA_LIBS = @KSBA_LIBS@ +LBER_LIBS = @LBER_LIBS@ +LDAPLIBS = @LDAPLIBS@ +LDAP_CPPFLAGS = @LDAP_CPPFLAGS@ +LDFLAGS = @LDFLAGS@ +LIBASSUAN_CFLAGS = @LIBASSUAN_CFLAGS@ +LIBASSUAN_CONFIG = @LIBASSUAN_CONFIG@ +LIBASSUAN_LIBS = @LIBASSUAN_LIBS@ +LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@ +LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@ +LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@ +LIBGNUTLS_CFLAGS = @LIBGNUTLS_CFLAGS@ +LIBGNUTLS_LIBS = @LIBGNUTLS_LIBS@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBOBJS = @LIBOBJS@ +LIBREADLINE = @LIBREADLINE@ +LIBS = @LIBS@ +LIBUSB_CPPFLAGS = @LIBUSB_CPPFLAGS@ +LIBUSB_LIBS = @LIBUSB_LIBS@ +LIBUTIL_LIBS = @LIBUTIL_LIBS@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NETLIBS = @NETLIBS@ +NPTH_CFLAGS = @NPTH_CFLAGS@ +NPTH_CONFIG = @NPTH_CONFIG@ +NPTH_LIBS = @NPTH_LIBS@ +NTBTLS_CFLAGS = @NTBTLS_CFLAGS@ +NTBTLS_CONFIG = @NTBTLS_CONFIG@ +NTBTLS_LIBS = @NTBTLS_LIBS@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_GT = @PACKAGE_GT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +POSUB = @POSUB@ +RANLIB = @RANLIB@ +SENDMAIL = @SENDMAIL@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SHRED = @SHRED@ +SQLITE3_CFLAGS = @SQLITE3_CFLAGS@ +SQLITE3_LIBS = @SQLITE3_LIBS@ +STRIP = @STRIP@ +SYSROOT = @SYSROOT@ +SYS_SOCKET_H = @SYS_SOCKET_H@ +TAR = @TAR@ +USE_C99_CFLAGS = @USE_C99_CFLAGS@ +USE_INCLUDED_LIBINTL = @USE_INCLUDED_LIBINTL@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +W32SOCKLIBS = @W32SOCKLIBS@ +WINDRES = @WINDRES@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +YAT2M = @YAT2M@ +ZLIBS = @ZLIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = $(datadir)/locale +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +EXTRA_DIST = \ + Manifest watchgnupg.c no-libgcrypt.c \ + addgnupghome applygnupgdefaults \ + lspgpot mail-signed-keys convert-from-106 sockprox.c \ + ccidmon.c ChangeLog-2011 \ + gpgconf-w32info.rc gpgconf.w32-manifest.in \ + gpgtar-w32info.rc gpgtar.w32-manifest.in \ + gpg-connect-agent-w32info.rc gpg-connect-agent.w32-manifest.in \ + gpg-check-pattern-w32info.rc gpg-check-pattern.w32-manifest.in \ + gpg-wks-client-w32info.rc gpg-wks-client.w32-manifest.in + + +# NB: AM_CFLAGS may also be used by tools running on the build +# platform to create source files. +AM_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\" $(am__append_1) \ + $(am__append_2) $(am__append_3) $(am__append_4) \ + $(am__append_5) $(am__append_6) $(am__append_7) +@HAVE_W32CE_SYSTEM_FALSE@extra_sys_libs = + +# Under Windows we use LockFileEx. WindowsCE provides this only on +# the WindowsMobile 6 platform and thus we need to use the coredll6 +# import library. We also want to use a stacksize of 256k instead of +# the 2MB which is the default with cegcc. 256k is the largest stack +# we use with pth. +@HAVE_W32CE_SYSTEM_TRUE@extra_sys_libs = -lcoredll6 +@HAVE_W32CE_SYSTEM_FALSE@extra_bin_ldflags = +@HAVE_W32CE_SYSTEM_TRUE@extra_bin_ldflags = -Wl,--stack=0x40000 +resource_objs = + +# Convenience macros +libcommon = ../common/libcommon.a +libcommonpth = ../common/libcommonpth.a +libcommontls = ../common/libcommontls.a +libcommontlsnpth = ../common/libcommontlsnpth.a +@HAVE_W32_SYSTEM_FALSE@gpgconf_robjs = +@HAVE_W32_SYSTEM_TRUE@gpgconf_robjs = $(resource_objs) gpgconf-w32info.o +@HAVE_W32_SYSTEM_FALSE@gpgtar_robjs = +@HAVE_W32_SYSTEM_TRUE@gpgtar_robjs = $(resource_objs) gpgtar-w32info.o +@HAVE_W32_SYSTEM_FALSE@gpg_connect_agent_robjs = +@HAVE_W32_SYSTEM_TRUE@gpg_connect_agent_robjs = $(resource_objs) gpg-connect-agent-w32info.o +@HAVE_W32_SYSTEM_FALSE@gpg_check_pattern_robjs = +@HAVE_W32_SYSTEM_TRUE@gpg_check_pattern_robjs = $(resource_objs) gpg-check-pattern-w32info.o +@HAVE_W32_SYSTEM_FALSE@gpg_wks_client_robjs = +@HAVE_W32_SYSTEM_TRUE@gpg_wks_client_robjs = $(resource_objs) gpg-wks-client-w32info.o +AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(LIBASSUAN_CFLAGS) +sbin_SCRIPTS = addgnupghome applygnupgdefaults + +# bin_SCRIPTS += gpg-zip +@HAVE_USTAR_TRUE@noinst_SCRIPTS = gpg-zip +@BUILD_WKS_TOOLS_FALSE@gpg_wks_server = +@BUILD_WKS_TOOLS_TRUE@gpg_wks_server = gpg-wks-server +common_libs = $(libcommon) +commonpth_libs = $(libcommonpth) +@HAVE_W32CE_SYSTEM_FALSE@maybe_commonpth_libs = $(common_libs) + +# Some modules require PTH under W32CE. +@HAVE_W32CE_SYSTEM_TRUE@maybe_commonpth_libs = $(commonpth_libs) +@HAVE_W32CE_SYSTEM_FALSE@pwquery_libs = ../common/libsimple-pwquery.a +@HAVE_W32CE_SYSTEM_TRUE@pwquery_libs = +@HAVE_W32CE_SYSTEM_TRUE@opt_libassuan_libs = $(LIBASSUAN_LIBS) +regexp_libs = ../regexp/libregexp.a +gpgsplit_LDADD = $(common_libs) \ + $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ + $(ZLIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV) + +gpgconf_SOURCES = gpgconf.c gpgconf.h gpgconf-comp.c + +# common sucks in gpg-error, will they, nil they (some compilers +# do not eliminate the supposed-to-be-unused-inline-functions). +gpgconf_LDADD = $(maybe_commonpth_libs) $(opt_libassuan_libs) \ + $(LIBINTL) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(NETLIBS) \ + $(LIBICONV) $(W32SOCKLIBS) \ + $(gpgconf_robjs) + +gpgconf_LDFLAGS = $(extra_bin_ldflags) +gpgconf_w32_SOURCES = $(gpgconf_SOURCES) +gpgconf_w32_LDADD = $(gpgconf_LDADD) +gpgconf_w32_LDFLAGS = $(gpgconf_LDFLAGS) -Wl,-subsystem,windows +gpgparsemail_SOURCES = gpgparsemail.c rfc822parse.c rfc822parse.h +gpgparsemail_LDADD = +watchgnupg_SOURCES = watchgnupg.c +watchgnupg_LDADD = $(NETLIBS) +gpg_connect_agent_SOURCES = gpg-connect-agent.c +gpg_connect_agent_LDADD = ../common/libgpgrl.a $(common_libs) \ + $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ + $(GPG_ERROR_LIBS) \ + $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ + $(gpg_connect_agent_robjs) + +gpg_check_pattern_SOURCES = gpg-check-pattern.c +gpg_check_pattern_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(INCICONV) +gpg_check_pattern_LDADD = $(common_libs) $(regexp_libs) $(LIBGCRYPT_LIBS) \ + $(GPG_ERROR_LIBS) \ + $(LIBINTL) $(NETLIBS) $(LIBICONV) $(W32SOCKLIBS) \ + $(LIBICONV) $(gpg_check_pattern_robjs) + +gpgtar_SOURCES = \ + gpgtar.c gpgtar.h \ + gpgtar-create.c \ + gpgtar-extract.c \ + gpgtar-list.c + +gpgtar_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) +gpgtar_LDADD = $(libcommon) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBINTL) $(NETLIBS) $(LIBICONV) $(W32SOCKLIBS) \ + $(gpgtar_robjs) + +gpg_wks_server_SOURCES = \ + gpg-wks-server.c \ + gpg-wks.h \ + wks-util.c \ + wks-receive.c \ + rfc822parse.c rfc822parse.h \ + mime-parser.c mime-parser.h \ + mime-maker.c mime-maker.h \ + send-mail.c send-mail.h + +gpg_wks_server_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(INCICONV) +gpg_wks_server_LDADD = $(libcommon) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBINTL) $(LIBICONV) + +gpg_wks_client_SOURCES = \ + gpg-wks-client.c \ + gpg-wks.h \ + wks-util.c \ + wks-receive.c \ + rfc822parse.c rfc822parse.h \ + mime-parser.c mime-parser.h \ + mime-maker.h mime-maker.c \ + send-mail.c send-mail.h \ + call-dirmngr.c call-dirmngr.h + +gpg_wks_client_CFLAGS = $(LIBASSUAN_CFLAGS) $(LIBGCRYPT_CFLAGS) \ + $(GPG_ERROR_CFLAGS) $(INCICONV) + +gpg_wks_client_LDADD = $(libcommon) \ + $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBINTL) $(LIBICONV) $(NETLIBS) \ + $(gpg_wks_client_robjs) + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj .rc +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/am/cmacros.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tools/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu tools/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; +$(top_srcdir)/am/cmacros.am $(am__empty): + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +gpg-zip: $(top_builddir)/config.status $(srcdir)/gpg-zip.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +gpgconf.w32-manifest: $(top_builddir)/config.status $(srcdir)/gpgconf.w32-manifest.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +gpgtar.w32-manifest: $(top_builddir)/config.status $(srcdir)/gpgtar.w32-manifest.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +gpg-connect-agent.w32-manifest: $(top_builddir)/config.status $(srcdir)/gpg-connect-agent.w32-manifest.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +gpg-check-pattern.w32-manifest: $(top_builddir)/config.status $(srcdir)/gpg-check-pattern.w32-manifest.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +gpg-wks-client.w32-manifest: $(top_builddir)/config.status $(srcdir)/gpg-wks-client.w32-manifest.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) +install-libexecPROGRAMS: $(libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(libexec_PROGRAMS)'; test -n "$(libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(libexec_PROGRAMS)'; test -n "$(libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(libexecdir)" && rm -f $$files + +clean-libexecPROGRAMS: + -test -z "$(libexec_PROGRAMS)" || rm -f $(libexec_PROGRAMS) + +clean-noinstPROGRAMS: + -test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS) + +clean-sat$(EXEEXT): $(clean_sat_OBJECTS) $(clean_sat_DEPENDENCIES) $(EXTRA_clean_sat_DEPENDENCIES) + @rm -f clean-sat$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(clean_sat_OBJECTS) $(clean_sat_LDADD) $(LIBS) + +gpg-check-pattern$(EXEEXT): $(gpg_check_pattern_OBJECTS) $(gpg_check_pattern_DEPENDENCIES) $(EXTRA_gpg_check_pattern_DEPENDENCIES) + @rm -f gpg-check-pattern$(EXEEXT) + $(AM_V_CCLD)$(gpg_check_pattern_LINK) $(gpg_check_pattern_OBJECTS) $(gpg_check_pattern_LDADD) $(LIBS) + +gpg-connect-agent$(EXEEXT): $(gpg_connect_agent_OBJECTS) $(gpg_connect_agent_DEPENDENCIES) $(EXTRA_gpg_connect_agent_DEPENDENCIES) + @rm -f gpg-connect-agent$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(gpg_connect_agent_OBJECTS) $(gpg_connect_agent_LDADD) $(LIBS) + +gpg-wks-client$(EXEEXT): $(gpg_wks_client_OBJECTS) $(gpg_wks_client_DEPENDENCIES) $(EXTRA_gpg_wks_client_DEPENDENCIES) + @rm -f gpg-wks-client$(EXEEXT) + $(AM_V_CCLD)$(gpg_wks_client_LINK) $(gpg_wks_client_OBJECTS) $(gpg_wks_client_LDADD) $(LIBS) + +gpg-wks-server$(EXEEXT): $(gpg_wks_server_OBJECTS) $(gpg_wks_server_DEPENDENCIES) $(EXTRA_gpg_wks_server_DEPENDENCIES) + @rm -f gpg-wks-server$(EXEEXT) + $(AM_V_CCLD)$(gpg_wks_server_LINK) $(gpg_wks_server_OBJECTS) $(gpg_wks_server_LDADD) $(LIBS) + +gpgconf$(EXEEXT): $(gpgconf_OBJECTS) $(gpgconf_DEPENDENCIES) $(EXTRA_gpgconf_DEPENDENCIES) + @rm -f gpgconf$(EXEEXT) + $(AM_V_CCLD)$(gpgconf_LINK) $(gpgconf_OBJECTS) $(gpgconf_LDADD) $(LIBS) + +gpgconf-w32$(EXEEXT): $(gpgconf_w32_OBJECTS) $(gpgconf_w32_DEPENDENCIES) $(EXTRA_gpgconf_w32_DEPENDENCIES) + @rm -f gpgconf-w32$(EXEEXT) + $(AM_V_CCLD)$(gpgconf_w32_LINK) $(gpgconf_w32_OBJECTS) $(gpgconf_w32_LDADD) $(LIBS) + +gpgparsemail$(EXEEXT): $(gpgparsemail_OBJECTS) $(gpgparsemail_DEPENDENCIES) $(EXTRA_gpgparsemail_DEPENDENCIES) + @rm -f gpgparsemail$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(gpgparsemail_OBJECTS) $(gpgparsemail_LDADD) $(LIBS) + +gpgsplit$(EXEEXT): $(gpgsplit_OBJECTS) $(gpgsplit_DEPENDENCIES) $(EXTRA_gpgsplit_DEPENDENCIES) + @rm -f gpgsplit$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(gpgsplit_OBJECTS) $(gpgsplit_LDADD) $(LIBS) + +gpgtar$(EXEEXT): $(gpgtar_OBJECTS) $(gpgtar_DEPENDENCIES) $(EXTRA_gpgtar_DEPENDENCIES) + @rm -f gpgtar$(EXEEXT) + $(AM_V_CCLD)$(gpgtar_LINK) $(gpgtar_OBJECTS) $(gpgtar_LDADD) $(LIBS) + +make-dns-cert$(EXEEXT): $(make_dns_cert_OBJECTS) $(make_dns_cert_DEPENDENCIES) $(EXTRA_make_dns_cert_DEPENDENCIES) + @rm -f make-dns-cert$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(make_dns_cert_OBJECTS) $(make_dns_cert_LDADD) $(LIBS) + +watchgnupg$(EXEEXT): $(watchgnupg_OBJECTS) $(watchgnupg_DEPENDENCIES) $(EXTRA_watchgnupg_DEPENDENCIES) + @rm -f watchgnupg$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(watchgnupg_OBJECTS) $(watchgnupg_LDADD) $(LIBS) +install-sbinSCRIPTS: $(sbin_SCRIPTS) + @$(NORMAL_INSTALL) + @list='$(sbin_SCRIPTS)'; test -n "$(sbindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(sbindir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n' \ + -e 'h;s|.*|.|' \ + -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) { files[d] = files[d] " " $$1; \ + if (++n[d] == $(am__install_max)) { \ + print "f", d, files[d]; n[d] = 0; files[d] = "" } } \ + else { print "f", d "/" $$4, $$1 } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(sbindir)$$dir'"; \ + $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-sbinSCRIPTS: + @$(NORMAL_UNINSTALL) + @list='$(sbin_SCRIPTS)'; test -n "$(sbindir)" || exit 0; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 's,.*/,,;$(transform)'`; \ + dir='$(DESTDIR)$(sbindir)'; $(am__uninstall_files_from_dir) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/clean-sat.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg-connect-agent.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_check_pattern-gpg-check-pattern.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_wks_client-call-dirmngr.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_wks_client-gpg-wks-client.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_wks_client-mime-maker.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_wks_client-mime-parser.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_wks_client-rfc822parse.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_wks_client-send-mail.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_wks_client-wks-receive.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_wks_client-wks-util.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_wks_server-gpg-wks-server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_wks_server-mime-maker.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_wks_server-mime-parser.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_wks_server-rfc822parse.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_wks_server-send-mail.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_wks_server-wks-receive.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpg_wks_server-wks-util.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpgconf-comp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpgconf.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpgparsemail.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpgsplit.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpgtar-gpgtar-create.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpgtar-gpgtar-extract.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpgtar-gpgtar-list.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gpgtar-gpgtar.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/make-dns-cert.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rfc822parse.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/watchgnupg.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +gpg_check_pattern-gpg-check-pattern.o: gpg-check-pattern.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_check_pattern_CFLAGS) $(CFLAGS) -MT gpg_check_pattern-gpg-check-pattern.o -MD -MP -MF $(DEPDIR)/gpg_check_pattern-gpg-check-pattern.Tpo -c -o gpg_check_pattern-gpg-check-pattern.o `test -f 'gpg-check-pattern.c' || echo '$(srcdir)/'`gpg-check-pattern.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_check_pattern-gpg-check-pattern.Tpo $(DEPDIR)/gpg_check_pattern-gpg-check-pattern.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gpg-check-pattern.c' object='gpg_check_pattern-gpg-check-pattern.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_check_pattern_CFLAGS) $(CFLAGS) -c -o gpg_check_pattern-gpg-check-pattern.o `test -f 'gpg-check-pattern.c' || echo '$(srcdir)/'`gpg-check-pattern.c + +gpg_check_pattern-gpg-check-pattern.obj: gpg-check-pattern.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_check_pattern_CFLAGS) $(CFLAGS) -MT gpg_check_pattern-gpg-check-pattern.obj -MD -MP -MF $(DEPDIR)/gpg_check_pattern-gpg-check-pattern.Tpo -c -o gpg_check_pattern-gpg-check-pattern.obj `if test -f 'gpg-check-pattern.c'; then $(CYGPATH_W) 'gpg-check-pattern.c'; else $(CYGPATH_W) '$(srcdir)/gpg-check-pattern.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_check_pattern-gpg-check-pattern.Tpo $(DEPDIR)/gpg_check_pattern-gpg-check-pattern.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gpg-check-pattern.c' object='gpg_check_pattern-gpg-check-pattern.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_check_pattern_CFLAGS) $(CFLAGS) -c -o gpg_check_pattern-gpg-check-pattern.obj `if test -f 'gpg-check-pattern.c'; then $(CYGPATH_W) 'gpg-check-pattern.c'; else $(CYGPATH_W) '$(srcdir)/gpg-check-pattern.c'; fi` + +gpg_wks_client-gpg-wks-client.o: gpg-wks-client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -MT gpg_wks_client-gpg-wks-client.o -MD -MP -MF $(DEPDIR)/gpg_wks_client-gpg-wks-client.Tpo -c -o gpg_wks_client-gpg-wks-client.o `test -f 'gpg-wks-client.c' || echo '$(srcdir)/'`gpg-wks-client.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_client-gpg-wks-client.Tpo $(DEPDIR)/gpg_wks_client-gpg-wks-client.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gpg-wks-client.c' object='gpg_wks_client-gpg-wks-client.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -c -o gpg_wks_client-gpg-wks-client.o `test -f 'gpg-wks-client.c' || echo '$(srcdir)/'`gpg-wks-client.c + +gpg_wks_client-gpg-wks-client.obj: gpg-wks-client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -MT gpg_wks_client-gpg-wks-client.obj -MD -MP -MF $(DEPDIR)/gpg_wks_client-gpg-wks-client.Tpo -c -o gpg_wks_client-gpg-wks-client.obj `if test -f 'gpg-wks-client.c'; then $(CYGPATH_W) 'gpg-wks-client.c'; else $(CYGPATH_W) '$(srcdir)/gpg-wks-client.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_client-gpg-wks-client.Tpo $(DEPDIR)/gpg_wks_client-gpg-wks-client.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gpg-wks-client.c' object='gpg_wks_client-gpg-wks-client.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -c -o gpg_wks_client-gpg-wks-client.obj `if test -f 'gpg-wks-client.c'; then $(CYGPATH_W) 'gpg-wks-client.c'; else $(CYGPATH_W) '$(srcdir)/gpg-wks-client.c'; fi` + +gpg_wks_client-wks-util.o: wks-util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -MT gpg_wks_client-wks-util.o -MD -MP -MF $(DEPDIR)/gpg_wks_client-wks-util.Tpo -c -o gpg_wks_client-wks-util.o `test -f 'wks-util.c' || echo '$(srcdir)/'`wks-util.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_client-wks-util.Tpo $(DEPDIR)/gpg_wks_client-wks-util.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='wks-util.c' object='gpg_wks_client-wks-util.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -c -o gpg_wks_client-wks-util.o `test -f 'wks-util.c' || echo '$(srcdir)/'`wks-util.c + +gpg_wks_client-wks-util.obj: wks-util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -MT gpg_wks_client-wks-util.obj -MD -MP -MF $(DEPDIR)/gpg_wks_client-wks-util.Tpo -c -o gpg_wks_client-wks-util.obj `if test -f 'wks-util.c'; then $(CYGPATH_W) 'wks-util.c'; else $(CYGPATH_W) '$(srcdir)/wks-util.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_client-wks-util.Tpo $(DEPDIR)/gpg_wks_client-wks-util.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='wks-util.c' object='gpg_wks_client-wks-util.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -c -o gpg_wks_client-wks-util.obj `if test -f 'wks-util.c'; then $(CYGPATH_W) 'wks-util.c'; else $(CYGPATH_W) '$(srcdir)/wks-util.c'; fi` + +gpg_wks_client-wks-receive.o: wks-receive.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -MT gpg_wks_client-wks-receive.o -MD -MP -MF $(DEPDIR)/gpg_wks_client-wks-receive.Tpo -c -o gpg_wks_client-wks-receive.o `test -f 'wks-receive.c' || echo '$(srcdir)/'`wks-receive.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_client-wks-receive.Tpo $(DEPDIR)/gpg_wks_client-wks-receive.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='wks-receive.c' object='gpg_wks_client-wks-receive.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -c -o gpg_wks_client-wks-receive.o `test -f 'wks-receive.c' || echo '$(srcdir)/'`wks-receive.c + +gpg_wks_client-wks-receive.obj: wks-receive.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -MT gpg_wks_client-wks-receive.obj -MD -MP -MF $(DEPDIR)/gpg_wks_client-wks-receive.Tpo -c -o gpg_wks_client-wks-receive.obj `if test -f 'wks-receive.c'; then $(CYGPATH_W) 'wks-receive.c'; else $(CYGPATH_W) '$(srcdir)/wks-receive.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_client-wks-receive.Tpo $(DEPDIR)/gpg_wks_client-wks-receive.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='wks-receive.c' object='gpg_wks_client-wks-receive.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -c -o gpg_wks_client-wks-receive.obj `if test -f 'wks-receive.c'; then $(CYGPATH_W) 'wks-receive.c'; else $(CYGPATH_W) '$(srcdir)/wks-receive.c'; fi` + +gpg_wks_client-rfc822parse.o: rfc822parse.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -MT gpg_wks_client-rfc822parse.o -MD -MP -MF $(DEPDIR)/gpg_wks_client-rfc822parse.Tpo -c -o gpg_wks_client-rfc822parse.o `test -f 'rfc822parse.c' || echo '$(srcdir)/'`rfc822parse.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_client-rfc822parse.Tpo $(DEPDIR)/gpg_wks_client-rfc822parse.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rfc822parse.c' object='gpg_wks_client-rfc822parse.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -c -o gpg_wks_client-rfc822parse.o `test -f 'rfc822parse.c' || echo '$(srcdir)/'`rfc822parse.c + +gpg_wks_client-rfc822parse.obj: rfc822parse.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -MT gpg_wks_client-rfc822parse.obj -MD -MP -MF $(DEPDIR)/gpg_wks_client-rfc822parse.Tpo -c -o gpg_wks_client-rfc822parse.obj `if test -f 'rfc822parse.c'; then $(CYGPATH_W) 'rfc822parse.c'; else $(CYGPATH_W) '$(srcdir)/rfc822parse.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_client-rfc822parse.Tpo $(DEPDIR)/gpg_wks_client-rfc822parse.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rfc822parse.c' object='gpg_wks_client-rfc822parse.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -c -o gpg_wks_client-rfc822parse.obj `if test -f 'rfc822parse.c'; then $(CYGPATH_W) 'rfc822parse.c'; else $(CYGPATH_W) '$(srcdir)/rfc822parse.c'; fi` + +gpg_wks_client-mime-parser.o: mime-parser.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -MT gpg_wks_client-mime-parser.o -MD -MP -MF $(DEPDIR)/gpg_wks_client-mime-parser.Tpo -c -o gpg_wks_client-mime-parser.o `test -f 'mime-parser.c' || echo '$(srcdir)/'`mime-parser.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_client-mime-parser.Tpo $(DEPDIR)/gpg_wks_client-mime-parser.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mime-parser.c' object='gpg_wks_client-mime-parser.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -c -o gpg_wks_client-mime-parser.o `test -f 'mime-parser.c' || echo '$(srcdir)/'`mime-parser.c + +gpg_wks_client-mime-parser.obj: mime-parser.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -MT gpg_wks_client-mime-parser.obj -MD -MP -MF $(DEPDIR)/gpg_wks_client-mime-parser.Tpo -c -o gpg_wks_client-mime-parser.obj `if test -f 'mime-parser.c'; then $(CYGPATH_W) 'mime-parser.c'; else $(CYGPATH_W) '$(srcdir)/mime-parser.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_client-mime-parser.Tpo $(DEPDIR)/gpg_wks_client-mime-parser.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mime-parser.c' object='gpg_wks_client-mime-parser.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -c -o gpg_wks_client-mime-parser.obj `if test -f 'mime-parser.c'; then $(CYGPATH_W) 'mime-parser.c'; else $(CYGPATH_W) '$(srcdir)/mime-parser.c'; fi` + +gpg_wks_client-mime-maker.o: mime-maker.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -MT gpg_wks_client-mime-maker.o -MD -MP -MF $(DEPDIR)/gpg_wks_client-mime-maker.Tpo -c -o gpg_wks_client-mime-maker.o `test -f 'mime-maker.c' || echo '$(srcdir)/'`mime-maker.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_client-mime-maker.Tpo $(DEPDIR)/gpg_wks_client-mime-maker.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mime-maker.c' object='gpg_wks_client-mime-maker.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -c -o gpg_wks_client-mime-maker.o `test -f 'mime-maker.c' || echo '$(srcdir)/'`mime-maker.c + +gpg_wks_client-mime-maker.obj: mime-maker.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -MT gpg_wks_client-mime-maker.obj -MD -MP -MF $(DEPDIR)/gpg_wks_client-mime-maker.Tpo -c -o gpg_wks_client-mime-maker.obj `if test -f 'mime-maker.c'; then $(CYGPATH_W) 'mime-maker.c'; else $(CYGPATH_W) '$(srcdir)/mime-maker.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_client-mime-maker.Tpo $(DEPDIR)/gpg_wks_client-mime-maker.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mime-maker.c' object='gpg_wks_client-mime-maker.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -c -o gpg_wks_client-mime-maker.obj `if test -f 'mime-maker.c'; then $(CYGPATH_W) 'mime-maker.c'; else $(CYGPATH_W) '$(srcdir)/mime-maker.c'; fi` + +gpg_wks_client-send-mail.o: send-mail.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -MT gpg_wks_client-send-mail.o -MD -MP -MF $(DEPDIR)/gpg_wks_client-send-mail.Tpo -c -o gpg_wks_client-send-mail.o `test -f 'send-mail.c' || echo '$(srcdir)/'`send-mail.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_client-send-mail.Tpo $(DEPDIR)/gpg_wks_client-send-mail.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='send-mail.c' object='gpg_wks_client-send-mail.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -c -o gpg_wks_client-send-mail.o `test -f 'send-mail.c' || echo '$(srcdir)/'`send-mail.c + +gpg_wks_client-send-mail.obj: send-mail.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -MT gpg_wks_client-send-mail.obj -MD -MP -MF $(DEPDIR)/gpg_wks_client-send-mail.Tpo -c -o gpg_wks_client-send-mail.obj `if test -f 'send-mail.c'; then $(CYGPATH_W) 'send-mail.c'; else $(CYGPATH_W) '$(srcdir)/send-mail.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_client-send-mail.Tpo $(DEPDIR)/gpg_wks_client-send-mail.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='send-mail.c' object='gpg_wks_client-send-mail.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -c -o gpg_wks_client-send-mail.obj `if test -f 'send-mail.c'; then $(CYGPATH_W) 'send-mail.c'; else $(CYGPATH_W) '$(srcdir)/send-mail.c'; fi` + +gpg_wks_client-call-dirmngr.o: call-dirmngr.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -MT gpg_wks_client-call-dirmngr.o -MD -MP -MF $(DEPDIR)/gpg_wks_client-call-dirmngr.Tpo -c -o gpg_wks_client-call-dirmngr.o `test -f 'call-dirmngr.c' || echo '$(srcdir)/'`call-dirmngr.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_client-call-dirmngr.Tpo $(DEPDIR)/gpg_wks_client-call-dirmngr.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='call-dirmngr.c' object='gpg_wks_client-call-dirmngr.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -c -o gpg_wks_client-call-dirmngr.o `test -f 'call-dirmngr.c' || echo '$(srcdir)/'`call-dirmngr.c + +gpg_wks_client-call-dirmngr.obj: call-dirmngr.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -MT gpg_wks_client-call-dirmngr.obj -MD -MP -MF $(DEPDIR)/gpg_wks_client-call-dirmngr.Tpo -c -o gpg_wks_client-call-dirmngr.obj `if test -f 'call-dirmngr.c'; then $(CYGPATH_W) 'call-dirmngr.c'; else $(CYGPATH_W) '$(srcdir)/call-dirmngr.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_client-call-dirmngr.Tpo $(DEPDIR)/gpg_wks_client-call-dirmngr.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='call-dirmngr.c' object='gpg_wks_client-call-dirmngr.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_client_CFLAGS) $(CFLAGS) -c -o gpg_wks_client-call-dirmngr.obj `if test -f 'call-dirmngr.c'; then $(CYGPATH_W) 'call-dirmngr.c'; else $(CYGPATH_W) '$(srcdir)/call-dirmngr.c'; fi` + +gpg_wks_server-gpg-wks-server.o: gpg-wks-server.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -MT gpg_wks_server-gpg-wks-server.o -MD -MP -MF $(DEPDIR)/gpg_wks_server-gpg-wks-server.Tpo -c -o gpg_wks_server-gpg-wks-server.o `test -f 'gpg-wks-server.c' || echo '$(srcdir)/'`gpg-wks-server.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_server-gpg-wks-server.Tpo $(DEPDIR)/gpg_wks_server-gpg-wks-server.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gpg-wks-server.c' object='gpg_wks_server-gpg-wks-server.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -c -o gpg_wks_server-gpg-wks-server.o `test -f 'gpg-wks-server.c' || echo '$(srcdir)/'`gpg-wks-server.c + +gpg_wks_server-gpg-wks-server.obj: gpg-wks-server.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -MT gpg_wks_server-gpg-wks-server.obj -MD -MP -MF $(DEPDIR)/gpg_wks_server-gpg-wks-server.Tpo -c -o gpg_wks_server-gpg-wks-server.obj `if test -f 'gpg-wks-server.c'; then $(CYGPATH_W) 'gpg-wks-server.c'; else $(CYGPATH_W) '$(srcdir)/gpg-wks-server.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_server-gpg-wks-server.Tpo $(DEPDIR)/gpg_wks_server-gpg-wks-server.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gpg-wks-server.c' object='gpg_wks_server-gpg-wks-server.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -c -o gpg_wks_server-gpg-wks-server.obj `if test -f 'gpg-wks-server.c'; then $(CYGPATH_W) 'gpg-wks-server.c'; else $(CYGPATH_W) '$(srcdir)/gpg-wks-server.c'; fi` + +gpg_wks_server-wks-util.o: wks-util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -MT gpg_wks_server-wks-util.o -MD -MP -MF $(DEPDIR)/gpg_wks_server-wks-util.Tpo -c -o gpg_wks_server-wks-util.o `test -f 'wks-util.c' || echo '$(srcdir)/'`wks-util.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_server-wks-util.Tpo $(DEPDIR)/gpg_wks_server-wks-util.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='wks-util.c' object='gpg_wks_server-wks-util.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -c -o gpg_wks_server-wks-util.o `test -f 'wks-util.c' || echo '$(srcdir)/'`wks-util.c + +gpg_wks_server-wks-util.obj: wks-util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -MT gpg_wks_server-wks-util.obj -MD -MP -MF $(DEPDIR)/gpg_wks_server-wks-util.Tpo -c -o gpg_wks_server-wks-util.obj `if test -f 'wks-util.c'; then $(CYGPATH_W) 'wks-util.c'; else $(CYGPATH_W) '$(srcdir)/wks-util.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_server-wks-util.Tpo $(DEPDIR)/gpg_wks_server-wks-util.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='wks-util.c' object='gpg_wks_server-wks-util.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -c -o gpg_wks_server-wks-util.obj `if test -f 'wks-util.c'; then $(CYGPATH_W) 'wks-util.c'; else $(CYGPATH_W) '$(srcdir)/wks-util.c'; fi` + +gpg_wks_server-wks-receive.o: wks-receive.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -MT gpg_wks_server-wks-receive.o -MD -MP -MF $(DEPDIR)/gpg_wks_server-wks-receive.Tpo -c -o gpg_wks_server-wks-receive.o `test -f 'wks-receive.c' || echo '$(srcdir)/'`wks-receive.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_server-wks-receive.Tpo $(DEPDIR)/gpg_wks_server-wks-receive.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='wks-receive.c' object='gpg_wks_server-wks-receive.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -c -o gpg_wks_server-wks-receive.o `test -f 'wks-receive.c' || echo '$(srcdir)/'`wks-receive.c + +gpg_wks_server-wks-receive.obj: wks-receive.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -MT gpg_wks_server-wks-receive.obj -MD -MP -MF $(DEPDIR)/gpg_wks_server-wks-receive.Tpo -c -o gpg_wks_server-wks-receive.obj `if test -f 'wks-receive.c'; then $(CYGPATH_W) 'wks-receive.c'; else $(CYGPATH_W) '$(srcdir)/wks-receive.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_server-wks-receive.Tpo $(DEPDIR)/gpg_wks_server-wks-receive.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='wks-receive.c' object='gpg_wks_server-wks-receive.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -c -o gpg_wks_server-wks-receive.obj `if test -f 'wks-receive.c'; then $(CYGPATH_W) 'wks-receive.c'; else $(CYGPATH_W) '$(srcdir)/wks-receive.c'; fi` + +gpg_wks_server-rfc822parse.o: rfc822parse.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -MT gpg_wks_server-rfc822parse.o -MD -MP -MF $(DEPDIR)/gpg_wks_server-rfc822parse.Tpo -c -o gpg_wks_server-rfc822parse.o `test -f 'rfc822parse.c' || echo '$(srcdir)/'`rfc822parse.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_server-rfc822parse.Tpo $(DEPDIR)/gpg_wks_server-rfc822parse.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rfc822parse.c' object='gpg_wks_server-rfc822parse.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -c -o gpg_wks_server-rfc822parse.o `test -f 'rfc822parse.c' || echo '$(srcdir)/'`rfc822parse.c + +gpg_wks_server-rfc822parse.obj: rfc822parse.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -MT gpg_wks_server-rfc822parse.obj -MD -MP -MF $(DEPDIR)/gpg_wks_server-rfc822parse.Tpo -c -o gpg_wks_server-rfc822parse.obj `if test -f 'rfc822parse.c'; then $(CYGPATH_W) 'rfc822parse.c'; else $(CYGPATH_W) '$(srcdir)/rfc822parse.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_server-rfc822parse.Tpo $(DEPDIR)/gpg_wks_server-rfc822parse.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rfc822parse.c' object='gpg_wks_server-rfc822parse.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -c -o gpg_wks_server-rfc822parse.obj `if test -f 'rfc822parse.c'; then $(CYGPATH_W) 'rfc822parse.c'; else $(CYGPATH_W) '$(srcdir)/rfc822parse.c'; fi` + +gpg_wks_server-mime-parser.o: mime-parser.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -MT gpg_wks_server-mime-parser.o -MD -MP -MF $(DEPDIR)/gpg_wks_server-mime-parser.Tpo -c -o gpg_wks_server-mime-parser.o `test -f 'mime-parser.c' || echo '$(srcdir)/'`mime-parser.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_server-mime-parser.Tpo $(DEPDIR)/gpg_wks_server-mime-parser.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mime-parser.c' object='gpg_wks_server-mime-parser.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -c -o gpg_wks_server-mime-parser.o `test -f 'mime-parser.c' || echo '$(srcdir)/'`mime-parser.c + +gpg_wks_server-mime-parser.obj: mime-parser.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -MT gpg_wks_server-mime-parser.obj -MD -MP -MF $(DEPDIR)/gpg_wks_server-mime-parser.Tpo -c -o gpg_wks_server-mime-parser.obj `if test -f 'mime-parser.c'; then $(CYGPATH_W) 'mime-parser.c'; else $(CYGPATH_W) '$(srcdir)/mime-parser.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_server-mime-parser.Tpo $(DEPDIR)/gpg_wks_server-mime-parser.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mime-parser.c' object='gpg_wks_server-mime-parser.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -c -o gpg_wks_server-mime-parser.obj `if test -f 'mime-parser.c'; then $(CYGPATH_W) 'mime-parser.c'; else $(CYGPATH_W) '$(srcdir)/mime-parser.c'; fi` + +gpg_wks_server-mime-maker.o: mime-maker.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -MT gpg_wks_server-mime-maker.o -MD -MP -MF $(DEPDIR)/gpg_wks_server-mime-maker.Tpo -c -o gpg_wks_server-mime-maker.o `test -f 'mime-maker.c' || echo '$(srcdir)/'`mime-maker.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_server-mime-maker.Tpo $(DEPDIR)/gpg_wks_server-mime-maker.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mime-maker.c' object='gpg_wks_server-mime-maker.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -c -o gpg_wks_server-mime-maker.o `test -f 'mime-maker.c' || echo '$(srcdir)/'`mime-maker.c + +gpg_wks_server-mime-maker.obj: mime-maker.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -MT gpg_wks_server-mime-maker.obj -MD -MP -MF $(DEPDIR)/gpg_wks_server-mime-maker.Tpo -c -o gpg_wks_server-mime-maker.obj `if test -f 'mime-maker.c'; then $(CYGPATH_W) 'mime-maker.c'; else $(CYGPATH_W) '$(srcdir)/mime-maker.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_server-mime-maker.Tpo $(DEPDIR)/gpg_wks_server-mime-maker.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mime-maker.c' object='gpg_wks_server-mime-maker.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -c -o gpg_wks_server-mime-maker.obj `if test -f 'mime-maker.c'; then $(CYGPATH_W) 'mime-maker.c'; else $(CYGPATH_W) '$(srcdir)/mime-maker.c'; fi` + +gpg_wks_server-send-mail.o: send-mail.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -MT gpg_wks_server-send-mail.o -MD -MP -MF $(DEPDIR)/gpg_wks_server-send-mail.Tpo -c -o gpg_wks_server-send-mail.o `test -f 'send-mail.c' || echo '$(srcdir)/'`send-mail.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_server-send-mail.Tpo $(DEPDIR)/gpg_wks_server-send-mail.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='send-mail.c' object='gpg_wks_server-send-mail.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -c -o gpg_wks_server-send-mail.o `test -f 'send-mail.c' || echo '$(srcdir)/'`send-mail.c + +gpg_wks_server-send-mail.obj: send-mail.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -MT gpg_wks_server-send-mail.obj -MD -MP -MF $(DEPDIR)/gpg_wks_server-send-mail.Tpo -c -o gpg_wks_server-send-mail.obj `if test -f 'send-mail.c'; then $(CYGPATH_W) 'send-mail.c'; else $(CYGPATH_W) '$(srcdir)/send-mail.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpg_wks_server-send-mail.Tpo $(DEPDIR)/gpg_wks_server-send-mail.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='send-mail.c' object='gpg_wks_server-send-mail.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpg_wks_server_CFLAGS) $(CFLAGS) -c -o gpg_wks_server-send-mail.obj `if test -f 'send-mail.c'; then $(CYGPATH_W) 'send-mail.c'; else $(CYGPATH_W) '$(srcdir)/send-mail.c'; fi` + +gpgtar-gpgtar.o: gpgtar.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpgtar_CFLAGS) $(CFLAGS) -MT gpgtar-gpgtar.o -MD -MP -MF $(DEPDIR)/gpgtar-gpgtar.Tpo -c -o gpgtar-gpgtar.o `test -f 'gpgtar.c' || echo '$(srcdir)/'`gpgtar.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpgtar-gpgtar.Tpo $(DEPDIR)/gpgtar-gpgtar.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gpgtar.c' object='gpgtar-gpgtar.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpgtar_CFLAGS) $(CFLAGS) -c -o gpgtar-gpgtar.o `test -f 'gpgtar.c' || echo '$(srcdir)/'`gpgtar.c + +gpgtar-gpgtar.obj: gpgtar.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpgtar_CFLAGS) $(CFLAGS) -MT gpgtar-gpgtar.obj -MD -MP -MF $(DEPDIR)/gpgtar-gpgtar.Tpo -c -o gpgtar-gpgtar.obj `if test -f 'gpgtar.c'; then $(CYGPATH_W) 'gpgtar.c'; else $(CYGPATH_W) '$(srcdir)/gpgtar.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpgtar-gpgtar.Tpo $(DEPDIR)/gpgtar-gpgtar.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gpgtar.c' object='gpgtar-gpgtar.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpgtar_CFLAGS) $(CFLAGS) -c -o gpgtar-gpgtar.obj `if test -f 'gpgtar.c'; then $(CYGPATH_W) 'gpgtar.c'; else $(CYGPATH_W) '$(srcdir)/gpgtar.c'; fi` + +gpgtar-gpgtar-create.o: gpgtar-create.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpgtar_CFLAGS) $(CFLAGS) -MT gpgtar-gpgtar-create.o -MD -MP -MF $(DEPDIR)/gpgtar-gpgtar-create.Tpo -c -o gpgtar-gpgtar-create.o `test -f 'gpgtar-create.c' || echo '$(srcdir)/'`gpgtar-create.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpgtar-gpgtar-create.Tpo $(DEPDIR)/gpgtar-gpgtar-create.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gpgtar-create.c' object='gpgtar-gpgtar-create.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpgtar_CFLAGS) $(CFLAGS) -c -o gpgtar-gpgtar-create.o `test -f 'gpgtar-create.c' || echo '$(srcdir)/'`gpgtar-create.c + +gpgtar-gpgtar-create.obj: gpgtar-create.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpgtar_CFLAGS) $(CFLAGS) -MT gpgtar-gpgtar-create.obj -MD -MP -MF $(DEPDIR)/gpgtar-gpgtar-create.Tpo -c -o gpgtar-gpgtar-create.obj `if test -f 'gpgtar-create.c'; then $(CYGPATH_W) 'gpgtar-create.c'; else $(CYGPATH_W) '$(srcdir)/gpgtar-create.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpgtar-gpgtar-create.Tpo $(DEPDIR)/gpgtar-gpgtar-create.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gpgtar-create.c' object='gpgtar-gpgtar-create.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpgtar_CFLAGS) $(CFLAGS) -c -o gpgtar-gpgtar-create.obj `if test -f 'gpgtar-create.c'; then $(CYGPATH_W) 'gpgtar-create.c'; else $(CYGPATH_W) '$(srcdir)/gpgtar-create.c'; fi` + +gpgtar-gpgtar-extract.o: gpgtar-extract.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpgtar_CFLAGS) $(CFLAGS) -MT gpgtar-gpgtar-extract.o -MD -MP -MF $(DEPDIR)/gpgtar-gpgtar-extract.Tpo -c -o gpgtar-gpgtar-extract.o `test -f 'gpgtar-extract.c' || echo '$(srcdir)/'`gpgtar-extract.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpgtar-gpgtar-extract.Tpo $(DEPDIR)/gpgtar-gpgtar-extract.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gpgtar-extract.c' object='gpgtar-gpgtar-extract.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpgtar_CFLAGS) $(CFLAGS) -c -o gpgtar-gpgtar-extract.o `test -f 'gpgtar-extract.c' || echo '$(srcdir)/'`gpgtar-extract.c + +gpgtar-gpgtar-extract.obj: gpgtar-extract.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpgtar_CFLAGS) $(CFLAGS) -MT gpgtar-gpgtar-extract.obj -MD -MP -MF $(DEPDIR)/gpgtar-gpgtar-extract.Tpo -c -o gpgtar-gpgtar-extract.obj `if test -f 'gpgtar-extract.c'; then $(CYGPATH_W) 'gpgtar-extract.c'; else $(CYGPATH_W) '$(srcdir)/gpgtar-extract.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpgtar-gpgtar-extract.Tpo $(DEPDIR)/gpgtar-gpgtar-extract.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gpgtar-extract.c' object='gpgtar-gpgtar-extract.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpgtar_CFLAGS) $(CFLAGS) -c -o gpgtar-gpgtar-extract.obj `if test -f 'gpgtar-extract.c'; then $(CYGPATH_W) 'gpgtar-extract.c'; else $(CYGPATH_W) '$(srcdir)/gpgtar-extract.c'; fi` + +gpgtar-gpgtar-list.o: gpgtar-list.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpgtar_CFLAGS) $(CFLAGS) -MT gpgtar-gpgtar-list.o -MD -MP -MF $(DEPDIR)/gpgtar-gpgtar-list.Tpo -c -o gpgtar-gpgtar-list.o `test -f 'gpgtar-list.c' || echo '$(srcdir)/'`gpgtar-list.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpgtar-gpgtar-list.Tpo $(DEPDIR)/gpgtar-gpgtar-list.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gpgtar-list.c' object='gpgtar-gpgtar-list.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpgtar_CFLAGS) $(CFLAGS) -c -o gpgtar-gpgtar-list.o `test -f 'gpgtar-list.c' || echo '$(srcdir)/'`gpgtar-list.c + +gpgtar-gpgtar-list.obj: gpgtar-list.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpgtar_CFLAGS) $(CFLAGS) -MT gpgtar-gpgtar-list.obj -MD -MP -MF $(DEPDIR)/gpgtar-gpgtar-list.Tpo -c -o gpgtar-gpgtar-list.obj `if test -f 'gpgtar-list.c'; then $(CYGPATH_W) 'gpgtar-list.c'; else $(CYGPATH_W) '$(srcdir)/gpgtar-list.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/gpgtar-gpgtar-list.Tpo $(DEPDIR)/gpgtar-gpgtar-list.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gpgtar-list.c' object='gpgtar-gpgtar-list.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(gpgtar_CFLAGS) $(CFLAGS) -c -o gpgtar-gpgtar-list.obj `if test -f 'gpgtar-list.c'; then $(CYGPATH_W) 'gpgtar-list.c'; else $(CYGPATH_W) '$(srcdir)/gpgtar-list.c'; fi` + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) $(SCRIPTS) +installdirs: + for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(libexecdir)" "$(DESTDIR)$(sbindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-libexecPROGRAMS \ + clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/clean-sat.Po + -rm -f ./$(DEPDIR)/gpg-connect-agent.Po + -rm -f ./$(DEPDIR)/gpg_check_pattern-gpg-check-pattern.Po + -rm -f ./$(DEPDIR)/gpg_wks_client-call-dirmngr.Po + -rm -f ./$(DEPDIR)/gpg_wks_client-gpg-wks-client.Po + -rm -f ./$(DEPDIR)/gpg_wks_client-mime-maker.Po + -rm -f ./$(DEPDIR)/gpg_wks_client-mime-parser.Po + -rm -f ./$(DEPDIR)/gpg_wks_client-rfc822parse.Po + -rm -f ./$(DEPDIR)/gpg_wks_client-send-mail.Po + -rm -f ./$(DEPDIR)/gpg_wks_client-wks-receive.Po + -rm -f ./$(DEPDIR)/gpg_wks_client-wks-util.Po + -rm -f ./$(DEPDIR)/gpg_wks_server-gpg-wks-server.Po + -rm -f ./$(DEPDIR)/gpg_wks_server-mime-maker.Po + -rm -f ./$(DEPDIR)/gpg_wks_server-mime-parser.Po + -rm -f ./$(DEPDIR)/gpg_wks_server-rfc822parse.Po + -rm -f ./$(DEPDIR)/gpg_wks_server-send-mail.Po + -rm -f ./$(DEPDIR)/gpg_wks_server-wks-receive.Po + -rm -f ./$(DEPDIR)/gpg_wks_server-wks-util.Po + -rm -f ./$(DEPDIR)/gpgconf-comp.Po + -rm -f ./$(DEPDIR)/gpgconf.Po + -rm -f ./$(DEPDIR)/gpgparsemail.Po + -rm -f ./$(DEPDIR)/gpgsplit.Po + -rm -f ./$(DEPDIR)/gpgtar-gpgtar-create.Po + -rm -f ./$(DEPDIR)/gpgtar-gpgtar-extract.Po + -rm -f ./$(DEPDIR)/gpgtar-gpgtar-list.Po + -rm -f ./$(DEPDIR)/gpgtar-gpgtar.Po + -rm -f ./$(DEPDIR)/make-dns-cert.Po + -rm -f ./$(DEPDIR)/rfc822parse.Po + -rm -f ./$(DEPDIR)/watchgnupg.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS install-libexecPROGRAMS \ + install-sbinSCRIPTS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/clean-sat.Po + -rm -f ./$(DEPDIR)/gpg-connect-agent.Po + -rm -f ./$(DEPDIR)/gpg_check_pattern-gpg-check-pattern.Po + -rm -f ./$(DEPDIR)/gpg_wks_client-call-dirmngr.Po + -rm -f ./$(DEPDIR)/gpg_wks_client-gpg-wks-client.Po + -rm -f ./$(DEPDIR)/gpg_wks_client-mime-maker.Po + -rm -f ./$(DEPDIR)/gpg_wks_client-mime-parser.Po + -rm -f ./$(DEPDIR)/gpg_wks_client-rfc822parse.Po + -rm -f ./$(DEPDIR)/gpg_wks_client-send-mail.Po + -rm -f ./$(DEPDIR)/gpg_wks_client-wks-receive.Po + -rm -f ./$(DEPDIR)/gpg_wks_client-wks-util.Po + -rm -f ./$(DEPDIR)/gpg_wks_server-gpg-wks-server.Po + -rm -f ./$(DEPDIR)/gpg_wks_server-mime-maker.Po + -rm -f ./$(DEPDIR)/gpg_wks_server-mime-parser.Po + -rm -f ./$(DEPDIR)/gpg_wks_server-rfc822parse.Po + -rm -f ./$(DEPDIR)/gpg_wks_server-send-mail.Po + -rm -f ./$(DEPDIR)/gpg_wks_server-wks-receive.Po + -rm -f ./$(DEPDIR)/gpg_wks_server-wks-util.Po + -rm -f ./$(DEPDIR)/gpgconf-comp.Po + -rm -f ./$(DEPDIR)/gpgconf.Po + -rm -f ./$(DEPDIR)/gpgparsemail.Po + -rm -f ./$(DEPDIR)/gpgsplit.Po + -rm -f ./$(DEPDIR)/gpgtar-gpgtar-create.Po + -rm -f ./$(DEPDIR)/gpgtar-gpgtar-extract.Po + -rm -f ./$(DEPDIR)/gpgtar-gpgtar-list.Po + -rm -f ./$(DEPDIR)/gpgtar-gpgtar.Po + -rm -f ./$(DEPDIR)/make-dns-cert.Po + -rm -f ./$(DEPDIR)/rfc822parse.Po + -rm -f ./$(DEPDIR)/watchgnupg.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS uninstall-libexecPROGRAMS \ + uninstall-sbinSCRIPTS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-binPROGRAMS clean-generic clean-libexecPROGRAMS \ + clean-noinstPROGRAMS cscopelist-am ctags ctags-am distclean \ + distclean-compile distclean-generic distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-binPROGRAMS install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am \ + install-libexecPROGRAMS install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-sbinSCRIPTS install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-binPROGRAMS uninstall-libexecPROGRAMS \ + uninstall-sbinSCRIPTS + +.PRECIOUS: Makefile + + +@HAVE_W32_SYSTEM_TRUE@.rc.o: +@HAVE_W32_SYSTEM_TRUE@ $(WINDRES) $(DEFAULT_INCLUDES) $(INCLUDES) "$<" "$@" + +@HAVE_W32_SYSTEM_TRUE@gpgconf-w32info.o: gpgconf.w32-manifest +@HAVE_W32_SYSTEM_TRUE@gpgtar-w32info.o: gpgtar.w32-manifest +@HAVE_W32_SYSTEM_TRUE@gpg-connect-agent-w32info.o: gpg-connect-agent.w32-manifest +@HAVE_W32_SYSTEM_TRUE@gpg-check-pattern-w32info.o: gpg-check-pattern.w32-manifest +@HAVE_W32_SYSTEM_TRUE@gpg-wks-client-w32info.o: gpg-wks-client.w32-manifest + +# Make sure that all libs are build before we use them. This is +# important for things like make -j2. +$(PROGRAMS): $(common_libs) $(pwquery_libs) ../common/libgpgrl.a + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/tools/Manifest b/tools/Manifest new file mode 100644 index 0000000..9642335 --- /dev/null +++ b/tools/Manifest @@ -0,0 +1,6 @@ +Makefile.am +watchgnupg.c +gpgconf.c +gpgconf.h +gpgconf-list.c +$names$ diff --git a/tools/addgnupghome b/tools/addgnupghome new file mode 100755 index 0000000..718b222 --- /dev/null +++ b/tools/addgnupghome @@ -0,0 +1,122 @@ +#!/bin/sh +# Add a new .gnupg home directory for a list of users -*- sh -*- +# +# Copyright 2004 Free Software Foundation, Inc. +# +# This file is free software; as a special exception the author gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# This file is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +PGM=addgnupghome +any_error=0 + + +error () { + echo "$PGM: $*" >&2 + any_error=1 +} + +info () { + echo "$PGM: $*" >&2 +} + +# Do it for one user +one_user () { + user="$1" + home=$(${cat_passwd} | awk -F: -v n="$user" '$1 == n {print $6}') + if [ -z "$home" ]; then + if ${cat_passwd} | awk -F: -v n="$user" '$1 == n {exit 1}'; then + error "no such user \`$user'" + else + error "no home directory for user \`$user'" + fi + return + fi + if [ ! -d "$home" ]; then + error "home directory \`$home' of user \`$user' does not exist" + return + fi + if [ -d "$home/.gnupg" ]; then + info "skipping user \`$user': \`.gnupg' already exists" + return + fi + info "creating home directory \`$home/.gnupg' for \`$user'" + if ! mkdir "$home/.gnupg" ; then + error "error creating \`$home/.gnupg'" + return + fi + + if ! chown $user "$home/.gnupg" ; then + error "error changing ownership of \`$home/.gnupg'" + return + fi + + group=$(id -g "$user") + [ -z "$group" ] && group="0" + + if [ "$group" -gt 0 ]; then + if ! chgrp $group "$home/.gnupg" ; then + error "error changing group of \`$home/.gnupg'" + return + fi + fi + + if ! cd "$home/.gnupg" ; then + error "error cd-ing to \`$home/.gnupg'" + return + fi + for f in $filelist; do + if [ -d /etc/skel/.gnupg/$f ]; then + mkdir $f + else + cp /etc/skel/.gnupg/$f $f + fi + if ! chown $user $f ; then + error "error changing ownership of \`$f'" + return + fi + if [ "$group" -gt 0 ]; then + if ! chgrp $group "$f" ; then + error "error changing group of \`$f'" + return + fi + fi + done + +} + +if [ -z "$1" ]; then + echo "usage: $PGM userids" + exit 1 +fi + +# Check whether we can use getent +if getent --help </dev/null >/dev/null 2>&1 ; then + cat_passwd='getent passwd' +else + cat_passwd='cat /etc/passwd' + info "please note that only users from /etc/passwd are checked" +fi + +if [ ! -d /etc/skel/.gnupg ]; then + error "skeleton directory \`/etc/skel/.gnupg' does not exist" + exit 1 +fi +cd "/etc/skel/.gnupg" || (error "error cd-ing to \`/etc/skel/.gnupg'"; exit 1) +filelist=$(find . \( -type f -o -type d \) '!' -name '*~' '!' -name . -print) + + +if ! umask 0077 ; then + error "error setting umask" + exit 1 +fi + +for name in $*; do + one_user $name +done + +exit $any_error diff --git a/tools/applygnupgdefaults b/tools/applygnupgdefaults new file mode 100755 index 0000000..316509f --- /dev/null +++ b/tools/applygnupgdefaults @@ -0,0 +1,81 @@ +#!/bin/sh +# Apply defaults from /etc/gnupg/gpgconf.conf to all users -*- sh -*- +# +# Copyright 2007 Free Software Foundation, Inc. +# +# This file is free software; as a special exception the author gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# This file is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +PGM=applygnupgdefaults +errorfile= + +error () { + echo "$PGM: $*" >&2 + [ -n "$errorfile" ] && echo "$PGM: $*" >>$errorfile +} + +info () { + echo "$PGM: $*" >&2 +} + +if [ -n "$1" ]; then + echo "usage: $PGM" >&2 + exit 1 +fi + +# Cleanup on exit +cleanup () +{ + [ -n "$errorfile" -a -f "$errorfile" ] && rm "$errorfile" +} +trap cleanup EXIT SIGINT SIGHUP SIGPIPE +errorfile=$(mktemp "/tmp/$PGM.log.XXXXXX") +[ -n "$errorfile" -a -f "$errorfile" ] || exit 2 + +# Check whether we can use getent +if getent --help </dev/null >/dev/null 2>&1 ; then + cat_passwd='getent passwd' +else + cat_passwd='cat /etc/passwd' + info "please note that only users from /etc/passwd are processed" +fi + +if [ ! -f /etc/gnupg/gpgconf.conf ]; then + error "global configuration file \`/etc/gnupg/gpgconf.conf' does not exist" + exit 1 +fi +if [ ! -f /etc/shells ]; then + error "missing file \`/etc/shells'" + exit 1 +fi + +if [ $(id -u) -ne 0 ]; then + error "needs to be run as root" + exit 1 +fi + +${cat_passwd} \ + | while IFS=: read -r user dmy_a uid dmy_c dmy_d home shell dmy_rest; do + # Process only entries with a valid login shell + grep </etc/shells "^$shell" 2>/dev/null >/dev/null || continue + # and with an pre-existing gnupg home directory + [ -d "$home/.gnupg" ] || continue + # but not root + [ "${uid:-0}" -eq 0 ] && continue + info "running \"gpgconf --apply-defaults\" for $user" + if su -l -s /bin/sh \ + -c 'gpgconf --apply-defaults && echo SUCCESS' $user \ + | tail -1 | grep ^SUCCESS >/dev/null ; then + : + else + error "failed to update gnupg defaults for $user" + fi +done + +[ "$(wc -c <$errorfile)" -gt 0 ] && exit 1 +exit 0 diff --git a/tools/call-dirmngr.c b/tools/call-dirmngr.c new file mode 100644 index 0000000..4eef9b2 --- /dev/null +++ b/tools/call-dirmngr.c @@ -0,0 +1,381 @@ +/* call-dirmngr.c - Interact with the Dirmngr. + * Copyright (C) 2016, 2022 g10 Code GmbH + * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#ifdef HAVE_LOCALE_H +# include <locale.h> +#endif + +#include <assuan.h> +#include "../common/util.h" +#include "../common/i18n.h" +#include "../common/asshelp.h" +#include "../common/mbox-util.h" +#include "./call-dirmngr.h" + +static struct +{ + int verbose; + int debug_ipc; + int autostart; +} opt; + + + +void +set_dirmngr_options (int verbose, int debug_ipc, int autostart) +{ + opt.verbose = verbose; + opt.debug_ipc = debug_ipc; + opt.autostart = autostart; +} + + +/* Connect to the Dirmngr and return an assuan context. */ +static gpg_error_t +connect_dirmngr (assuan_context_t *r_ctx) +{ + gpg_error_t err; + assuan_context_t ctx; + + *r_ctx = NULL; + err = start_new_dirmngr (&ctx, + GPG_ERR_SOURCE_DEFAULT, + NULL, + opt.autostart, opt.verbose, opt.debug_ipc, + NULL, NULL); + if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_DIRMNGR) + { + static int shown; + + if (!shown) + { + shown = 1; + log_info (_("no dirmngr running in this session\n")); + } + } + + if (err) + assuan_release (ctx); + else + { + *r_ctx = ctx; + } + + return err; +} + + + + +/* Parameter structure used with the WKD_GET command. */ +struct wkd_get_parm_s +{ + estream_t memfp; +}; + + +/* Data callback for the WKD_GET command. */ +static gpg_error_t +wkd_get_data_cb (void *opaque, const void *data, size_t datalen) +{ + struct wkd_get_parm_s *parm = opaque; + gpg_error_t err = 0; + size_t nwritten; + + if (!data) + return 0; /* Ignore END commands. */ + if (!parm->memfp) + return 0; /* Data is not required. */ + + if (es_write (parm->memfp, data, datalen, &nwritten)) + err = gpg_error_from_syserror (); + + return err; +} + + +/* Status callback for the WKD_GET command. */ +static gpg_error_t +wkd_get_status_cb (void *opaque, const char *line) +{ + struct wkd_get_parm_s *parm = opaque; + gpg_error_t err = 0; + + (void)line; + (void)parm; + + return err; +} + + +/* Ask the dirmngr for the submission address of a WKD server for the + * mail address ADDRSPEC. On success the submission address is stored + * at R_ADDRSPEC. */ +gpg_error_t +wkd_get_submission_address (const char *addrspec, char **r_addrspec) +{ + gpg_error_t err; + assuan_context_t ctx; + struct wkd_get_parm_s parm; + char *line = NULL; + void *vp; + char *buffer = NULL; + char *p; + + memset (&parm, 0, sizeof parm); + *r_addrspec = NULL; + + err = connect_dirmngr (&ctx); + if (err) + return err; + + line = es_bsprintf ("WKD_GET --submission-address -- %s", addrspec); + if (!line) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (strlen (line) + 2 >= ASSUAN_LINELENGTH) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + + parm.memfp = es_fopenmem (0, "rwb"); + if (!parm.memfp) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = assuan_transact (ctx, line, wkd_get_data_cb, &parm, + NULL, NULL, wkd_get_status_cb, &parm); + if (err) + goto leave; + + es_fputc (0, parm.memfp); + if (es_fclose_snatch (parm.memfp, &vp, NULL)) + { + err = gpg_error_from_syserror (); + goto leave; + } + buffer = vp; + parm.memfp = NULL; + p = strchr (buffer, '\n'); + if (p) + *p = 0; + trim_spaces (buffer); + if (!is_valid_mailbox (buffer)) + { + err = gpg_error (GPG_ERR_INV_USER_ID); + goto leave; + } + *r_addrspec = xtrystrdup (buffer); + if (!*r_addrspec) + err = gpg_error_from_syserror (); + + leave: + es_free (buffer); + es_fclose (parm.memfp); + xfree (line); + assuan_release (ctx); + return err; +} + + +/* Ask the dirmngr for the policy flags and return them as an estream + * memory stream. If no policy flags are set, NULL is stored at + * R_BUFFER. */ +gpg_error_t +wkd_get_policy_flags (const char *addrspec, estream_t *r_buffer) +{ + gpg_error_t err; + assuan_context_t ctx; + struct wkd_get_parm_s parm; + char *line = NULL; + char *buffer = NULL; + + memset (&parm, 0, sizeof parm); + *r_buffer = NULL; + + err = connect_dirmngr (&ctx); + if (err) + return err; + + line = es_bsprintf ("WKD_GET --policy-flags -- %s", addrspec); + if (!line) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (strlen (line) + 2 >= ASSUAN_LINELENGTH) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + + parm.memfp = es_fopenmem (0, "rwb"); + if (!parm.memfp) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = assuan_transact (ctx, line, wkd_get_data_cb, &parm, + NULL, NULL, wkd_get_status_cb, &parm); + if (err) + goto leave; + + es_rewind (parm.memfp); + *r_buffer = parm.memfp; + parm.memfp = 0; + + leave: + es_free (buffer); + es_fclose (parm.memfp); + xfree (line); + assuan_release (ctx); + return err; +} + + +/* Ask the dirmngr for the key for ADDRSPEC. On success a stream with + * the key is stored at R_KEY. */ +gpg_error_t +wkd_get_key (const char *addrspec, estream_t *r_key) +{ + gpg_error_t err; + assuan_context_t ctx; + struct wkd_get_parm_s parm; + char *line = NULL; + + memset (&parm, 0, sizeof parm); + *r_key = NULL; + + err = connect_dirmngr (&ctx); + if (err) + return err; + + line = es_bsprintf ("WKD_GET -- %s", addrspec); + if (!line) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (strlen (line) + 2 >= ASSUAN_LINELENGTH) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + + parm.memfp = es_fopenmem (0, "rwb"); + if (!parm.memfp) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = assuan_transact (ctx, line, wkd_get_data_cb, &parm, + NULL, NULL, wkd_get_status_cb, &parm); + if (err) + goto leave; + + es_rewind (parm.memfp); + *r_key = parm.memfp; + parm.memfp = NULL; + + leave: + es_fclose (parm.memfp); + xfree (line); + assuan_release (ctx); + return err; +} + + +/* Send the KS_GET command to the dirmngr. The caller provides CB + * which is called for each key. The callback is called wit a stream + * conveying a single key and several other informational parameters. + * DOMAIN restricts the returned keys to this domain. */ +gpg_error_t +wkd_dirmngr_ks_get (const char *domain, gpg_error_t cb (estream_t key)) +{ + gpg_error_t err; + assuan_context_t ctx; + struct wkd_get_parm_s parm; + char *line = NULL; + int any = 0; + + memset (&parm, 0, sizeof parm); + + err = connect_dirmngr (&ctx); + if (err) + return err; + + line = es_bsprintf ("KS_GET --ldap --first %s", domain? domain:""); + if (!line) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (strlen (line) + 2 >= ASSUAN_LINELENGTH) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + + parm.memfp = es_fopenmem (0, "rwb"); + if (!parm.memfp) + { + err = gpg_error_from_syserror (); + goto leave; + } + + for (;;) + { + err = assuan_transact (ctx, any? "KS_GET --next" : line, + wkd_get_data_cb, &parm, + NULL, NULL, wkd_get_status_cb, &parm); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_NO_DATA + && gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR) + err = any? 0 : gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + any = 1; + + es_rewind (parm.memfp); + err = cb (parm.memfp); + if (err) + break; + es_ftruncate (parm.memfp, 0); + } + + + leave: + es_fclose (parm.memfp); + xfree (line); + assuan_release (ctx); + return err; +} diff --git a/tools/call-dirmngr.h b/tools/call-dirmngr.h new file mode 100644 index 0000000..3acea51 --- /dev/null +++ b/tools/call-dirmngr.h @@ -0,0 +1,35 @@ +/* call-dirmngr.h - Interact with the Dirmngr. + * Copyright (C) 2016 g10 Code GmbH + * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ +#ifndef GNUPG_TOOLS_CALL_DIRMNGR_H +#define GNUPG_TOOLS_CALL_DIRMNGR_H + +void set_dirmngr_options (int verbose, int debug_ipc, int autostart); + +gpg_error_t wkd_get_submission_address (const char *addrspec, + char **r_addrspec); +gpg_error_t wkd_get_policy_flags (const char *addrspec, estream_t *r_buffer); + +gpg_error_t wkd_get_key (const char *addrspec, estream_t *r_key); + +gpg_error_t wkd_dirmngr_ks_get (const char *domain, + gpg_error_t cb (estream_t key)); + + +#endif /*GNUPG_TOOLS_CALL_DIRMNGR_H*/ diff --git a/tools/ccidmon.c b/tools/ccidmon.c new file mode 100644 index 0000000..d61bb3c --- /dev/null +++ b/tools/ccidmon.c @@ -0,0 +1,882 @@ +/* ccidmon.c - CCID monitor for use with the Linux usbmon facility. + * Copyright (C) 2009 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + + +/* This utility takes the output of usbmon, filters out the bulk data + and prints the CCID messages in a human friendly way. + +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <errno.h> +#include <stdarg.h> +#include <assert.h> +#include <unistd.h> +#include <signal.h> + + +#ifndef PACKAGE_VERSION +# define PACKAGE_VERSION "[build on " __DATE__ " " __TIME__ "]" +#endif +#ifndef PACKAGE_BUGREPORT +# define PACKAGE_BUGREPORT "devnull@example.org" +#endif +#define PGM "ccidmon" +#ifndef GNUPG_NAME +# define GNUPG_NAME "GnuPG" +#endif + +/* Option flags. */ +static int verbose; +static int debug; +static int skip_escape; +static int usb_bus, usb_dev; +static int sniffusb; + + +/* Error counter. */ +static int any_error; + +/* Data storage. */ +struct +{ + int is_bi; + char address[50]; + int count; + char data[2000]; +} databuffer; + + +enum { + RDR_to_PC_NotifySlotChange= 0x50, + RDR_to_PC_HardwareError = 0x51, + + PC_to_RDR_SetParameters = 0x61, + PC_to_RDR_IccPowerOn = 0x62, + PC_to_RDR_IccPowerOff = 0x63, + PC_to_RDR_GetSlotStatus = 0x65, + PC_to_RDR_Secure = 0x69, + PC_to_RDR_T0APDU = 0x6a, + PC_to_RDR_Escape = 0x6b, + PC_to_RDR_GetParameters = 0x6c, + PC_to_RDR_ResetParameters = 0x6d, + PC_to_RDR_IccClock = 0x6e, + PC_to_RDR_XfrBlock = 0x6f, + PC_to_RDR_Mechanical = 0x71, + PC_to_RDR_Abort = 0x72, + PC_to_RDR_SetDataRate = 0x73, + + RDR_to_PC_DataBlock = 0x80, + RDR_to_PC_SlotStatus = 0x81, + RDR_to_PC_Parameters = 0x82, + RDR_to_PC_Escape = 0x83, + RDR_to_PC_DataRate = 0x84 +}; + + +#define digitp(p) (*(p) >= '0' && *(p) <= '9') +#define hexdigitp(a) (digitp (a) \ + || (*(a) >= 'A' && *(a) <= 'F') \ + || (*(a) >= 'a' && *(a) <= 'f')) +#define ascii_isspace(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t') +#define xtoi_1(p) ((p) <= '9'? ((p)- '0'): \ + (p) <= 'F'? ((p)-'A'+10):((p)-'a'+10)) + + + +/* Print diagnostic message and exit with failure. */ +static void +die (const char *format, ...) +{ + va_list arg_ptr; + + fflush (stdout); + fprintf (stderr, "%s: ", PGM); + + va_start (arg_ptr, format); + vfprintf (stderr, format, arg_ptr); + va_end (arg_ptr); + putc ('\n', stderr); + + exit (1); +} + + +/* Print diagnostic message. */ +static void +err (const char *format, ...) +{ + va_list arg_ptr; + + any_error = 1; + + fflush (stdout); + fprintf (stderr, "%s: ", PGM); + + va_start (arg_ptr, format); + vfprintf (stderr, format, arg_ptr); + va_end (arg_ptr); + putc ('\n', stderr); +} + + +/* Convert a little endian stored 4 byte value into an unsigned + integer. */ +static unsigned int +convert_le_u32 (const unsigned char *buf) +{ + return buf[0] | (buf[1] << 8) | (buf[2] << 16) | ((unsigned int)buf[3] << 24); +} + + +/* Convert a little endian stored 2 byte value into an unsigned + integer. */ +static unsigned int +convert_le_u16 (const unsigned char *buf) +{ + return buf[0] | (buf[1] << 8); +} + + + + +static void +print_pr_data (const unsigned char *data, size_t datalen, size_t off) +{ + int needlf = 0; + int first = 1; + + for (; off < datalen; off++) + { + if (!(off % 16) || first) + { + if (needlf) + putchar ('\n'); + printf (" [%04lu] ", (unsigned long)off); + } + printf (" %02X", data[off]); + needlf = 1; + first = 0; + } + if (needlf) + putchar ('\n'); +} + + +static void +print_p2r_header (const char *name, const unsigned char *msg, size_t msglen) +{ + printf ("%s:\n", name); + if (msglen < 7) + return; + printf (" dwLength ..........: %u\n", convert_le_u32 (msg+1)); + printf (" bSlot .............: %u\n", msg[5]); + printf (" bSeq ..............: %u\n", msg[6]); +} + + +static void +print_p2r_iccpoweron (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_IccPowerOn", msg, msglen); + if (msglen < 10) + return; + printf (" bPowerSelect ......: 0x%02x (%s)\n", msg[7], + msg[7] == 0? "auto": + msg[7] == 1? "5.0 V": + msg[7] == 2? "3.0 V": + msg[7] == 3? "1.8 V":""); + print_pr_data (msg, msglen, 8); +} + + +static void +print_p2r_iccpoweroff (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_IccPowerOff", msg, msglen); + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_getslotstatus (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_GetSlotStatus", msg, msglen); + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_xfrblock (const unsigned char *msg, size_t msglen) +{ + unsigned int val; + + print_p2r_header ("PC_to_RDR_XfrBlock", msg, msglen); + if (msglen < 10) + return; + printf (" bBWI ..............: 0x%02x\n", msg[7]); + val = convert_le_u16 (msg+8); + printf (" wLevelParameter ...: 0x%04x%s\n", val, + val == 1? " (continued)": + val == 2? " (continues+ends)": + val == 3? " (continues+continued)": + val == 16? " (DataBlock-expected)":""); + print_pr_data (msg, msglen, 10); +} + + +static void +print_p2r_getparameters (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_GetParameters", msg, msglen); + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_resetparameters (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_ResetParameters", msg, msglen); + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_setparameters (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_SetParameters", msg, msglen); + if (msglen < 10) + return; + printf (" bProtocolNum ......: 0x%02x\n", msg[7]); + print_pr_data (msg, msglen, 8); +} + + +static void +print_p2r_escape (const unsigned char *msg, size_t msglen) +{ + if (skip_escape) + return; + print_p2r_header ("PC_to_RDR_Escape", msg, msglen); + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_iccclock (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_IccClock", msg, msglen); + if (msglen < 10) + return; + printf (" bClockCommand .....: 0x%02x\n", msg[7]); + print_pr_data (msg, msglen, 8); +} + + +static void +print_p2r_to0apdu (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_T0APDU", msg, msglen); + if (msglen < 10) + return; + printf (" bmChanges .........: 0x%02x\n", msg[7]); + printf (" bClassGetResponse .: 0x%02x\n", msg[8]); + printf (" bClassEnvelope ....: 0x%02x\n", msg[9]); + print_pr_data (msg, msglen, 10); +} + + +static void +print_p2r_secure (const unsigned char *msg, size_t msglen) +{ + unsigned int val; + + print_p2r_header ("PC_to_RDR_Secure", msg, msglen); + if (msglen < 10) + return; + printf (" bBMI ..............: 0x%02x\n", msg[7]); + val = convert_le_u16 (msg+8); + printf (" wLevelParameter ...: 0x%04x%s\n", val, + val == 1? " (continued)": + val == 2? " (continues+ends)": + val == 3? " (continues+continued)": + val == 16? " (DataBlock-expected)":""); + print_pr_data (msg, msglen, 10); +} + + +static void +print_p2r_mechanical (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_Mechanical", msg, msglen); + if (msglen < 10) + return; + printf (" bFunction .........: 0x%02x\n", msg[7]); + print_pr_data (msg, msglen, 8); +} + + +static void +print_p2r_abort (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_Abort", msg, msglen); + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_setdatarate (const unsigned char *msg, size_t msglen) +{ + print_p2r_header ("PC_to_RDR_SetDataRate", msg, msglen); + if (msglen < 10) + return; + print_pr_data (msg, msglen, 7); +} + + +static void +print_p2r_unknown (const unsigned char *msg, size_t msglen) +{ + char buf[100]; + + snprintf (buf, sizeof buf, "Unknown PC_to_RDR command 0x%02X", + msglen? msg[0]:0); + print_p2r_header (buf, msg, msglen); + if (msglen < 10) + return; + print_pr_data (msg, msglen, 0); +} + + +static void +print_p2r (const unsigned char *msg, size_t msglen) +{ + switch (msglen? msg[0]:0) + { + case PC_to_RDR_IccPowerOn: + print_p2r_iccpoweron (msg, msglen); + break; + case PC_to_RDR_IccPowerOff: + print_p2r_iccpoweroff (msg, msglen); + break; + case PC_to_RDR_GetSlotStatus: + print_p2r_getslotstatus (msg, msglen); + break; + case PC_to_RDR_XfrBlock: + print_p2r_xfrblock (msg, msglen); + break; + case PC_to_RDR_GetParameters: + print_p2r_getparameters (msg, msglen); + break; + case PC_to_RDR_ResetParameters: + print_p2r_resetparameters (msg, msglen); + break; + case PC_to_RDR_SetParameters: + print_p2r_setparameters (msg, msglen); + break; + case PC_to_RDR_Escape: + print_p2r_escape (msg, msglen); + break; + case PC_to_RDR_IccClock: + print_p2r_iccclock (msg, msglen); + break; + case PC_to_RDR_T0APDU: + print_p2r_to0apdu (msg, msglen); + break; + case PC_to_RDR_Secure: + print_p2r_secure (msg, msglen); + break; + case PC_to_RDR_Mechanical: + print_p2r_mechanical (msg, msglen); + break; + case PC_to_RDR_Abort: + print_p2r_abort (msg, msglen); + break; + case PC_to_RDR_SetDataRate: + print_p2r_setdatarate (msg, msglen); + break; + default: + print_p2r_unknown (msg, msglen); + break; + } +} + + +static void +print_r2p_header (const char *name, const unsigned char *msg, size_t msglen) +{ + printf ("%s:\n", name); + if (msglen < 9) + return; + printf (" dwLength ..........: %u\n", convert_le_u32 (msg+1)); + printf (" bSlot .............: %u\n", msg[5]); + printf (" bSeq ..............: %u\n", msg[6]); + printf (" bStatus ...........: %u\n", msg[7]); + if (msg[8]) + printf (" bError ............: %u\n", msg[8]); +} + + +static void +print_r2p_datablock (const unsigned char *msg, size_t msglen) +{ + print_r2p_header ("RDR_to_PC_DataBlock", msg, msglen); + if (msglen < 10) + return; + if (msg[9]) + printf (" bChainParameter ...: 0x%02x%s\n", msg[9], + msg[9] == 1? " (continued)": + msg[9] == 2? " (continues+ends)": + msg[9] == 3? " (continues+continued)": + msg[9] == 16? " (XferBlock-expected)":""); + print_pr_data (msg, msglen, 10); +} + + +static void +print_r2p_slotstatus (const unsigned char *msg, size_t msglen) +{ + print_r2p_header ("RDR_to_PC_SlotStatus", msg, msglen); + if (msglen < 10) + return; + printf (" bClockStatus ......: 0x%02x%s\n", msg[9], + msg[9] == 0? " (running)": + msg[9] == 1? " (stopped-L)": + msg[9] == 2? " (stopped-H)": + msg[9] == 3? " (stopped)":""); + print_pr_data (msg, msglen, 10); +} + + +static void +print_r2p_parameters (const unsigned char *msg, size_t msglen) +{ + print_r2p_header ("RDR_to_PC_Parameters", msg, msglen); + if (msglen < 10) + return; + + printf (" protocol ..........: T=%d\n", msg[9]); + if (msglen == 17 && msg[9] == 1) + { + /* Protocol T=1. */ + printf (" bmFindexDindex ....: %02X\n", msg[10]); + printf (" bmTCCKST1 .........: %02X\n", msg[11]); + printf (" bGuardTimeT1 ......: %02X\n", msg[12]); + printf (" bmWaitingIntegersT1: %02X\n", msg[13]); + printf (" bClockStop ........: %02X\n", msg[14]); + printf (" bIFSC .............: %d\n", msg[15]); + printf (" bNadValue .........: %d\n", msg[16]); + } + else + print_pr_data (msg, msglen, 10); +} + + +static void +print_r2p_escape (const unsigned char *msg, size_t msglen) +{ + if (skip_escape) + return; + print_r2p_header ("RDR_to_PC_Escape", msg, msglen); + if (msglen < 10) + return; + printf (" buffer[9] .........: %02X\n", msg[9]); + print_pr_data (msg, msglen, 10); +} + + +static void +print_r2p_datarate (const unsigned char *msg, size_t msglen) +{ + print_r2p_header ("RDR_to_PC_DataRate", msg, msglen); + if (msglen < 10) + return; + if (msglen >= 18) + { + printf (" dwClockFrequency ..: %u\n", convert_le_u32 (msg+10)); + printf (" dwDataRate ..... ..: %u\n", convert_le_u32 (msg+14)); + print_pr_data (msg, msglen, 18); + } + else + print_pr_data (msg, msglen, 10); +} + + +static void +print_r2p_unknown (const unsigned char *msg, size_t msglen) +{ + char buf[100]; + + snprintf (buf, sizeof buf, "Unknown RDR_to_PC command 0x%02X", + msglen? msg[0]:0); + print_r2p_header (buf, msg, msglen); + if (msglen < 10) + return; + printf (" bMessageType ......: %02X\n", msg[0]); + printf (" buffer[9] .........: %02X\n", msg[9]); + print_pr_data (msg, msglen, 10); +} + + +static void +print_r2p (const unsigned char *msg, size_t msglen) +{ + switch (msglen? msg[0]:0) + { + case RDR_to_PC_DataBlock: + print_r2p_datablock (msg, msglen); + break; + case RDR_to_PC_SlotStatus: + print_r2p_slotstatus (msg, msglen); + break; + case RDR_to_PC_Parameters: + print_r2p_parameters (msg, msglen); + break; + case RDR_to_PC_Escape: + print_r2p_escape (msg, msglen); + break; + case RDR_to_PC_DataRate: + print_r2p_datarate (msg, msglen); + break; + default: + print_r2p_unknown (msg, msglen); + break; + } + +} + + +static void +flush_data (void) +{ + if (!databuffer.count) + return; + + if (verbose) + printf ("Address: %s\n", databuffer.address); + if (databuffer.is_bi) + { + print_r2p (databuffer.data, databuffer.count); + if (verbose) + putchar ('\n'); + } + else + print_p2r (databuffer.data, databuffer.count); + + databuffer.count = 0; +} + +static void +collect_data (char *hexdata, const char *address, unsigned int lineno) +{ + size_t length; + int is_bi; + char *s; + unsigned int value; + + is_bi = (*address && address[1] == 'i'); + + if (databuffer.is_bi != is_bi || strcmp (databuffer.address, address)) + flush_data (); + databuffer.is_bi = is_bi; + if (strlen (address) >= sizeof databuffer.address) + die ("address field too long"); + strcpy (databuffer.address, address); + + length = databuffer.count; + for (s=hexdata; *s; s++ ) + { + if (ascii_isspace (*s)) + continue; + if (!hexdigitp (s)) + { + err ("invalid hex digit in line %u - line skipped", lineno); + break; + } + value = xtoi_1 (*s) * 16; + s++; + if (!hexdigitp (s)) + { + err ("invalid hex digit in line %u - line skipped", lineno); + break; + } + value += xtoi_1 (*s); + + if (length >= sizeof (databuffer.data)) + { + err ("too much data at line %u - can handle only up to % bytes", + lineno, sizeof (databuffer.data)); + break; + } + databuffer.data[length++] = value; + } + databuffer.count = length; +} + + +static void +parse_line (char *line, unsigned int lineno) +{ + char *p; + char *event_type, *address, *data, *status, *datatag; + + if (debug) + printf ("line[%u] ='%s'\n", lineno, line); + + p = strtok (line, " "); + if (!p) + die ("invalid line %d (no URB)"); + p = strtok (NULL, " "); + if (!p) + die ("invalid line %d (no timestamp)"); + event_type = strtok (NULL, " "); + if (!event_type) + die ("invalid line %d (no event type)"); + address = strtok (NULL, " "); + if (!address) + die ("invalid line %d (no address"); + if (usb_bus || usb_dev) + { + int bus, dev; + + p = strchr (address, ':'); + if (!p) + die ("invalid line %d (invalid address"); + p++; + bus = atoi (p); + p = strchr (p, ':'); + if (!p) + die ("invalid line %d (invalid address"); + p++; + dev = atoi (p); + + if ((usb_bus && usb_bus != bus) || (usb_dev && usb_dev != dev)) + return; /* We don't want that one. */ + } + if (*address != 'B' || (address[1] != 'o' && address[1] != 'i')) + return; /* We only want block in and block out. */ + status = strtok (NULL, " "); + if (!status) + return; + if (!strchr ("-0123456789", *status)) + return; /* Setup packet. */ + /* We don't support "Z[io]" types thus we don't need to check here. */ + p = strtok (NULL, " "); + if (!p) + return; /* No data length. */ + + datatag = strtok (NULL, " "); + if (datatag && *datatag == '=') + { + data = strtok (NULL, ""); + collect_data (data?data:"", address, lineno); + } +} + + +static void +parse_line_sniffusb (char *line, unsigned int lineno) +{ + char *p; + + if (debug) + printf ("line[%u] ='%s'\n", lineno, line); + + p = strtok (line, " \t"); + if (!p) + return; + p = strtok (NULL, " \t"); + if (!p) + return; + p = strtok (NULL, " \t"); + if (!p) + return; + + if (hexdigitp (p+0) && hexdigitp (p+1) + && hexdigitp (p+2) && hexdigitp (p+3) + && p[4] == ':' && !p[5]) + { + size_t length; + unsigned int value; + + length = databuffer.count; + while ((p=strtok (NULL, " \t"))) + { + if (!hexdigitp (p+0) || !hexdigitp (p+1)) + { + err ("invalid hex digit in line %u (%s)", lineno,p); + break; + } + value = xtoi_1 (p[0]) * 16 + xtoi_1 (p[1]); + + if (length >= sizeof (databuffer.data)) + { + err ("too much data at line %u - can handle only up to % bytes", + lineno, sizeof (databuffer.data)); + break; + } + databuffer.data[length++] = value; + } + databuffer.count = length; + + } + else if (!strcmp (p, "TransferFlags")) + { + flush_data (); + + *databuffer.address = 0; + while ((p=strtok (NULL, " \t(,)"))) + { + if (!strcmp (p, "USBD_TRANSFER_DIRECTION_IN")) + { + databuffer.is_bi = 1; + break; + } + else if (!strcmp (p, "USBD_TRANSFER_DIRECTION_OUT")) + { + databuffer.is_bi = 0; + break; + } + } + } + +} + + +static void +parse_input (FILE *fp) +{ + char line[2000]; + size_t length; + unsigned int lineno = 0; + + while (fgets (line, sizeof (line), fp)) + { + lineno++; + length = strlen (line); + if (length && line[length - 1] == '\n') + line[--length] = 0; + else + err ("line number %u too long or last line not terminated", lineno); + if (length && line[length - 1] == '\r') + line[--length] = 0; + if (sniffusb) + parse_line_sniffusb (line, lineno); + else + parse_line (line, lineno); + } + flush_data (); + if (ferror (fp)) + err ("error reading input at line %u: %s", lineno, strerror (errno)); +} + + +int +main (int argc, char **argv) +{ + int last_argc = -1; + + if (argc) + { + argc--; argv++; + } + while (argc && last_argc != argc ) + { + last_argc = argc; + if (!strcmp (*argv, "--")) + { + argc--; argv++; + break; + } + else if (!strcmp (*argv, "--version")) + { + fputs (PGM " (" GNUPG_NAME ") " PACKAGE_VERSION "\n", stdout); + exit (0); + } + else if (!strcmp (*argv, "--help")) + { + puts ("Usage: " PGM " [BUS:DEV]\n" + "Parse the output of usbmod assuming it is CCID compliant.\n\n" + " --skip-escape do not show escape packets\n" + " --sniffusb Assume output from Sniffusb.exe\n" + " --verbose enable extra informational output\n" + " --debug enable additional debug output\n" + " --help display this help and exit\n\n" + "Report bugs to " PACKAGE_BUGREPORT "."); + exit (0); + } + else if (!strcmp (*argv, "--verbose")) + { + verbose = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--debug")) + { + verbose = debug = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--skip-escape")) + { + skip_escape = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--sniffusb")) + { + sniffusb = 1; + argc--; argv++; + } + } + + if (argc && sniffusb) + die ("no arguments expected when using --sniffusb\n"); + else if (argc > 1) + die ("usage: " PGM " [BUS:DEV] (try --help for more information)\n"); + + if (argc == 1) + { + const char *s = strchr (argv[0], ':'); + + usb_bus = atoi (argv[0]); + if (s) + usb_dev = atoi (s+1); + if (usb_bus < 1 || usb_bus > 999 || usb_dev < 1 || usb_dev > 999) + die ("invalid bus:dev specified"); + } + + + signal (SIGPIPE, SIG_IGN); + + parse_input (stdin); + + return any_error? 1:0; +} + + +/* +Local Variables: +compile-command: "gcc -Wall -Wno-pointer-sign -g -o ccidmon ccidmon.c" +End: +*/ diff --git a/tools/clean-sat.c b/tools/clean-sat.c new file mode 100644 index 0000000..4848f97 --- /dev/null +++ b/tools/clean-sat.c @@ -0,0 +1,35 @@ +/* clean-sat.c + * Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. + * + * This file is free software; as a special exception the author gives + * unlimited permission to copy and/or distribute it, with or without + * modifications, as long as this notice is preserved. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include <stdio.h> + +int +main(int argc, char **argv) +{ + int c; + + (void)argv; + + if( argc > 1 ) { + fprintf(stderr, "no arguments, please\n"); + return 1; + } + + while( (c=getchar()) == '\n' ) + ; + while( c != EOF ) { + putchar(c); + c = getchar(); + } + + return 0; +} diff --git a/tools/convert-from-106 b/tools/convert-from-106 new file mode 100755 index 0000000..173793b --- /dev/null +++ b/tools/convert-from-106 @@ -0,0 +1,55 @@ +#!/bin/sh +# Copyright (C) 2002, 2004 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 <http://www.gnu.org/licenses/>. +if ! gpg --version > /dev/null 2>&1 ; then + echo "GnuPG not available!" + exit 1 +fi + +gpg="gpg --no-greeting --no-secmem-warning" + +echo "This script converts your public keyring and trustdb from GnuPG" +echo "1.0.6 or earlier to the 1.0.7 and later format." + +echo "If you have already done this, there is no harm (but no point)" +echo "in doing it again." + +echo -n "Continue? (y/N)" + +read answer + +if test "x$answer" != "xy" ; then + exit 0 +fi + +echo +echo "Marking your keys as ultimately trusted" +for key in `$gpg --with-colons --list-secret-keys | grep sec: | cut -d: -f5` +do + $gpg --trusted-key $key --with-colons --list-keys $key > /dev/null 2>&1 + echo -n "." +done +echo + +echo +echo "Adding signature caches" +$gpg --rebuild-keydb-caches + +echo +echo "Checking trustdb" +$gpg --check-trustdb + +echo +echo "Done!" diff --git a/tools/gpg-check-pattern-w32info.rc b/tools/gpg-check-pattern-w32info.rc new file mode 100644 index 0000000..ed50301 --- /dev/null +++ b/tools/gpg-check-pattern-w32info.rc @@ -0,0 +1,52 @@ +/* gpg-check-pattern-w32info.rc -*- c -*- + * Copyright (C) 2020 g10 Code GmbH + * + * This file is free software; as a special exception the author gives + * unlimited permission to copy and/or distribute it, with or without + * modifications, as long as this notice is preserved. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "afxres.h" +#include "../common/w32info-rc.h" + +1 ICON "../common/gnupg.ico" + +1 VERSIONINFO + FILEVERSION W32INFO_VI_FILEVERSION + PRODUCTVERSION W32INFO_VI_PRODUCTVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ + FILETYPE 0x1L /* VFT_APP (0x1) */ + FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ + BEGIN + VALUE "FileDescription", L"GnuPG\x2019s pattern checker\0" + VALUE "InternalName", "gpg-check-pattern\0" + VALUE "OriginalFilename", "gpg-check-pattern.exe\0" + VALUE "ProductName", W32INFO_PRODUCTNAME + VALUE "ProductVersion", W32INFO_PRODUCTVERSION + VALUE "CompanyName", W32INFO_COMPANYNAME + VALUE "FileVersion", W32INFO_FILEVERSION + VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT + VALUE "Comments", W32INFO_COMMENTS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END + END + +1 RT_MANIFEST "gpg-check-pattern.w32-manifest" diff --git a/tools/gpg-check-pattern.c b/tools/gpg-check-pattern.c new file mode 100644 index 0000000..77176cc --- /dev/null +++ b/tools/gpg-check-pattern.c @@ -0,0 +1,633 @@ +/* gpg-check-pattern.c - A tool to check passphrases against pattern. + * Copyright (C) 2021 g10 Code GmbH + * Copyright (C) 2007 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#ifdef HAVE_LOCALE_H +# include <locale.h> +#endif +#ifdef HAVE_LANGINFO_CODESET +# include <langinfo.h> +#endif +#ifdef HAVE_DOSISH_SYSTEM +# include <fcntl.h> /* for setmode() */ +#endif +#include <sys/stat.h> +#include <sys/types.h> +#include <ctype.h> + +#include "../common/util.h" +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "../common/init.h" +#include "../regexp/jimregexp.h" + + +enum cmd_and_opt_values +{ aNull = 0, + oVerbose = 'v', + + oNull = '0', + + oNoVerbose = 500, + oCheck, + + oHomedir +}; + + +/* The list of commands and options. */ +static ARGPARSE_OPTS opts[] = { + + { 301, NULL, 0, N_("@Options:\n ") }, + + { oVerbose, "verbose", 0, "verbose" }, + + { oHomedir, "homedir", 2, "@" }, + { oCheck, "check", 0, "run only a syntax check on the patternfile" }, + { oNull, "null", 0, "input is expected to be null delimited" }, + + ARGPARSE_end () +}; + + +/* Global options are accessed through the usual OPT structure. */ +static struct +{ + int verbose; + const char *homedir; + int checkonly; + int null; +} opt; + + +enum { + PAT_NULL, /* Indicates end of the array. */ + PAT_STRING, /* The pattern is a simple string. */ + PAT_REGEX /* The pattern is an extended regualr expression. */ +}; + + +/* An object to decibe an item of our pattern table. */ +struct pattern_s +{ + int type; + unsigned int lineno; /* Line number of the pattern file. */ + unsigned int newblock; /* First pattern in a new block. */ + unsigned int icase:1; /* Case insensitive match. */ + unsigned int accept:1; /* In accept mode. */ + unsigned int reverse:1; /* Reverse the outcome of a regexp match. */ + union { + struct { + const char *string; /* Pointer to the actual string (nul termnated). */ + size_t length; /* The length of this string (strlen). */ + } s; /*PAT_STRING*/ + struct { + /* We allocate the regex_t because this type is larger than what + we need for PAT_STRING and we expect only a few regex in a + patternfile. It would be a waste of core to have so many + unused stuff in the table. */ + regex_t *regex; + } r; /*PAT_REGEX*/ + } u; +}; +typedef struct pattern_s pattern_t; + + + +/*** Local prototypes ***/ +static char *read_file (const char *fname, size_t *r_length); +static pattern_t *parse_pattern_file (char *data, size_t datalen); +static void process (FILE *fp, pattern_t *patarray); + + + + +/* Info function for usage(). */ +static const char * +my_strusage (int level) +{ + const char *p; + switch (level) + { + case 9: p = "GPL-3.0-or-later"; break; + case 11: p = "gpg-check-pattern (@GnuPG@)"; + break; + case 13: p = VERSION; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; + + case 1: + case 40: + p = _("Usage: gpg-check-pattern [options] patternfile (-h for help)\n"); + break; + case 41: + p = _("Syntax: gpg-check-pattern [options] patternfile\n" + "Check a passphrase given on stdin against the patternfile\n"); + break; + + default: p = NULL; + } + return p; +} + + +int +main (int argc, char **argv ) +{ + ARGPARSE_ARGS pargs; + char *raw_pattern; + size_t raw_pattern_length; + pattern_t *patternarray; + + early_system_init (); + set_strusage (my_strusage); + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + log_set_prefix ("gpg-check-pattern", GPGRT_LOG_WITH_PREFIX); + + /* Make sure that our subsystems are ready. */ + i18n_init (); + init_common_subsystems (&argc, &argv); + + setup_libgcrypt_logging (); + gcry_control (GCRYCTL_INIT_SECMEM, 4096, 0); + + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= ARGPARSE_FLAG_KEEP; + while (gnupg_argparse (NULL, &pargs, opts)) + { + switch (pargs.r_opt) + { + case oVerbose: opt.verbose++; break; + case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; + case oCheck: opt.checkonly = 1; break; + case oNull: opt.null = 1; break; + + default : pargs.err = 2; break; + } + } + gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ + + if (log_get_errorcount(0)) + exit (2); + + if (argc != 1) + usage (1); + + /* We read the entire pattern file into our memory and parse it + using a separate function. This allows us to eventually do the + reading while running setuid so that the pattern file can be + hidden from regular users. I am not sure whether this makes + sense, but lets be prepared for it. */ + raw_pattern = read_file (*argv, &raw_pattern_length); + if (!raw_pattern) + exit (2); + + patternarray = parse_pattern_file (raw_pattern, raw_pattern_length); + if (!patternarray) + exit (1); + if (opt.checkonly) + return 0; + +#ifdef HAVE_DOSISH_SYSTEM + setmode (fileno (stdin) , O_BINARY ); +#endif + process (stdin, patternarray); + + return 4; /*NOTREACHED*/ +} + + + +/* Read a file FNAME into a buffer and return that malloced buffer. + Caller must free the buffer. On error NULL is returned, on success + the valid length of the buffer is stored at R_LENGTH. The returned + buffer is guarnteed to be nul terminated. */ +static char * +read_file (const char *fname, size_t *r_length) +{ + estream_t fp; + char *buf; + size_t buflen; + + if (!strcmp (fname, "-")) + { + size_t nread, bufsize = 0; + + fp = es_stdin; + es_set_binary (fp); + buf = NULL; + buflen = 0; +#define NCHUNK 8192 + do + { + bufsize += NCHUNK; + if (!buf) + buf = xmalloc (bufsize+1); + else + buf = xrealloc (buf, bufsize+1); + + nread = es_fread (buf+buflen, 1, NCHUNK, fp); + if (nread < NCHUNK && es_ferror (fp)) + { + log_error ("error reading '[stdin]': %s\n", strerror (errno)); + xfree (buf); + return NULL; + } + buflen += nread; + } + while (nread == NCHUNK); +#undef NCHUNK + + } + else + { + struct stat st; + + fp = es_fopen (fname, "rb"); + if (!fp) + { + log_error ("can't open '%s': %s\n", fname, strerror (errno)); + return NULL; + } + + if (fstat (es_fileno (fp), &st)) + { + log_error ("can't stat '%s': %s\n", fname, strerror (errno)); + es_fclose (fp); + return NULL; + } + + buflen = st.st_size; + buf = xmalloc (buflen+1); + if (es_fread (buf, buflen, 1, fp) != 1) + { + log_error ("error reading '%s': %s\n", fname, strerror (errno)); + es_fclose (fp); + xfree (buf); + return NULL; + } + es_fclose (fp); + } + buf[buflen] = 0; + *r_length = buflen; + return buf; +} + + + +static char * +get_regerror (int errcode, regex_t *compiled) +{ + size_t length = regerror (errcode, compiled, NULL, 0); + char *buffer = xmalloc (length); + regerror (errcode, compiled, buffer, length); + return buffer; +} + + +/* Parse the pattern given in the memory aread DATA/DATALEN and return + a new pattern array. The end of the array is indicated by a NULL + entry. On error an error message is printed and the function + returns NULL. Note that the function modifies DATA and assumes + that data is nul terminated (even if this is one byte past + DATALEN). */ +static pattern_t * +parse_pattern_file (char *data, size_t datalen) +{ + char *p, *p2; + size_t n; + pattern_t *array; + size_t arraysize, arrayidx; + unsigned int lineno = 0; + unsigned int icase_mode = 1; + unsigned int accept_mode = 0; + unsigned int newblock = 1; /* The first implict block. */ + + /* Estimate the number of entries by counting the non-comment lines. */ + arraysize = 0; + p = data; + for (n = datalen; n && (p2 = memchr (p, '\n', n)); p2++, n -= p2 - p, p = p2) + if (*p != '#') + arraysize++; + arraysize += 2; /* For the terminating NULL and a last line w/o a LF. */ + + array = xcalloc (arraysize, sizeof *array); + arrayidx = 0; + + /* Loop over all lines. */ + while (datalen && data) + { + lineno++; + p = data; + p2 = data = memchr (p, '\n', datalen); + if (p2) + { + *data++ = 0; + datalen -= data - p; + } + else + p2 = p + datalen; + log_assert (!*p2); + p2--; + while (isascii (*p) && isspace (*p)) + p++; + if (*p == '#') + continue; + while (p2 > p && isascii (*p2) && isspace (*p2)) + *p2-- = 0; + if (!*p) + continue; + if (!strcmp (p, "[case]")) + { + icase_mode = 0; + continue; + } + if (!strcmp (p, "[icase]")) + { + icase_mode = 1; + continue; + } + if (!strcmp (p, "[accept]")) + { + accept_mode = 1; + newblock = 1; + continue; + } + if (!strcmp (p, "[reject]")) + { + accept_mode = 0; + newblock = 1; + continue; + } + + log_assert (arrayidx < arraysize); + array[arrayidx].lineno = lineno; + array[arrayidx].icase = icase_mode; + array[arrayidx].accept = accept_mode; + array[arrayidx].reverse = 0; + array[arrayidx].newblock = newblock; + newblock = 0; + + if (*p == '/' || (*p == '!' && p[1] == '/')) + { + int rerr; + int reverse; + + reverse = (*p == '!'); + p++; + if (reverse) + p++; + array[arrayidx].type = PAT_REGEX; + if (*p && p[strlen(p)-1] == '/') + p[strlen(p)-1] = 0; /* Remove optional delimiter. */ + array[arrayidx].u.r.regex = xcalloc (1, sizeof (regex_t)); + array[arrayidx].reverse = reverse; + rerr = regcomp (array[arrayidx].u.r.regex, p, + (array[arrayidx].icase? REG_ICASE:0)|REG_EXTENDED); + if (rerr) + { + char *rerrbuf = get_regerror (rerr, array[arrayidx].u.r.regex); + log_error ("invalid regexp at line %u: %s\n", lineno, rerrbuf); + xfree (rerrbuf); + if (!opt.checkonly) + exit (1); + } + } + else + { + if (*p == '[') + { + static int shown; + + if (!shown) + { + log_info ("future warning: do no start a string with '['" + " but use a regexp (line %u)\n", lineno); + shown = 1; + } + } + array[arrayidx].type = PAT_STRING; + array[arrayidx].u.s.string = p; + array[arrayidx].u.s.length = strlen (p); + } + + arrayidx++; + } + log_assert (arrayidx < arraysize); + array[arrayidx].type = PAT_NULL; + + if (lineno && newblock) + log_info ("warning: pattern list ends with a singleton" + " accept or reject tag\n"); + + return array; +} + + +/* Check whether string matches any of the pattern in PATARRAY and + returns the matching pattern item or NULL. */ +static pattern_t * +match_p (const char *string, pattern_t *patarray) +{ + pattern_t *pat; + int match; + int accept_match; /* Tracks matchinf state in an accept block. */ + int accept_skip; /* Skip remaining patterns in an accept block. */ + + if (!*string) + { + if (opt.verbose) + log_info ("zero length input line - ignored\n"); + return NULL; + } + + accept_match = 0; + accept_skip = 0; + for (pat = patarray; pat->type != PAT_NULL; pat++) + { + match = 0; + if (pat->newblock) + accept_match = accept_skip = 0; + + if (pat->type == PAT_STRING) + { + if (pat->icase) + { + if (!strcasecmp (pat->u.s.string, string)) + match = 1; + } + else + { + if (!strcmp (pat->u.s.string, string)) + match = 1; + } + } + else if (pat->type == PAT_REGEX) + { + int rerr; + + rerr = regexec (pat->u.r.regex, string, 0, NULL, 0); + if (pat->reverse) + { + if (!rerr) + rerr = REG_NOMATCH; + else if (rerr == REG_NOMATCH) + rerr = 0; + } + + if (!rerr) + match = 1; + else if (rerr != REG_NOMATCH) + { + char *rerrbuf = get_regerror (rerr, pat->u.r.regex); + log_error ("matching regexp failed: %s\n", rerrbuf); + xfree (rerrbuf); + if (pat->accept) + match = 0; /* Better indicate no match on error. */ + else + match = 1; /* Better indicate a match on error. */ + } + } + else + BUG (); + + if (pat->accept) + { + /* Accept mode: all patterns in the accept block must match. + * Thus we need to check whether the next pattern has a + * transition and act only then. */ + if (match && !accept_skip) + accept_match = 1; + else + { + accept_match = 0; + accept_skip = 1; + } + + if (pat[1].type == PAT_NULL || pat[1].newblock) + { + /* Transition detected. Note that this also handles the + * end of pattern loop case. */ + if (accept_match) + return pat; + /* The next is not really but we do it for clarity. */ + accept_match = accept_skip = 0; + } + } + else /* Reject mode: Return true on the first match. */ + { + if (match) + return pat; + } + } + return NULL; +} + + +/* Actual processing of the input. This function does not return an + error code but exits as soon as a match has been found. */ +static void +process (FILE *fp, pattern_t *patarray) +{ + char buffer[2048]; + size_t idx; + int c; + unsigned long lineno = 0; + pattern_t *pat; + int last_is_accept; + + idx = 0; + c = 0; + while (idx < sizeof buffer -1 && c != EOF ) + { + if ((c = getc (fp)) != EOF) + buffer[idx] = c; + if ((c == '\n' && !opt.null) || (!c && opt.null) || c == EOF) + { + lineno++; + if (!opt.null) + { + while (idx && isascii (buffer[idx-1]) && isspace (buffer[idx-1])) + idx--; + } + buffer[idx] = 0; + pat = match_p (buffer, patarray); + if (pat) + { + /* Note that the accept mode works correctly only with + * one input line. */ + if (opt.verbose) + log_info ("input line %lu matches pattern at line %u" + " - %s\n", + lineno, pat->lineno, + pat->accept? "accepted":"rejected"); + } + idx = 0; + wipememory (buffer, sizeof buffer); + if (pat) + { + if (pat->accept) + exit (0); + else + exit (1); + } + } + else + idx++; + } + wipememory (buffer, sizeof buffer); + if (c != EOF) + { + log_error ("input line %lu too long - rejected\n", lineno+1); + exit (1); + } + if (ferror (fp)) + { + log_error ("input read error at line %lu: %s - rejected\n", + lineno+1, strerror (errno)); + exit (1); + } + + /* Check last pattern to see whether we are in accept mode. */ + last_is_accept = 0; + for (pat = patarray; pat->type != PAT_NULL; pat++) + last_is_accept = pat->accept; + + if (opt.verbose) + log_info ("no input line matches the pattern - %s\n", + last_is_accept? "rejected":"accepted"); + + if (log_get_errorcount(0)) + exit (2); /* Ooops - reject. */ + else if (last_is_accept) + exit (1); /* Reject */ + else + exit (0); /* Accept */ +} diff --git a/tools/gpg-check-pattern.w32-manifest.in b/tools/gpg-check-pattern.w32-manifest.in new file mode 100644 index 0000000..2a5f8ec --- /dev/null +++ b/tools/gpg-check-pattern.w32-manifest.in @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<description>GNU Privacy Guard (Pattern checker)</description> +<assemblyIdentity + type="win32" + name="GnuPG.gpg-check-pattern" + version="@BUILD_VERSION@" + /> +<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/><!-- 10 --> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/><!-- 8.1 --> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/><!-- 8 --> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/><!-- 7 --> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/><!-- Vista --> + </application> +</compatibility> +</assembly> diff --git a/tools/gpg-connect-agent-w32info.rc b/tools/gpg-connect-agent-w32info.rc new file mode 100644 index 0000000..06612f8 --- /dev/null +++ b/tools/gpg-connect-agent-w32info.rc @@ -0,0 +1,52 @@ +/* gpg-connect-agent-w32info.rc -*- c -*- + * Copyright (C) 2013 g10 Code GmbH + * + * This file is free software; as a special exception the author gives + * unlimited permission to copy and/or distribute it, with or without + * modifications, as long as this notice is preserved. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "afxres.h" +#include "../common/w32info-rc.h" + +1 ICON "../common/gnupg.ico" + +1 VERSIONINFO + FILEVERSION W32INFO_VI_FILEVERSION + PRODUCTVERSION W32INFO_VI_PRODUCTVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ + FILETYPE 0x1L /* VFT_APP (0x1) */ + FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ + BEGIN + VALUE "FileDescription", L"GnuPG\x2019s IPC tool\0" + VALUE "InternalName", "gpg-connect-agent\0" + VALUE "OriginalFilename", "gpg-connect-agent.exe\0" + VALUE "ProductName", W32INFO_PRODUCTNAME + VALUE "ProductVersion", W32INFO_PRODUCTVERSION + VALUE "CompanyName", W32INFO_COMPANYNAME + VALUE "FileVersion", W32INFO_FILEVERSION + VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT + VALUE "Comments", W32INFO_COMMENTS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END + END + +1 RT_MANIFEST "gpg-connect-agent.w32-manifest" diff --git a/tools/gpg-connect-agent.c b/tools/gpg-connect-agent.c new file mode 100644 index 0000000..eecfd67 --- /dev/null +++ b/tools/gpg-connect-agent.c @@ -0,0 +1,2266 @@ +/* gpg-connect-agent.c - Tool to connect to the agent. + * Copyright (C) 2005, 2007, 2008, 2010 Free Software Foundation, Inc. + * Copyright (C) 2014 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <assuan.h> +#include <unistd.h> +#include <assert.h> + +#include "../common/i18n.h" +#include "../common/util.h" +#include "../common/asshelp.h" +#include "../common/sysutils.h" +#include "../common/membuf.h" +#include "../common/ttyio.h" +#ifdef HAVE_W32_SYSTEM +# include "../common/exechelp.h" +#endif +#include "../common/init.h" + + +#define CONTROL_D ('D' - 'A' + 1) +#define octdigitp(p) (*(p) >= '0' && *(p) <= '7') + +/* Constants to identify the commands and options. */ +enum cmd_and_opt_values + { + aNull = 0, + oQuiet = 'q', + oVerbose = 'v', + oRawSocket = 'S', + oTcpSocket = 'T', + oExec = 'E', + oRun = 'r', + oSubst = 's', + + oNoVerbose = 500, + oHomedir, + oAgentProgram, + oDirmngrProgram, + oHex, + oDecode, + oNoExtConnect, + oDirmngr, + oUIServer, + oNoAutostart, + + }; + + +/* The list of commands and options. */ +static ARGPARSE_OPTS opts[] = { + ARGPARSE_group (301, N_("@\nOptions:\n ")), + + ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), + ARGPARSE_s_n (oQuiet, "quiet", N_("quiet")), + ARGPARSE_s_n (oHex, "hex", N_("print data out hex encoded")), + ARGPARSE_s_n (oDecode,"decode", N_("decode received data lines")), + ARGPARSE_s_n (oDirmngr,"dirmngr", N_("connect to the dirmngr")), + ARGPARSE_s_n (oUIServer, "uiserver", "@"), + ARGPARSE_s_s (oRawSocket, "raw-socket", + N_("|NAME|connect to Assuan socket NAME")), + ARGPARSE_s_s (oTcpSocket, "tcp-socket", + N_("|ADDR|connect to Assuan server at ADDR")), + ARGPARSE_s_n (oExec, "exec", + N_("run the Assuan server given on the command line")), + ARGPARSE_s_n (oNoExtConnect, "no-ext-connect", + N_("do not use extended connect mode")), + ARGPARSE_s_s (oRun, "run", + N_("|FILE|run commands from FILE on startup")), + ARGPARSE_s_n (oSubst, "subst", N_("run /subst on startup")), + + ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), + ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"), + ARGPARSE_s_s (oHomedir, "homedir", "@" ), + ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), + ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"), + + ARGPARSE_end () +}; + + +/* We keep all global options in the structure OPT. */ +struct +{ + int verbose; /* Verbosity level. */ + int quiet; /* Be extra quiet. */ + int autostart; /* Start the server if not running. */ + const char *homedir; /* Configuration directory name */ + const char *agent_program; /* Value of --agent-program. */ + const char *dirmngr_program; /* Value of --dirmngr-program. */ + int hex; /* Print data lines in hex format. */ + int decode; /* Decode received data lines. */ + int use_dirmngr; /* Use the dirmngr and not gpg-agent. */ + int use_uiserver; /* Use the standard UI server. */ + const char *raw_socket; /* Name of socket to connect in raw mode. */ + const char *tcp_socket; /* Name of server to connect in tcp mode. */ + int exec; /* Run the pgm given on the command line. */ + unsigned int connect_flags; /* Flags used for connecting. */ + int enable_varsubst; /* Set if variable substitution is enabled. */ + int trim_leading_spaces; +} opt; + + + +/* Definitions for /definq commands and a global linked list with all + the definitions. */ +struct definq_s +{ + struct definq_s *next; + char *name; /* Name of inquiry or NULL for any name. */ + int is_var; /* True if FILE is a variable name. */ + int is_prog; /* True if FILE is a program to run. */ + char file[1]; /* Name of file or program. */ +}; +typedef struct definq_s *definq_t; + +static definq_t definq_list; +static definq_t *definq_list_tail = &definq_list; + + +/* Variable definitions and glovbal table. */ +struct variable_s +{ + struct variable_s *next; + char *value; /* Malloced value - always a string. */ + char name[1]; /* Name of the variable. */ +}; +typedef struct variable_s *variable_t; + +static variable_t variable_table; + + +/* To implement loops we store entire lines in a linked list. */ +struct loopline_s +{ + struct loopline_s *next; + char line[1]; +}; +typedef struct loopline_s *loopline_t; + + +/* This is used to store the pid of the server. */ +static pid_t server_pid = (pid_t)(-1); + +/* The current datasink file or NULL. */ +static estream_t current_datasink; + +/* A list of open file descriptors. */ +static struct +{ + int inuse; +#ifdef HAVE_W32_SYSTEM + HANDLE handle; +#endif +} open_fd_table[256]; + + +/*-- local prototypes --*/ +static char *substitute_line_copy (const char *buffer); +static int read_and_print_response (assuan_context_t ctx, int withhash, + int *r_goterr); +static assuan_context_t start_agent (void); + + + + +/* Print usage information and provide strings for help. */ +static const char * +my_strusage( int level ) +{ + const char *p; + + switch (level) + { + case 9: p = "GPL-3.0-or-later"; break; + case 11: p = "@GPG@-connect-agent (@GNUPG@)"; + break; + case 13: p = VERSION; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; + + case 1: + case 40: p = _("Usage: @GPG@-connect-agent [options] (-h for help)"); + break; + case 41: + p = _("Syntax: @GPG@-connect-agent [options]\n" + "Connect to a running agent and send commands\n"); + break; + case 31: p = "\nHome: "; break; + case 32: p = gnupg_homedir (); break; + case 33: p = "\n"; break; + + default: p = NULL; break; + } + return p; +} + + +/* Unescape STRING and returned the malloced result. The surrounding + quotes must already be removed from STRING. */ +static char * +unescape_string (const char *string) +{ + const unsigned char *s; + int esc; + size_t n; + char *buffer; + unsigned char *d; + + n = 0; + for (s = (const unsigned char*)string, esc=0; *s; s++) + { + if (esc) + { + switch (*s) + { + case 'b': + case 't': + case 'v': + case 'n': + case 'f': + case 'r': + case '"': + case '\'': + case '\\': n++; break; + case 'x': + if (s[1] && s[2] && hexdigitp (s+1) && hexdigitp (s+2)) + n++; + break; + + default: + if (s[1] && s[2] + && octdigitp (s) && octdigitp (s+1) && octdigitp (s+2)) + n++; + break; + } + esc = 0; + } + else if (*s == '\\') + esc = 1; + else + n++; + } + + buffer = xmalloc (n+1); + d = (unsigned char*)buffer; + for (s = (const unsigned char*)string, esc=0; *s; s++) + { + if (esc) + { + switch (*s) + { + case 'b': *d++ = '\b'; break; + case 't': *d++ = '\t'; break; + case 'v': *d++ = '\v'; break; + case 'n': *d++ = '\n'; break; + case 'f': *d++ = '\f'; break; + case 'r': *d++ = '\r'; break; + case '"': *d++ = '\"'; break; + case '\'': *d++ = '\''; break; + case '\\': *d++ = '\\'; break; + case 'x': + if (s[1] && s[2] && hexdigitp (s+1) && hexdigitp (s+2)) + { + s++; + *d++ = xtoi_2 (s); + s++; + } + break; + + default: + if (s[1] && s[2] + && octdigitp (s) && octdigitp (s+1) && octdigitp (s+2)) + { + *d++ = (atoi_1 (s)*64) + (atoi_1 (s+1)*8) + atoi_1 (s+2); + s += 2; + } + break; + } + esc = 0; + } + else if (*s == '\\') + esc = 1; + else + *d++ = *s; + } + *d = 0; + return buffer; +} + + +/* Do the percent unescaping and return a newly malloced string. + If WITH_PLUS is set '+' characters will be changed to space. */ +static char * +unpercent_string (const char *string, int with_plus) +{ + const unsigned char *s; + unsigned char *buffer, *p; + size_t n; + + n = 0; + for (s=(const unsigned char *)string; *s; s++) + { + if (*s == '%' && s[1] && s[2]) + { + s++; + n++; + s++; + } + else if (with_plus && *s == '+') + n++; + else + n++; + } + + buffer = xmalloc (n+1); + p = buffer; + for (s=(const unsigned char *)string; *s; s++) + { + if (*s == '%' && s[1] && s[2]) + { + s++; + *p++ = xtoi_2 (s); + s++; + } + else if (with_plus && *s == '+') + *p++ = ' '; + else + *p++ = *s; + } + *p = 0; + return (char*)buffer; +} + + + + + +static const char * +set_var (const char *name, const char *value) +{ + variable_t var; + + for (var = variable_table; var; var = var->next) + if (!strcmp (var->name, name)) + break; + if (!var) + { + var = xmalloc (sizeof *var + strlen (name)); + var->value = NULL; + strcpy (var->name, name); + var->next = variable_table; + variable_table = var; + } + xfree (var->value); + var->value = value? xstrdup (value) : NULL; + return var->value; +} + + +static void +set_int_var (const char *name, int value) +{ + char numbuf[35]; + + snprintf (numbuf, sizeof numbuf, "%d", value); + set_var (name, numbuf); +} + + +/* Return the value of a variable. That value is valid until a + variable of the name is changed. Return NULL if not found. Note + that envvars are copied to our variable list at the first access + and not at oprogram start. */ +static const char * +get_var (const char *name) +{ + variable_t var; + const char *s; + + if (!*name) + return ""; + for (var = variable_table; var; var = var->next) + if (!strcmp (var->name, name)) + break; + if (!var && (s = getenv (name))) + return set_var (name, s); + if (!var || !var->value) + return NULL; + return var->value; +} + + +/* Perform some simple arithmetic operations. Caller must release + the return value. On error the return value is NULL. */ +static char * +arithmetic_op (int operator, const char *operands) +{ + long result, value; + char numbuf[35]; + + while ( spacep (operands) ) + operands++; + if (!*operands) + return NULL; + result = strtol (operands, NULL, 0); + while (*operands && !spacep (operands) ) + operands++; + if (operator == '!') + result = !result; + + while (*operands) + { + while ( spacep (operands) ) + operands++; + if (!*operands) + break; + value = strtol (operands, NULL, 0); + while (*operands && !spacep (operands) ) + operands++; + switch (operator) + { + case '+': result += value; break; + case '-': result -= value; break; + case '*': result *= value; break; + case '/': + if (!value) + return NULL; + result /= value; + break; + case '%': + if (!value) + return NULL; + result %= value; + break; + case '!': result = !value; break; + case '|': result = result || value; break; + case '&': result = result && value; break; + default: + log_error ("unknown arithmetic operator '%c'\n", operator); + return NULL; + } + } + snprintf (numbuf, sizeof numbuf, "%ld", result); + return xstrdup (numbuf); +} + + + +/* Extended version of get_var. This returns a malloced string and + understand the function syntax: "func args". + + Defined functions are + + get - Return a value described by the next argument: + cwd - The current working directory. + homedir - The gnupg homedir. + sysconfdir - GnuPG's system configuration directory. + bindir - GnuPG's binary directory. + libdir - GnuPG's library directory. + libexecdir - GnuPG's library directory for executable files. + datadir - GnuPG's data directory. + serverpid - The PID of the current server. + + unescape ARGS + Remove C-style escapes from string. Note that "\0" and + "\x00" terminate the string implictly. Use "\x7d" to + represent the closing brace. The args start right after + the first space after the function name. + + unpercent ARGS + unpercent+ ARGS + Remove percent style ecaping from string. Note that "%00 + terminates the string implicitly. Use "%7d" to represetn + the closing brace. The args start right after the first + space after the function name. "unpercent+" also maps '+' + to space. + + percent ARGS + percent+ ARGS + Escape the args using the percent style. Tabs, formfeeds, + linefeeds, carriage return, and the plus sign are also + escaped. "percent+" also maps spaces to plus characters. + + errcode ARG + Assuming ARG is an integer, return the gpg-error code. + + errsource ARG + Assuming ARG is an integer, return the gpg-error source. + + errstring ARG + Assuming ARG is an integer return a formatted fpf error string. + + + Example: get_var_ext ("get sysconfdir") -> "/etc/gnupg" + + */ +static char * +get_var_ext (const char *name) +{ + static int recursion_count; + const char *s; + char *result; + char *p; + char *free_me = NULL; + int intvalue; + + if (recursion_count > 50) + { + log_error ("variables nested too deeply\n"); + return NULL; + } + + recursion_count++; + free_me = opt.enable_varsubst? substitute_line_copy (name) : NULL; + if (free_me) + name = free_me; + for (s=name; *s && !spacep (s); s++) + ; + if (!*s) + { + s = get_var (name); + result = s? xstrdup (s): NULL; + } + else if ( (s - name) == 3 && !strncmp (name, "get", 3)) + { + while ( spacep (s) ) + s++; + if (!strcmp (s, "cwd")) + { + result = gnupg_getcwd (); + if (!result) + log_error ("getcwd failed: %s\n", strerror (errno)); + } + else if (!strcmp (s, "homedir")) + result = xstrdup (gnupg_homedir ()); + else if (!strcmp (s, "sysconfdir")) + result = xstrdup (gnupg_sysconfdir ()); + else if (!strcmp (s, "bindir")) + result = xstrdup (gnupg_bindir ()); + else if (!strcmp (s, "libdir")) + result = xstrdup (gnupg_libdir ()); + else if (!strcmp (s, "libexecdir")) + result = xstrdup (gnupg_libexecdir ()); + else if (!strcmp (s, "datadir")) + result = xstrdup (gnupg_datadir ()); + else if (!strcmp (s, "serverpid")) + result = xasprintf ("%d", (int)server_pid); + else + { + log_error ("invalid argument '%s' for variable function 'get'\n", s); + log_info ("valid are: cwd, " + "{home,bin,lib,libexec,data}dir, serverpid\n"); + result = NULL; + } + } + else if ( (s - name) == 8 && !strncmp (name, "unescape", 8)) + { + s++; + result = unescape_string (s); + } + else if ( (s - name) == 9 && !strncmp (name, "unpercent", 9)) + { + s++; + result = unpercent_string (s, 0); + } + else if ( (s - name) == 10 && !strncmp (name, "unpercent+", 10)) + { + s++; + result = unpercent_string (s, 1); + } + else if ( (s - name) == 7 && !strncmp (name, "percent", 7)) + { + s++; + result = percent_escape (s, "+\t\r\n\f\v"); + } + else if ( (s - name) == 8 && !strncmp (name, "percent+", 8)) + { + s++; + result = percent_escape (s, "+\t\r\n\f\v"); + for (p=result; *p; p++) + if (*p == ' ') + *p = '+'; + } + else if ( (s - name) == 7 && !strncmp (name, "errcode", 7)) + { + s++; + intvalue = (int)strtol (s, NULL, 0); + result = xasprintf ("%d", gpg_err_code (intvalue)); + } + else if ( (s - name) == 9 && !strncmp (name, "errsource", 9)) + { + s++; + intvalue = (int)strtol (s, NULL, 0); + result = xasprintf ("%d", gpg_err_source (intvalue)); + } + else if ( (s - name) == 9 && !strncmp (name, "errstring", 9)) + { + s++; + intvalue = (int)strtol (s, NULL, 0); + result = xasprintf ("%s <%s>", + gpg_strerror (intvalue), gpg_strsource (intvalue)); + } + else if ( (s - name) == 1 && strchr ("+-*/%!|&", *name)) + { + result = arithmetic_op (*name, s+1); + } + else + { + log_error ("unknown variable function '%.*s'\n", (int)(s-name), name); + result = NULL; + } + + xfree (free_me); + recursion_count--; + return result; +} + + +/* Substitute variables in LINE and return a new allocated buffer if + required. The function might modify LINE if the expanded version + fits into it. */ +static char * +substitute_line (char *buffer) +{ + char *line = buffer; + char *p, *pend; + const char *value; + size_t valuelen, n; + char *result = NULL; + char *freeme = NULL; + + while (*line) + { + p = strchr (line, '$'); + if (!p) + return result; /* No more variables. */ + + if (p[1] == '$') /* Escaped dollar sign. */ + { + memmove (p, p+1, strlen (p+1)+1); + line = p + 1; + continue; + } + if (p[1] == '{') + { + int count = 0; + + for (pend=p+2; *pend; pend++) + { + if (*pend == '{') + count++; + else if (*pend == '}') + { + if (--count < 0) + break; + } + } + if (!*pend) + return result; /* Unclosed - don't substitute. */ + } + else + { + for (pend=p+1; *pend && !spacep (pend) && *pend != '$' ; pend++) + ; + } + if (p[1] == '{' && *pend == '}') + { + int save = *pend; + *pend = 0; + freeme = get_var_ext (p+2); + value = freeme; + *pend++ = save; + } + else if (*pend) + { + int save = *pend; + *pend = 0; + value = get_var (p+1); + *pend = save; + } + else + value = get_var (p+1); + if (!value) + value = ""; + valuelen = strlen (value); + if (valuelen <= pend - p) + { + memcpy (p, value, valuelen); + p += valuelen; + n = pend - p; + if (n) + memmove (p, p+n, strlen (p+n)+1); + line = p; + } + else + { + char *src = result? result : buffer; + char *dst; + + dst = xmalloc (strlen (src) + valuelen + 1); + n = p - src; + memcpy (dst, src, n); + memcpy (dst + n, value, valuelen); + n += valuelen; + strcpy (dst + n, pend); + line = dst + n; + xfree (result); + result = dst; + } + xfree (freeme); + freeme = NULL; + } + return result; +} + +/* Same as substitute_line but do not modify BUFFER. */ +static char * +substitute_line_copy (const char *buffer) +{ + char *result, *p; + + p = xstrdup (buffer?buffer:""); + result = substitute_line (p); + if (!result) + result = p; + else + xfree (p); + return result; +} + + +static void +assign_variable (char *line, int syslet) +{ + char *name, *p, *tmp, *free_me, *buffer; + + /* Get the name. */ + name = line; + for (p=name; *p && !spacep (p); p++) + ; + if (*p) + *p++ = 0; + while (spacep (p)) + p++; + + if (!*p) + set_var (name, NULL); /* Remove variable. */ + else if (syslet) + { + free_me = opt.enable_varsubst? substitute_line_copy (p) : NULL; + if (free_me) + p = free_me; + buffer = xmalloc (4 + strlen (p) + 1); + strcpy (stpcpy (buffer, "get "), p); + tmp = get_var_ext (buffer); + xfree (buffer); + set_var (name, tmp); + xfree (tmp); + xfree (free_me); + } + else + { + tmp = opt.enable_varsubst? substitute_line_copy (p) : NULL; + if (tmp) + { + set_var (name, tmp); + xfree (tmp); + } + else + set_var (name, p); + } +} + + +static void +show_variables (void) +{ + variable_t var; + + for (var = variable_table; var; var = var->next) + if (var->value) + printf ("%-20s %s\n", var->name, var->value); +} + + +/* Store an inquire response pattern. Note, that this function may + change the content of LINE. We assume that leading white spaces + are already removed. */ +static void +add_definq (char *line, int is_var, int is_prog) +{ + definq_t d; + char *name, *p; + + /* Get name. */ + name = line; + for (p=name; *p && !spacep (p); p++) + ; + if (*p) + *p++ = 0; + while (spacep (p)) + p++; + + d = xmalloc (sizeof *d + strlen (p) ); + strcpy (d->file, p); + d->is_var = is_var; + d->is_prog = is_prog; + if ( !strcmp (name, "*")) + d->name = NULL; + else + d->name = xstrdup (name); + + d->next = NULL; + *definq_list_tail = d; + definq_list_tail = &d->next; +} + + +/* Show all inquiry definitions. */ +static void +show_definq (void) +{ + definq_t d; + + for (d=definq_list; d; d = d->next) + if (d->name) + printf ("%-20s %c %s\n", + d->name, d->is_var? 'v' : d->is_prog? 'p':'f', d->file); + for (d=definq_list; d; d = d->next) + if (!d->name) + printf ("%-20s %c %s\n", "*", + d->is_var? 'v': d->is_prog? 'p':'f', d->file); +} + + +/* Clear all inquiry definitions. */ +static void +clear_definq (void) +{ + while (definq_list) + { + definq_t tmp = definq_list->next; + xfree (definq_list->name); + xfree (definq_list); + definq_list = tmp; + } + definq_list_tail = &definq_list; +} + + +static void +do_sendfd (assuan_context_t ctx, char *line) +{ + estream_t fp; + char *name, *mode, *p; + int rc, fd; + + /* Get file name. */ + name = line; + for (p=name; *p && !spacep (p); p++) + ; + if (*p) + *p++ = 0; + while (spacep (p)) + p++; + + /* Get mode. */ + mode = p; + if (!*mode) + mode = "r"; + else + { + for (p=mode; *p && !spacep (p); p++) + ; + if (*p) + *p++ = 0; + } + + /* Open and send. */ + fp = es_fopen (name, mode); + if (!fp) + { + log_error ("can't open '%s' in \"%s\" mode: %s\n", + name, mode, strerror (errno)); + return; + } + fd = es_fileno (fp); + + if (opt.verbose) + log_error ("file '%s' opened in \"%s\" mode, fd=%d\n", + name, mode, fd); + + rc = assuan_sendfd (ctx, INT2FD (fd) ); + if (rc) + log_error ("sending descriptor %d failed: %s\n", fd, gpg_strerror (rc)); + es_fclose (fp); +} + + +static void +do_recvfd (assuan_context_t ctx, char *line) +{ + (void)ctx; + (void)line; + log_info ("This command has not yet been implemented\n"); +} + + +static void +do_open (char *line) +{ + estream_t fp; + char *varname, *name, *mode, *p; + int fd; + +#ifdef HAVE_W32_SYSTEM + if (server_pid == (pid_t)(-1)) + { + log_error ("the pid of the server is unknown\n"); + log_info ("use command \"/serverpid\" first\n"); + return; + } +#endif + + /* Get variable name. */ + varname = line; + for (p=varname; *p && !spacep (p); p++) + ; + if (*p) + *p++ = 0; + while (spacep (p)) + p++; + + /* Get file name. */ + name = p; + for (p=name; *p && !spacep (p); p++) + ; + if (*p) + *p++ = 0; + while (spacep (p)) + p++; + + /* Get mode. */ + mode = p; + if (!*mode) + mode = "r"; + else + { + for (p=mode; *p && !spacep (p); p++) + ; + if (*p) + *p++ = 0; + } + + /* Open and send. */ + fp = es_fopen (name, mode); + if (!fp) + { + log_error ("can't open '%s' in \"%s\" mode: %s\n", + name, mode, strerror (errno)); + return; + } + fd = dup (es_fileno (fp)); + if (fd >= 0 && fd < DIM (open_fd_table)) + { + open_fd_table[fd].inuse = 1; +#ifdef HAVE_W32CE_SYSTEM +# warning fixme: implement our pipe emulation. +#endif +#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM) + { + HANDLE prochandle, handle, newhandle; + + handle = (void*)_get_osfhandle (fd); + + prochandle = OpenProcess (PROCESS_DUP_HANDLE, FALSE, server_pid); + if (!prochandle) + { + log_error ("failed to open the server process\n"); + close (fd); + return; + } + + if (!DuplicateHandle (GetCurrentProcess(), handle, + prochandle, &newhandle, 0, + TRUE, DUPLICATE_SAME_ACCESS )) + { + log_error ("failed to duplicate the handle\n"); + close (fd); + CloseHandle (prochandle); + return; + } + CloseHandle (prochandle); + open_fd_table[fd].handle = newhandle; + } + if (opt.verbose) + log_info ("file '%s' opened in \"%s\" mode, fd=%d (libc=%d)\n", + name, mode, (int)open_fd_table[fd].handle, fd); + set_int_var (varname, (int)open_fd_table[fd].handle); +#else + if (opt.verbose) + log_info ("file '%s' opened in \"%s\" mode, fd=%d\n", + name, mode, fd); + set_int_var (varname, fd); +#endif + } + else + { + log_error ("can't put fd %d into table\n", fd); + if (fd != -1) + close (fd); /* Table was full. */ + } + es_fclose (fp); +} + + +static void +do_close (char *line) +{ + int fd = atoi (line); + +#ifdef HAVE_W32_SYSTEM + int i; + + for (i=0; i < DIM (open_fd_table); i++) + if ( open_fd_table[i].inuse && open_fd_table[i].handle == (void*)fd) + break; + if (i < DIM (open_fd_table)) + fd = i; + else + { + log_error ("given fd (system handle) has not been opened\n"); + return; + } +#endif + + if (fd < 0 || fd >= DIM (open_fd_table)) + { + log_error ("invalid fd\n"); + return; + } + + if (!open_fd_table[fd].inuse) + { + log_error ("given fd has not been opened\n"); + return; + } +#ifdef HAVE_W32_SYSTEM + CloseHandle (open_fd_table[fd].handle); /* Close duped handle. */ +#endif + close (fd); + open_fd_table[fd].inuse = 0; +} + + +static void +do_showopen (void) +{ + int i; + + for (i=0; i < DIM (open_fd_table); i++) + if (open_fd_table[i].inuse) + { +#ifdef HAVE_W32_SYSTEM + printf ("%-15d (libc=%d)\n", (int)open_fd_table[i].handle, i); +#else + printf ("%-15d\n", i); +#endif + } +} + + + +static gpg_error_t +getinfo_pid_cb (void *opaque, const void *buffer, size_t length) +{ + membuf_t *mb = opaque; + put_membuf (mb, buffer, length); + return 0; +} + +/* Get the pid of the server and store it locally. */ +static void +do_serverpid (assuan_context_t ctx) +{ + int rc; + membuf_t mb; + char *buffer; + + init_membuf (&mb, 100); + rc = assuan_transact (ctx, "GETINFO pid", getinfo_pid_cb, &mb, + NULL, NULL, NULL, NULL); + put_membuf (&mb, "", 1); + buffer = get_membuf (&mb, NULL); + if (rc || !buffer) + log_error ("command \"%s\" failed: %s\n", + "GETINFO pid", gpg_strerror (rc)); + else + { + server_pid = (pid_t)strtoul (buffer, NULL, 10); + if (opt.verbose) + log_info ("server's PID is %lu\n", (unsigned long)server_pid); + } + xfree (buffer); +} + + +/* Return true if the command is either "HELP" or "SCD HELP". */ +static int +help_cmd_p (const char *line) +{ + if (!ascii_strncasecmp (line, "SCD", 3) + && (spacep (line+3) || !line[3])) + { + for (line += 3; spacep (line); line++) + ; + } + + return (!ascii_strncasecmp (line, "HELP", 4) + && (spacep (line+4) || !line[4])); +} + + +/* gpg-connect-agent's entry point. */ +int +main (int argc, char **argv) +{ + ARGPARSE_ARGS pargs; + int no_more_options = 0; + assuan_context_t ctx; + char *line, *p; + char *tmpline; + size_t linesize; + int rc; + int cmderr; + const char *opt_run = NULL; + gpgrt_stream_t script_fp = NULL; + int use_tty, keep_line; + struct { + int collecting; + loopline_t head; + loopline_t *tail; + loopline_t current; + unsigned int nestlevel; + int oneshot; + char *condition; + } loopstack[20]; + int loopidx; + char **cmdline_commands = NULL; + + early_system_init (); + gnupg_rl_initialize (); + set_strusage (my_strusage); + log_set_prefix ("gpg-connect-agent", GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY); + + /* Make sure that our subsystems are ready. */ + i18n_init(); + init_common_subsystems (&argc, &argv); + + assuan_set_gpg_err_source (0); + + gnupg_init_signals (0, NULL); + + opt.autostart = 1; + opt.connect_flags = 1; + + /* Parse the command line. */ + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags = ARGPARSE_FLAG_KEEP; + while (!no_more_options && gnupg_argparse (NULL, &pargs, opts)) + { + switch (pargs.r_opt) + { + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + case oNoVerbose: opt.verbose = 0; break; + case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; + case oAgentProgram: opt.agent_program = pargs.r.ret_str; break; + case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break; + case oNoAutostart: opt.autostart = 0; break; + case oHex: opt.hex = 1; break; + case oDecode: opt.decode = 1; break; + case oDirmngr: opt.use_dirmngr = 1; break; + case oUIServer: opt.use_uiserver = 1; break; + case oRawSocket: opt.raw_socket = pargs.r.ret_str; break; + case oTcpSocket: opt.tcp_socket = pargs.r.ret_str; break; + case oExec: opt.exec = 1; break; + case oNoExtConnect: opt.connect_flags &= ~(1); break; + case oRun: opt_run = pargs.r.ret_str; break; + case oSubst: + opt.enable_varsubst = 1; + opt.trim_leading_spaces = 1; + break; + + default: pargs.err = 2; break; + } + } + gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ + + if (log_get_errorcount (0)) + exit (2); + + /* --uiserver is a shortcut for a specific raw socket. This comes + in particular handy on Windows. */ + if (opt.use_uiserver) + { + opt.raw_socket = make_absfilename (gnupg_homedir (), "S.uiserver", NULL); + } + + /* Print a warning if an argument looks like an option. */ + if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) + { + int i; + + for (i=0; i < argc; i++) + if (argv[i][0] == '-' && argv[i][1] == '-') + log_info (_("Note: '%s' is not considered an option\n"), argv[i]); + } + + + use_tty = (gnupg_isatty (fileno (stdin)) && gnupg_isatty (fileno (stdout))); + + if (opt.exec) + { + if (!argc) + { + log_error (_("option \"%s\" requires a program " + "and optional arguments\n"), "--exec" ); + exit (1); + } + } + else if (argc) + cmdline_commands = argv; + + if (opt.exec && opt.raw_socket) + { + opt.raw_socket = NULL; + log_info (_("option \"%s\" ignored due to \"%s\"\n"), + "--raw-socket", "--exec"); + } + if (opt.exec && opt.tcp_socket) + { + opt.tcp_socket = NULL; + log_info (_("option \"%s\" ignored due to \"%s\"\n"), + "--tcp-socket", "--exec"); + } + if (opt.tcp_socket && opt.raw_socket) + { + opt.tcp_socket = NULL; + log_info (_("option \"%s\" ignored due to \"%s\"\n"), + "--tcp-socket", "--raw-socket"); + } + + if (opt_run && !(script_fp = gpgrt_fopen (opt_run, "r"))) + { + log_error ("cannot open run file '%s': %s\n", + opt_run, strerror (errno)); + exit (1); + } + + + if (opt.exec) + { + assuan_fd_t no_close[3]; + + no_close[0] = assuan_fd_from_posix_fd (es_fileno (es_stderr)); + no_close[1] = assuan_fd_from_posix_fd (log_get_fd ()); + no_close[2] = ASSUAN_INVALID_FD; + + rc = assuan_new (&ctx); + if (rc) + { + log_error ("assuan_new failed: %s\n", gpg_strerror (rc)); + exit (1); + } + + rc = assuan_pipe_connect + (ctx, *argv, (const char **)argv, no_close, NULL, NULL, + (opt.connect_flags & 1) ? ASSUAN_PIPE_CONNECT_FDPASSING : 0); + if (rc) + { + log_error ("assuan_pipe_connect_ext failed: %s\n", + gpg_strerror (rc)); + exit (1); + } + + if (opt.verbose) + log_info ("server '%s' started\n", *argv); + + } + else if (opt.raw_socket) + { + rc = assuan_new (&ctx); + if (rc) + { + log_error ("assuan_new failed: %s\n", gpg_strerror (rc)); + exit (1); + } + + rc = assuan_socket_connect + (ctx, opt.raw_socket, 0, + (opt.connect_flags & 1) ? ASSUAN_SOCKET_CONNECT_FDPASSING : 0); + if (rc) + { + log_error ("can't connect to socket '%s': %s\n", + opt.raw_socket, gpg_strerror (rc)); + exit (1); + } + + if (opt.verbose) + log_info ("connection to socket '%s' established\n", opt.raw_socket); + } + else if (opt.tcp_socket) + { + char *url; + + url = xstrconcat ("assuan://", opt.tcp_socket, NULL); + + rc = assuan_new (&ctx); + if (rc) + { + log_error ("assuan_new failed: %s\n", gpg_strerror (rc)); + exit (1); + } + + rc = assuan_socket_connect (ctx, opt.tcp_socket, 0, 0); + if (rc) + { + log_error ("can't connect to server '%s': %s\n", + opt.tcp_socket, gpg_strerror (rc)); + exit (1); + } + + if (opt.verbose) + log_info ("connection to socket '%s' established\n", url); + + xfree (url); + } + else + ctx = start_agent (); + + /* See whether there is a line pending from the server (in case + assuan did not run the initial handshaking). */ + if (assuan_pending_line (ctx)) + { + rc = read_and_print_response (ctx, 0, &cmderr); + if (rc) + log_info (_("receiving line failed: %s\n"), gpg_strerror (rc) ); + } + + + for (loopidx=0; loopidx < DIM (loopstack); loopidx++) + loopstack[loopidx].collecting = 0; + loopidx = -1; + line = NULL; + linesize = 0; + keep_line = 1; + for (;;) + { + int n; + size_t maxlength = 2048; + + assert (loopidx < (int)DIM (loopstack)); + if (loopidx >= 0 && loopstack[loopidx].current) + { + keep_line = 0; + xfree (line); + line = xstrdup (loopstack[loopidx].current->line); + n = strlen (line); + /* Never go beyond of the final /end. */ + if (loopstack[loopidx].current->next) + loopstack[loopidx].current = loopstack[loopidx].current->next; + else if (!strncmp (line, "/end", 4) && (!line[4]||spacep(line+4))) + ; + else + log_fatal ("/end command vanished\n"); + } + else if (cmdline_commands && *cmdline_commands && !script_fp) + { + keep_line = 0; + xfree (line); + line = xstrdup (*cmdline_commands); + cmdline_commands++; + n = strlen (line); + if (n >= maxlength) + maxlength = 0; + } + else if (use_tty && !script_fp) + { + keep_line = 0; + xfree (line); + line = tty_get ("> "); + n = strlen (line); + if (n==1 && *line == CONTROL_D) + n = 0; + if (n >= maxlength) + maxlength = 0; + } + else + { + if (!keep_line) + { + xfree (line); + line = NULL; + linesize = 0; + keep_line = 1; + } + n = gpgrt_read_line (script_fp ? script_fp : gpgrt_stdin, + &line, &linesize, &maxlength); + } + if (n < 0) + { + log_error (_("error reading input: %s\n"), strerror (errno)); + if (script_fp) + { + gpgrt_fclose (script_fp); + script_fp = NULL; + log_error ("stopping script execution\n"); + continue; + } + exit (1); + } + if (!n) + { + /* EOF */ + if (script_fp) + { + gpgrt_fclose (script_fp); + script_fp = NULL; + if (opt.verbose) + log_info ("end of script\n"); + continue; + } + break; + } + if (!maxlength) + { + log_error (_("line too long - skipped\n")); + continue; + } + if (memchr (line, 0, n)) + log_info (_("line shortened due to embedded Nul character\n")); + if (line[n-1] == '\n') + line[n-1] = 0; + + if (opt.trim_leading_spaces) + { + const char *s = line; + + while (spacep (s)) + s++; + if (s != line) + { + for (p=line; *s;) + *p++ = *s++; + *p = 0; + n = p - line; + } + } + + if (loopidx+1 >= 0 && loopstack[loopidx+1].collecting) + { + loopline_t ll; + + ll = xmalloc (sizeof *ll + strlen (line)); + ll->next = NULL; + strcpy (ll->line, line); + *loopstack[loopidx+1].tail = ll; + loopstack[loopidx+1].tail = &ll->next; + + if (!strncmp (line, "/end", 4) && (!line[4]||spacep(line+4))) + loopstack[loopidx+1].nestlevel--; + else if (!strncmp (line, "/while", 6) && (!line[6]||spacep(line+6))) + loopstack[loopidx+1].nestlevel++; + + if (loopstack[loopidx+1].nestlevel) + continue; + /* We reached the corresponding /end. */ + loopstack[loopidx+1].collecting = 0; + loopidx++; + } + + if (*line == '/') + { + /* Handle control commands. */ + char *cmd = line+1; + + for (p=cmd; *p && !spacep (p); p++) + ; + if (*p) + *p++ = 0; + while (spacep (p)) + p++; + if (!strcmp (cmd, "let")) + { + assign_variable (p, 0); + } + else if (!strcmp (cmd, "slet")) + { + /* Deprecated - never used in a released version. */ + assign_variable (p, 1); + } + else if (!strcmp (cmd, "showvar")) + { + show_variables (); + } + else if (!strcmp (cmd, "definq")) + { + tmpline = opt.enable_varsubst? substitute_line (p) : NULL; + if (tmpline) + { + add_definq (tmpline, 1, 0); + xfree (tmpline); + } + else + add_definq (p, 1, 0); + } + else if (!strcmp (cmd, "definqfile")) + { + tmpline = opt.enable_varsubst? substitute_line (p) : NULL; + if (tmpline) + { + add_definq (tmpline, 0, 0); + xfree (tmpline); + } + else + add_definq (p, 0, 0); + } + else if (!strcmp (cmd, "definqprog")) + { + tmpline = opt.enable_varsubst? substitute_line (p) : NULL; + if (tmpline) + { + add_definq (tmpline, 0, 1); + xfree (tmpline); + } + else + add_definq (p, 0, 1); + } + else if (!strcmp (cmd, "datafile")) + { + const char *fname; + + if (current_datasink) + { + if (current_datasink != es_stdout) + es_fclose (current_datasink); + current_datasink = NULL; + } + tmpline = opt.enable_varsubst? substitute_line (p) : NULL; + fname = tmpline? tmpline : p; + if (fname && !strcmp (fname, "-")) + current_datasink = es_stdout; + else if (fname && *fname) + { + current_datasink = es_fopen (fname, "wb"); + if (!current_datasink) + log_error ("can't open '%s': %s\n", + fname, strerror (errno)); + } + xfree (tmpline); + } + else if (!strcmp (cmd, "showdef")) + { + show_definq (); + } + else if (!strcmp (cmd, "cleardef")) + { + clear_definq (); + } + else if (!strcmp (cmd, "echo")) + { + tmpline = opt.enable_varsubst? substitute_line (p) : NULL; + if (tmpline) + { + puts (tmpline); + xfree (tmpline); + } + else + puts (p); + } + else if (!strcmp (cmd, "sendfd")) + { + tmpline = opt.enable_varsubst? substitute_line (p) : NULL; + if (tmpline) + { + do_sendfd (ctx, tmpline); + xfree (tmpline); + } + else + do_sendfd (ctx, p); + continue; + } + else if (!strcmp (cmd, "recvfd")) + { + tmpline = opt.enable_varsubst? substitute_line (p) : NULL; + if (tmpline) + { + do_recvfd (ctx, tmpline); + xfree (tmpline); + } + else + do_recvfd (ctx, p); + continue; + } + else if (!strcmp (cmd, "open")) + { + tmpline = opt.enable_varsubst? substitute_line (p) : NULL; + if (tmpline) + { + do_open (tmpline); + xfree (tmpline); + } + else + do_open (p); + } + else if (!strcmp (cmd, "close")) + { + tmpline = opt.enable_varsubst? substitute_line (p) : NULL; + if (tmpline) + { + do_close (tmpline); + xfree (tmpline); + } + else + do_close (p); + } + else if (!strcmp (cmd, "showopen")) + { + do_showopen (); + } + else if (!strcmp (cmd, "serverpid")) + { + do_serverpid (ctx); + } + else if (!strcmp (cmd, "hex")) + opt.hex = 1; + else if (!strcmp (cmd, "nohex")) + opt.hex = 0; + else if (!strcmp (cmd, "decode")) + opt.decode = 1; + else if (!strcmp (cmd, "nodecode")) + opt.decode = 0; + else if (!strcmp (cmd, "subst")) + { + opt.enable_varsubst = 1; + opt.trim_leading_spaces = 1; + } + else if (!strcmp (cmd, "nosubst")) + opt.enable_varsubst = 0; + else if (!strcmp (cmd, "run")) + { + char *p2; + + for (p2=p; *p2 && !spacep (p2); p2++) + ; + if (*p2) + *p2++ = 0; + while (spacep (p2)) + p++; + if (*p2) + { + log_error ("syntax error in run command\n"); + if (script_fp) + { + gpgrt_fclose (script_fp); + script_fp = NULL; + } + } + else if (script_fp) + { + log_error ("cannot nest run commands - stop\n"); + gpgrt_fclose (script_fp); + script_fp = NULL; + } + else if (!(script_fp = gpgrt_fopen (p, "r"))) + { + log_error ("cannot open run file '%s': %s\n", + p, strerror (errno)); + } + else if (opt.verbose) + log_info ("running commands from '%s'\n", p); + } + else if (!strcmp (cmd, "while")) + { + if (loopidx+2 >= (int)DIM(loopstack)) + { + log_error ("blocks are nested too deep\n"); + /* We should better die or break all loop in this + case as recovering from this error won't be + easy. */ + } + else + { + loopstack[loopidx+1].head = NULL; + loopstack[loopidx+1].tail = &loopstack[loopidx+1].head; + loopstack[loopidx+1].current = NULL; + loopstack[loopidx+1].nestlevel = 1; + loopstack[loopidx+1].oneshot = 0; + loopstack[loopidx+1].condition = xstrdup (p); + loopstack[loopidx+1].collecting = 1; + } + } + else if (!strcmp (cmd, "if")) + { + if (loopidx+2 >= (int)DIM(loopstack)) + { + log_error ("blocks are nested too deep\n"); + } + else + { + /* Note that we need to evaluate the condition right + away and not just at the end of the block as we + do with a WHILE. */ + loopstack[loopidx+1].head = NULL; + loopstack[loopidx+1].tail = &loopstack[loopidx+1].head; + loopstack[loopidx+1].current = NULL; + loopstack[loopidx+1].nestlevel = 1; + loopstack[loopidx+1].oneshot = 1; + loopstack[loopidx+1].condition = substitute_line_copy (p); + loopstack[loopidx+1].collecting = 1; + } + } + else if (!strcmp (cmd, "end")) + { + if (loopidx < 0) + log_error ("stray /end command encountered - ignored\n"); + else + { + char *tmpcond; + const char *value; + long condition; + + /* Evaluate the condition. */ + tmpcond = xstrdup (loopstack[loopidx].condition); + if (loopstack[loopidx].oneshot) + { + xfree (loopstack[loopidx].condition); + loopstack[loopidx].condition = xstrdup ("0"); + } + tmpline = substitute_line (tmpcond); + value = tmpline? tmpline : tmpcond; + /* "true" or "yes" are commonly used to mean TRUE; + all other strings will evaluate to FALSE due to + the strtoul. */ + if (!ascii_strcasecmp (value, "true") + || !ascii_strcasecmp (value, "yes")) + condition = 1; + else + condition = strtol (value, NULL, 0); + xfree (tmpline); + xfree (tmpcond); + + if (condition) + { + /* Run loop. */ + loopstack[loopidx].current = loopstack[loopidx].head; + } + else + { + /* Cleanup. */ + while (loopstack[loopidx].head) + { + loopline_t tmp = loopstack[loopidx].head->next; + xfree (loopstack[loopidx].head); + loopstack[loopidx].head = tmp; + } + loopstack[loopidx].tail = NULL; + loopstack[loopidx].current = NULL; + loopstack[loopidx].nestlevel = 0; + loopstack[loopidx].collecting = 0; + loopstack[loopidx].oneshot = 0; + xfree (loopstack[loopidx].condition); + loopstack[loopidx].condition = NULL; + loopidx--; + } + } + } + else if (!strcmp (cmd, "bye")) + { + break; + } + else if (!strcmp (cmd, "sleep")) + { + gnupg_sleep (1); + } + else if (!strcmp (cmd, "help")) + { + puts ( +"Available commands:\n" +"/echo ARGS Echo ARGS.\n" +"/let NAME VALUE Set variable NAME to VALUE.\n" +"/showvar Show all variables.\n" +"/definq NAME VAR Use content of VAR for inquiries with NAME.\n" +"/definqfile NAME FILE Use content of FILE for inquiries with NAME.\n" +"/definqprog NAME PGM Run PGM for inquiries with NAME.\n" +"/datafile [NAME] Write all D line content to file NAME.\n" +"/showdef Print all definitions.\n" +"/cleardef Delete all definitions.\n" +"/sendfd FILE MODE Open FILE and pass descriptor to server.\n" +"/recvfd Receive FD from server and print.\n" +"/open VAR FILE MODE Open FILE and assign the file descriptor to VAR.\n" +"/close FD Close file with descriptor FD.\n" +"/showopen Show descriptors of all open files.\n" +"/serverpid Retrieve the pid of the server.\n" +"/[no]hex Enable hex dumping of received data lines.\n" +"/[no]decode Enable decoding of received data lines.\n" +"/[no]subst Enable variable substitution.\n" +"/run FILE Run commands from FILE.\n" +"/if VAR Begin conditional block controlled by VAR.\n" +"/while VAR Begin loop controlled by VAR.\n" +"/end End loop or condition\n" +"/bye Terminate gpg-connect-agent.\n" +"/help Print this help."); + } + else + log_error (_("unknown command '%s'\n"), cmd ); + + continue; + } + + if (opt.verbose && script_fp) + puts (line); + + tmpline = opt.enable_varsubst? substitute_line (line) : NULL; + if (tmpline) + { + rc = assuan_write_line (ctx, tmpline); + xfree (tmpline); + } + else + rc = assuan_write_line (ctx, line); + if (rc) + { + log_info (_("sending line failed: %s\n"), gpg_strerror (rc) ); + break; + } + if (*line == '#' || !*line) + continue; /* Don't expect a response for a comment line. */ + + rc = read_and_print_response (ctx, help_cmd_p (line), &cmderr); + if (rc) + log_info (_("receiving line failed: %s\n"), gpg_strerror (rc) ); + if ((rc || cmderr) && script_fp) + { + log_error ("stopping script execution\n"); + gpgrt_fclose (script_fp); + script_fp = NULL; + } + + + /* FIXME: If the last command was BYE or the server died for + some other reason, we won't notice until we get the next + input command. Probing the connection with a non-blocking + read could help to notice termination or other problems + early. */ + } + + if (opt.verbose) + log_info ("closing connection to agent\n"); + + /* XXX: We would like to release the context here, but libassuan + nicely says good bye to the server, which results in a SIGPIPE if + the server died. Unfortunately, libassuan does not ignore + SIGPIPE when used with UNIX sockets, hence we simply leak the + context here. */ + if (0) + assuan_release (ctx); + else + gpgrt_annotate_leaked_object (ctx); + xfree (line); + return 0; +} + + +/* Handle an Inquire from the server. Return False if it could not be + handled; in this case the caller shll complete the operation. LINE + is the complete line as received from the server. This function + may change the content of LINE. */ +static int +handle_inquire (assuan_context_t ctx, char *line) +{ + const char *name; + definq_t d; + /* FIXME: Due to the use of popen we can't easily switch to estream. */ + FILE *fp = NULL; + char buffer[1024]; + int rc, n; + + /* Skip the command and trailing spaces. */ + for (; *line && !spacep (line); line++) + ; + while (spacep (line)) + line++; + /* Get the name. */ + name = line; + for (; *line && !spacep (line); line++) + ; + if (*line) + *line++ = 0; + + /* Now match it against our list. The second loop is there to + detect the match-all entry. */ + for (d=definq_list; d; d = d->next) + if (d->name && !strcmp (d->name, name)) + break; + if (!d) + for (d=definq_list; d; d = d->next) + if (!d->name) + break; + if (!d) + { + if (opt.verbose) + log_info ("no handler for inquiry '%s' found\n", name); + return 0; + } + + if (d->is_var) + { + char *tmpvalue = get_var_ext (d->file); + if (tmpvalue) + rc = assuan_send_data (ctx, tmpvalue, strlen (tmpvalue)); + else + rc = assuan_send_data (ctx, "", 0); + xfree (tmpvalue); + if (rc) + log_error ("sending data back failed: %s\n", gpg_strerror (rc) ); + } + else + { + if (d->is_prog) + { +#ifdef HAVE_W32CE_SYSTEM + fp = NULL; +#else + fp = popen (d->file, "r"); +#endif + if (!fp) + log_error ("error executing '%s': %s\n", + d->file, strerror (errno)); + else if (opt.verbose) + log_error ("handling inquiry '%s' by running '%s'\n", + name, d->file); + } + else + { + fp = gnupg_fopen (d->file, "rb"); + if (!fp) + log_error ("error opening '%s': %s\n", d->file, strerror (errno)); + else if (opt.verbose) + log_error ("handling inquiry '%s' by returning content of '%s'\n", + name, d->file); + } + if (!fp) + return 0; + + while ( (n = fread (buffer, 1, sizeof buffer, fp)) ) + { + rc = assuan_send_data (ctx, buffer, n); + if (rc) + { + log_error ("sending data back failed: %s\n", gpg_strerror (rc) ); + break; + } + } + if (ferror (fp)) + log_error ("error reading from '%s': %s\n", d->file, strerror (errno)); + } + + rc = assuan_send_data (ctx, NULL, 0); + if (rc) + log_error ("sending data back failed: %s\n", gpg_strerror (rc) ); + + if (d->is_var) + ; + else if (d->is_prog) + { +#ifndef HAVE_W32CE_SYSTEM + if (pclose (fp)) + log_error ("error running '%s': %s\n", d->file, strerror (errno)); +#endif + } + else + fclose (fp); + return 1; +} + + +/* Read all response lines from server and print them. Returns 0 on + success or an assuan error code. If WITHHASH istrue, comment lines + are printed. Sets R_GOTERR to true if the command did not returned + OK. */ +static int +read_and_print_response (assuan_context_t ctx, int withhash, int *r_goterr) +{ + char *line; + size_t linelen; + gpg_error_t rc; + int i, j; + int need_lf = 0; + + *r_goterr = 0; + for (;;) + { + do + { + rc = assuan_read_line (ctx, &line, &linelen); + if (rc) + return rc; + + if ((withhash || opt.verbose > 1) && *line == '#') + { + fwrite (line, linelen, 1, stdout); + putchar ('\n'); + } + } + while (*line == '#' || !linelen); + + if (linelen >= 1 + && line[0] == 'D' && line[1] == ' ') + { + if (current_datasink) + { + const unsigned char *s; + int c = 0; + + for (j=2, s=(unsigned char*)line+2; j < linelen; j++, s++ ) + { + if (*s == '%' && j+2 < linelen) + { + s++; j++; + c = xtoi_2 ( s ); + s++; j++; + } + else + c = *s; + es_putc (c, current_datasink); + } + } + else if (opt.hex) + { + for (i=2; i < linelen; ) + { + int save_i = i; + + printf ("D[%04X] ", i-2); + for (j=0; j < 16 ; j++, i++) + { + if (j == 8) + putchar (' '); + if (i < linelen) + printf (" %02X", ((unsigned char*)line)[i]); + else + fputs (" ", stdout); + } + fputs (" ", stdout); + i= save_i; + for (j=0; j < 16; j++, i++) + { + unsigned int c = ((unsigned char*)line)[i]; + if ( i >= linelen ) + putchar (' '); + else if (isascii (c) && isprint (c) && !iscntrl (c)) + putchar (c); + else + putchar ('.'); + } + putchar ('\n'); + } + } + else if (opt.decode) + { + const unsigned char *s; + int need_d = 1; + int c = 0; + + for (j=2, s=(unsigned char*)line+2; j < linelen; j++, s++ ) + { + if (need_d) + { + fputs ("D ", stdout); + need_d = 0; + } + if (*s == '%' && j+2 < linelen) + { + s++; j++; + c = xtoi_2 ( s ); + s++; j++; + } + else + c = *s; + if (c == '\n') + need_d = 1; + putchar (c); + } + need_lf = (c != '\n'); + } + else + { + fwrite (line, linelen, 1, stdout); + putchar ('\n'); + } + } + else + { + if (need_lf) + { + if (!current_datasink || current_datasink != es_stdout) + putchar ('\n'); + need_lf = 0; + } + + if (linelen >= 1 + && line[0] == 'S' + && (line[1] == '\0' || line[1] == ' ')) + { + if (!current_datasink || current_datasink != es_stdout) + { + fwrite (line, linelen, 1, stdout); + putchar ('\n'); + } + } + else if (linelen >= 2 + && line[0] == 'O' && line[1] == 'K' + && (line[2] == '\0' || line[2] == ' ')) + { + if (!current_datasink || current_datasink != es_stdout) + { + fwrite (line, linelen, 1, stdout); + putchar ('\n'); + } + set_int_var ("?", 0); + return 0; + } + else if (linelen >= 3 + && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' + && (line[3] == '\0' || line[3] == ' ')) + { + int errval; + + errval = strtol (line+3, NULL, 10); + if (!errval) + errval = -1; + set_int_var ("?", errval); + if (!current_datasink || current_datasink != es_stdout) + { + fwrite (line, linelen, 1, stdout); + putchar ('\n'); + } + *r_goterr = 1; + return 0; + } + else if (linelen >= 7 + && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q' + && line[3] == 'U' && line[4] == 'I' && line[5] == 'R' + && line[6] == 'E' + && (line[7] == '\0' || line[7] == ' ')) + { + if (!current_datasink || current_datasink != es_stdout) + { + fwrite (line, linelen, 1, stdout); + putchar ('\n'); + } + if (!handle_inquire (ctx, line)) + assuan_write_line (ctx, "CANCEL"); + } + else if (linelen >= 3 + && line[0] == 'E' && line[1] == 'N' && line[2] == 'D' + && (line[3] == '\0' || line[3] == ' ')) + { + if (!current_datasink || current_datasink != es_stdout) + { + fwrite (line, linelen, 1, stdout); + putchar ('\n'); + } + /* Received from server, thus more responses are expected. */ + } + else + return gpg_error (GPG_ERR_ASS_INV_RESPONSE); + } + } +} + + + + +/* Connect to the agent and send the standard options. */ +static assuan_context_t +start_agent (void) +{ + gpg_error_t err; + assuan_context_t ctx; + session_env_t session_env; + + session_env = session_env_new (); + if (!session_env) + log_fatal ("error allocating session environment block: %s\n", + strerror (errno)); + if (opt.use_dirmngr) + err = start_new_dirmngr (&ctx, + GPG_ERR_SOURCE_DEFAULT, + opt.dirmngr_program, + opt.autostart, + !opt.quiet, 0, + NULL, NULL); + else + err = start_new_gpg_agent (&ctx, + GPG_ERR_SOURCE_DEFAULT, + opt.agent_program, + NULL, NULL, + session_env, + opt.autostart, + !opt.quiet, 0, + NULL, NULL); + + session_env_release (session_env); + if (err) + { + if (!opt.autostart + && (gpg_err_code (err) + == (opt.use_dirmngr? GPG_ERR_NO_DIRMNGR : GPG_ERR_NO_AGENT))) + { + /* In the no-autostart case we don't make gpg-connect-agent + fail on a missing server. */ + log_info (opt.use_dirmngr? + _("no dirmngr running in this session\n"): + _("no gpg-agent running in this session\n")); + exit (0); + } + else + { + log_error (_("error sending standard options: %s\n"), + gpg_strerror (err)); + exit (1); + } + } + + return ctx; +} diff --git a/tools/gpg-connect-agent.w32-manifest.in b/tools/gpg-connect-agent.w32-manifest.in new file mode 100644 index 0000000..aba5420 --- /dev/null +++ b/tools/gpg-connect-agent.w32-manifest.in @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<description>GNU Privacy Guard (IPC tool)</description> +<assemblyIdentity + type="win32" + name="GnuPG.gpg-connect-agent" + version="@BUILD_VERSION@" + /> +<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/><!-- 10 --> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/><!-- 8.1 --> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/><!-- 8 --> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/><!-- 7 --> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/><!-- Vista --> + </application> +</compatibility> +</assembly> diff --git a/tools/gpg-wks-client-w32info.rc b/tools/gpg-wks-client-w32info.rc new file mode 100644 index 0000000..60021a7 --- /dev/null +++ b/tools/gpg-wks-client-w32info.rc @@ -0,0 +1,52 @@ +/* gpg-wks-client-w32info.rc -*- c -*- + * Copyright (C) 2020 g10 Code GmbH + * + * This file is free software; as a special exception the author gives + * unlimited permission to copy and/or distribute it, with or without + * modifications, as long as this notice is preserved. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "afxres.h" +#include "../common/w32info-rc.h" + +1 ICON "../common/gnupg.ico" + +1 VERSIONINFO + FILEVERSION W32INFO_VI_FILEVERSION + PRODUCTVERSION W32INFO_VI_PRODUCTVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ + FILETYPE 0x1L /* VFT_APP (0x1) */ + FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ + BEGIN + VALUE "FileDescription", L"GnuPG\x2019s Web Key Service client\0" + VALUE "InternalName", "gpg-wks-client\0" + VALUE "OriginalFilename", "gpg-wks-client.exe\0" + VALUE "ProductName", W32INFO_PRODUCTNAME + VALUE "ProductVersion", W32INFO_PRODUCTVERSION + VALUE "CompanyName", W32INFO_COMPANYNAME + VALUE "FileVersion", W32INFO_FILEVERSION + VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT + VALUE "Comments", W32INFO_COMMENTS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END + END + +1 RT_MANIFEST "gpg-wks-client.w32-manifest" diff --git a/tools/gpg-wks-client.c b/tools/gpg-wks-client.c new file mode 100644 index 0000000..3aa8f98 --- /dev/null +++ b/tools/gpg-wks-client.c @@ -0,0 +1,1984 @@ +/* gpg-wks-client.c - A client for the Web Key Service protocols. + * Copyright (C) 2016, 2022 g10 Code GmbH + * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> + +#define INCLUDED_BY_MAIN_MODULE 1 +#include "../common/util.h" +#include "../common/status.h" +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "../common/init.h" +#include "../common/asshelp.h" +#include "../common/userids.h" +#include "../common/ccparray.h" +#include "../common/exectool.h" +#include "../common/mbox-util.h" +#include "../common/name-value.h" +#include "call-dirmngr.h" +#include "mime-maker.h" +#include "send-mail.h" +#include "gpg-wks.h" + + +/* Constants to identify the commands and options. */ +enum cmd_and_opt_values + { + aNull = 0, + + oQuiet = 'q', + oVerbose = 'v', + oOutput = 'o', + oDirectory = 'C', + + oDebug = 500, + + aSupported, + aCheck, + aCreate, + aReceive, + aRead, + aMirror, + aInstallKey, + aRemoveKey, + aPrintWKDHash, + aPrintWKDURL, + + oGpgProgram, + oSend, + oFakeSubmissionAddr, + oStatusFD, + oWithColons, + oBlacklist, + oNoAutostart, + + oDummy + }; + + +/* The list of commands and options. */ +static ARGPARSE_OPTS opts[] = { + ARGPARSE_group (300, ("@Commands:\n ")), + + ARGPARSE_c (aSupported, "supported", + ("check whether provider supports WKS")), + ARGPARSE_c (aCheck, "check", + ("check whether a key is available")), + ARGPARSE_c (aCreate, "create", + ("create a publication request")), + ARGPARSE_c (aReceive, "receive", + ("receive a MIME confirmation request")), + ARGPARSE_c (aRead, "read", + ("receive a plain text confirmation request")), + ARGPARSE_c (aMirror, "mirror", + "mirror an LDAP directory"), + ARGPARSE_c (aInstallKey, "install-key", + "install a key into a directory"), + ARGPARSE_c (aRemoveKey, "remove-key", + "remove a key from a directory"), + ARGPARSE_c (aPrintWKDHash, "print-wkd-hash", + "Print the WKD identifier for the given user ids"), + ARGPARSE_c (aPrintWKDURL, "print-wkd-url", + "Print the WKD URL for the given user id"), + + ARGPARSE_group (301, ("@\nOptions:\n ")), + + ARGPARSE_s_n (oVerbose, "verbose", ("verbose")), + ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")), + ARGPARSE_s_s (oDebug, "debug", "@"), + ARGPARSE_s_s (oGpgProgram, "gpg", "@"), + ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"), + ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"), + ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), + ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), + ARGPARSE_s_n (oWithColons, "with-colons", "@"), + ARGPARSE_s_s (oBlacklist, "blacklist", "@"), + ARGPARSE_s_s (oDirectory, "directory", "@"), + + ARGPARSE_s_s (oFakeSubmissionAddr, "fake-submission-addr", "@"), + + ARGPARSE_end () +}; + + +/* The list of supported debug flags. */ +static struct debug_flags_s debug_flags [] = + { + { DBG_MIME_VALUE , "mime" }, + { DBG_PARSER_VALUE , "parser" }, + { DBG_CRYPTO_VALUE , "crypto" }, + { DBG_MEMORY_VALUE , "memory" }, + { DBG_MEMSTAT_VALUE, "memstat" }, + { DBG_IPC_VALUE , "ipc" }, + { DBG_EXTPROG_VALUE, "extprog" }, + { 0, NULL } + }; + + + +/* Value of the option --fake-submission-addr. */ +const char *fake_submission_addr; + +/* An array with blacklisted addresses and its length. Use + * is_in_blacklist to check. */ +static char **blacklist_array; +static size_t blacklist_array_len; + + +static void wrong_args (const char *text) GPGRT_ATTR_NORETURN; +static void add_blacklist (const char *fname); +static gpg_error_t proc_userid_from_stdin (gpg_error_t (*func)(const char *), + const char *text); +static gpg_error_t command_supported (char *userid); +static gpg_error_t command_check (char *userid); +static gpg_error_t command_send (const char *fingerprint, const char *userid); +static gpg_error_t encrypt_response (estream_t *r_output, estream_t input, + const char *addrspec, + const char *fingerprint); +static gpg_error_t read_confirmation_request (estream_t msg); +static gpg_error_t command_receive_cb (void *opaque, + const char *mediatype, estream_t fp, + unsigned int flags); +static gpg_error_t command_mirror (char *domain[]); + + + +/* Print usage information and provide strings for help. */ +static const char * +my_strusage( int level ) +{ + const char *p; + + switch (level) + { + case 9: p = "LGPL-2.1-or-later"; break; + case 11: p = "gpg-wks-client"; break; + case 12: p = "@GNUPG@"; break; + case 13: p = VERSION; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break; + + case 1: + case 40: + p = ("Usage: gpg-wks-client [command] [options] [args] (-h for help)"); + break; + case 41: + p = ("Syntax: gpg-wks-client [command] [options] [args]\n" + "Client for the Web Key Service\n"); + break; + + default: p = NULL; break; + } + return p; +} + + +static void +wrong_args (const char *text) +{ + es_fprintf (es_stderr, _("usage: %s [options] %s\n"), strusage (11), text); + exit (2); +} + + + +/* Command line parsing. */ +static enum cmd_and_opt_values +parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) +{ + enum cmd_and_opt_values cmd = 0; + int no_more_options = 0; + + while (!no_more_options && gnupg_argparse (NULL, pargs, popts)) + { + switch (pargs->r_opt) + { + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + case oDebug: + if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags)) + { + pargs->r_opt = ARGPARSE_INVALID_ARG; + pargs->err = ARGPARSE_PRINT_ERROR; + } + break; + + case oGpgProgram: + opt.gpg_program = pargs->r.ret_str; + break; + case oDirectory: + opt.directory = pargs->r.ret_str; + break; + case oSend: + opt.use_sendmail = 1; + break; + case oOutput: + opt.output = pargs->r.ret_str; + break; + case oFakeSubmissionAddr: + fake_submission_addr = pargs->r.ret_str; + break; + case oStatusFD: + wks_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1)); + break; + case oWithColons: + opt.with_colons = 1; + break; + case oNoAutostart: + opt.no_autostart = 1; + break; + case oBlacklist: + add_blacklist (pargs->r.ret_str); + break; + + case aSupported: + case aCreate: + case aReceive: + case aRead: + case aCheck: + case aMirror: + case aInstallKey: + case aRemoveKey: + case aPrintWKDHash: + case aPrintWKDURL: + cmd = pargs->r_opt; + break; + + default: pargs->err = ARGPARSE_PRINT_ERROR; break; + } + } + + return cmd; +} + + + +/* gpg-wks-client main. */ +int +main (int argc, char **argv) +{ + gpg_error_t err, delayed_err; + ARGPARSE_ARGS pargs; + enum cmd_and_opt_values cmd; + + gnupg_reopen_std ("gpg-wks-client"); + set_strusage (my_strusage); + log_set_prefix ("gpg-wks-client", GPGRT_LOG_WITH_PREFIX); + + /* Make sure that our subsystems are ready. */ + i18n_init(); + init_common_subsystems (&argc, &argv); + + assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); + setup_libassuan_logging (&opt.debug, NULL); + + /* Parse the command line. */ + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags = ARGPARSE_FLAG_KEEP; + cmd = parse_arguments (&pargs, opts); + gnupg_argparse (NULL, &pargs, NULL); + + if (log_get_errorcount (0)) + exit (2); + + /* Print a warning if an argument looks like an option. */ + if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) + { + int i; + + for (i=0; i < argc; i++) + if (argv[i][0] == '-' && argv[i][1] == '-') + log_info (("NOTE: '%s' is not considered an option\n"), argv[i]); + } + + /* Set defaults for non given options. */ + if (!opt.gpg_program) + opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); + + if (!opt.directory) + opt.directory = "openpgpkey"; + + /* Tell call-dirmngr what options we want. */ + set_dirmngr_options (opt.verbose, (opt.debug & DBG_IPC_VALUE), + !opt.no_autostart); + + + /* Check that the top directory exists. */ + if (cmd == aInstallKey || cmd == aRemoveKey || cmd == aMirror) + { + struct stat sb; + + if (gnupg_stat (opt.directory, &sb)) + { + err = gpg_error_from_syserror (); + log_error ("error accessing directory '%s': %s\n", + opt.directory, gpg_strerror (err)); + goto leave; + } + if (!S_ISDIR(sb.st_mode)) + { + log_error ("error accessing directory '%s': %s\n", + opt.directory, "not a directory"); + err = gpg_error (GPG_ERR_ENOENT); + goto leave; + } + } + + /* Run the selected command. */ + switch (cmd) + { + case aSupported: + if (opt.with_colons) + { + for (; argc; argc--, argv++) + command_supported (*argv); + err = 0; + } + else + { + if (argc != 1) + wrong_args ("--supported DOMAIN"); + err = command_supported (argv[0]); + if (err && gpg_err_code (err) != GPG_ERR_FALSE) + log_error ("checking support failed: %s\n", gpg_strerror (err)); + } + break; + + case aCreate: + if (argc != 2) + wrong_args ("--create FINGERPRINT USER-ID"); + err = command_send (argv[0], argv[1]); + if (err) + log_error ("creating request failed: %s\n", gpg_strerror (err)); + break; + + case aReceive: + if (argc) + wrong_args ("--receive < MIME-DATA"); + err = wks_receive (es_stdin, command_receive_cb, NULL); + if (err) + log_error ("processing mail failed: %s\n", gpg_strerror (err)); + break; + + case aRead: + if (argc) + wrong_args ("--read < WKS-DATA"); + err = read_confirmation_request (es_stdin); + if (err) + log_error ("processing mail failed: %s\n", gpg_strerror (err)); + break; + + case aCheck: + if (argc != 1) + wrong_args ("--check USER-ID"); + err = command_check (argv[0]); + break; + + case aMirror: + if (!argc) + err = command_mirror (NULL); + else + err = command_mirror (argv); + break; + + case aInstallKey: + if (!argc) + err = wks_cmd_install_key (NULL, NULL); + else if (argc == 2) + err = wks_cmd_install_key (*argv, argv[1]); + else + wrong_args ("--install-key [FILE|FINGERPRINT USER-ID]"); + break; + + case aRemoveKey: + if (argc != 1) + wrong_args ("--remove-key USER-ID"); + err = wks_cmd_remove_key (*argv); + break; + + case aPrintWKDHash: + case aPrintWKDURL: + if (!argc) + { + if (cmd == aPrintWKDHash) + err = proc_userid_from_stdin (wks_cmd_print_wkd_hash, + "printing WKD hash"); + else + err = proc_userid_from_stdin (wks_cmd_print_wkd_url, + "printing WKD URL"); + } + else + { + for (err = delayed_err = 0; !err && argc; argc--, argv++) + { + if (cmd == aPrintWKDHash) + err = wks_cmd_print_wkd_hash (*argv); + else + err = wks_cmd_print_wkd_url (*argv); + if (gpg_err_code (err) == GPG_ERR_INV_USER_ID) + { + /* Diagnostic already printed. */ + delayed_err = err; + err = 0; + } + else if (err) + log_error ("printing hash failed: %s\n", gpg_strerror (err)); + } + if (!err) + err = delayed_err; + } + break; + + default: + usage (1); + err = 0; + break; + } + + leave: + if (err) + wks_write_status (STATUS_FAILURE, "- %u", err); + else if (log_get_errorcount (0)) + wks_write_status (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL); + else + wks_write_status (STATUS_SUCCESS, NULL); + return (err || log_get_errorcount (0))? 1:0; +} + + + +/* Read a file FNAME into a buffer and return that malloced buffer. + * Caller must free the buffer. On error NULL is returned, on success + * the valid length of the buffer is stored at R_LENGTH. The returned + * buffer is guaranteed to be Nul terminated. */ +static char * +read_file (const char *fname, size_t *r_length) +{ + estream_t fp; + char *buf; + size_t buflen; + + if (!strcmp (fname, "-")) + { + size_t nread, bufsize = 0; + + fp = es_stdin; + es_set_binary (fp); + buf = NULL; + buflen = 0; +#define NCHUNK 32767 + do + { + bufsize += NCHUNK; + if (!buf) + buf = xmalloc (bufsize+1); + else + buf = xrealloc (buf, bufsize+1); + + nread = es_fread (buf+buflen, 1, NCHUNK, fp); + if (nread < NCHUNK && es_ferror (fp)) + { + log_error ("error reading '[stdin]': %s\n", strerror (errno)); + xfree (buf); + return NULL; + } + buflen += nread; + } + while (nread == NCHUNK); +#undef NCHUNK + } + else + { + struct stat st; + + fp = es_fopen (fname, "rb"); + if (!fp) + { + log_error ("can't open '%s': %s\n", fname, strerror (errno)); + return NULL; + } + + if (fstat (es_fileno (fp), &st)) + { + log_error ("can't stat '%s': %s\n", fname, strerror (errno)); + es_fclose (fp); + return NULL; + } + + buflen = st.st_size; + buf = xmalloc (buflen+1); + if (es_fread (buf, buflen, 1, fp) != 1) + { + log_error ("error reading '%s': %s\n", fname, strerror (errno)); + es_fclose (fp); + xfree (buf); + return NULL; + } + es_fclose (fp); + } + buf[buflen] = 0; + if (r_length) + *r_length = buflen; + return buf; +} + + +static int +cmp_blacklist (const void *arg_a, const void *arg_b) +{ + const char *a = *(const char **)arg_a; + const char *b = *(const char **)arg_b; + return strcmp (a, b); +} + + +/* Add a blacklist to our global table. This is called during option + * parsing and thus any use of log_error will eventually stop further + * processing. */ +static void +add_blacklist (const char *fname) +{ + char *buffer; + char *p, *pend; + char **array; + size_t arraysize, arrayidx; + + buffer = read_file (fname, NULL); + if (!buffer) + return; + + /* Estimate the number of entries by counting the non-comment lines. */ + arraysize = 2; /* For the first and an extra NULL item. */ + for (p=buffer; *p; p++) + if (*p == '\n' && p[1] && p[1] != '#') + arraysize++; + + array = xcalloc (arraysize, sizeof *array); + arrayidx = 0; + + /* Loop over all lines. */ + for (p = buffer; p && *p; p = pend) + { + pend = strchr (p, '\n'); + if (pend) + *pend++ = 0; + trim_spaces (p); + if (!*p || *p == '#' ) + continue; + ascii_strlwr (p); + log_assert (arrayidx < arraysize); + array[arrayidx] = p; + arrayidx++; + } + log_assert (arrayidx < arraysize); + + qsort (array, arrayidx, sizeof *array, cmp_blacklist); + + blacklist_array = array; + blacklist_array_len = arrayidx; + gpgrt_annotate_leaked_object (buffer); + gpgrt_annotate_leaked_object (blacklist_array); +} + + +/* Return true if NAME is in a blacklist. */ +static int +is_in_blacklist (const char *name) +{ + if (!name || !blacklist_array) + return 0; + return !!bsearch (&name, blacklist_array, blacklist_array_len, + sizeof *blacklist_array, cmp_blacklist); +} + + + +/* Read user ids from stdin and call FUNC for each user id. TEXT is + * used for error messages. */ +static gpg_error_t +proc_userid_from_stdin (gpg_error_t (*func)(const char *), const char *text) +{ + gpg_error_t err = 0; + gpg_error_t delayed_err = 0; + char line[2048]; + size_t n = 0; + + /* If we are on a terminal disable buffering to get direct response. */ + if (gnupg_isatty (es_fileno (es_stdin)) + && gnupg_isatty (es_fileno (es_stdout))) + { + es_setvbuf (es_stdin, NULL, _IONBF, 0); + es_setvbuf (es_stdout, NULL, _IOLBF, 0); + } + + while (es_fgets (line, sizeof line - 1, es_stdin)) + { + n = strlen (line); + if (!n || line[n-1] != '\n') + { + err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG + : GPG_ERR_INCOMPLETE_LINE); + log_error ("error reading stdin: %s\n", gpg_strerror (err)); + break; + } + trim_spaces (line); + err = func (line); + if (gpg_err_code (err) == GPG_ERR_INV_USER_ID) + { + delayed_err = err; + err = 0; + } + else if (err) + log_error ("%s failed: %s\n", text, gpg_strerror (err)); + } + if (es_ferror (es_stdin)) + { + err = gpg_error_from_syserror (); + log_error ("error reading stdin: %s\n", gpg_strerror (err)); + goto leave; + } + + leave: + if (!err) + err = delayed_err; + return err; +} + + + + +/* Add the user id UID to the key identified by FINGERPRINT. */ +static gpg_error_t +add_user_id (const char *fingerprint, const char *uid) +{ + gpg_error_t err; + ccparray_t ccp; + const char **argv = NULL; + + ccparray_init (&ccp, 0); + + ccparray_put (&ccp, "--no-options"); + if (opt.verbose < 2) + ccparray_put (&ccp, "--quiet"); + else + ccparray_put (&ccp, "--verbose"); + ccparray_put (&ccp, "--batch"); + ccparray_put (&ccp, "--always-trust"); + ccparray_put (&ccp, "--quick-add-uid"); + ccparray_put (&ccp, fingerprint); + ccparray_put (&ccp, uid); + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL, + NULL, NULL, + NULL, NULL); + if (err) + { + log_error ("adding user id failed: %s\n", gpg_strerror (err)); + goto leave; + } + + leave: + xfree (argv); + return err; +} + + + +struct decrypt_stream_parm_s +{ + char *fpr; + char *mainfpr; + int otrust; +}; + +static void +decrypt_stream_status_cb (void *opaque, const char *keyword, char *args) +{ + struct decrypt_stream_parm_s *decinfo = opaque; + + if (DBG_CRYPTO) + log_debug ("gpg status: %s %s\n", keyword, args); + if (!strcmp (keyword, "DECRYPTION_KEY") && !decinfo->fpr) + { + char *fields[3]; + + if (split_fields (args, fields, DIM (fields)) >= 3) + { + decinfo->fpr = xstrdup (fields[0]); + decinfo->mainfpr = xstrdup (fields[1]); + decinfo->otrust = *fields[2]; + } + } +} + +/* Decrypt the INPUT stream to a new stream which is stored at success + * at R_OUTPUT. */ +static gpg_error_t +decrypt_stream (estream_t *r_output, struct decrypt_stream_parm_s *decinfo, + estream_t input) +{ + gpg_error_t err; + ccparray_t ccp; + const char **argv; + estream_t output; + + *r_output = NULL; + memset (decinfo, 0, sizeof *decinfo); + + output = es_fopenmem (0, "w+b"); + if (!output) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); + return err; + } + + ccparray_init (&ccp, 0); + + ccparray_put (&ccp, "--no-options"); + /* We limit the output to 64 KiB to avoid DoS using compression + * tricks. A regular client will anyway only send a minimal key; + * that is one w/o key signatures and attribute packets. */ + ccparray_put (&ccp, "--max-output=0x10000"); + if (opt.verbose < 2) + ccparray_put (&ccp, "--quiet"); + else + ccparray_put (&ccp, "--verbose"); + ccparray_put (&ccp, "--batch"); + ccparray_put (&ccp, "--status-fd=2"); + ccparray_put (&ccp, "--decrypt"); + ccparray_put (&ccp, "--"); + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = gnupg_exec_tool_stream (opt.gpg_program, argv, input, + NULL, output, + decrypt_stream_status_cb, decinfo); + if (!err && (!decinfo->fpr || !decinfo->mainfpr || !decinfo->otrust)) + err = gpg_error (GPG_ERR_INV_ENGINE); + if (err) + { + log_error ("decryption failed: %s\n", gpg_strerror (err)); + goto leave; + } + else if (opt.verbose) + log_info ("decryption succeeded\n"); + + es_rewind (output); + *r_output = output; + output = NULL; + + leave: + if (err) + { + xfree (decinfo->fpr); + xfree (decinfo->mainfpr); + memset (decinfo, 0, sizeof *decinfo); + } + es_fclose (output); + xfree (argv); + return err; +} + + +/* Return the submission address for the address or just the domain in + * ADDRSPEC. The submission address is stored as a malloced string at + * R_SUBMISSION_ADDRESS. At R_POLICY the policy flags of the domain + * are stored. The caller needs to free them with wks_free_policy. + * The function returns an error code on failure to find a submission + * address or policy file. Note: The function may store NULL at + * R_SUBMISSION_ADDRESS but return success to indicate that the web + * key directory is supported but not the web key service. As per WKD + * specs a policy file is always required and will thus be return on + * success. */ +static gpg_error_t +get_policy_and_sa (const char *addrspec, int silent, + policy_flags_t *r_policy, char **r_submission_address) +{ + gpg_error_t err; + estream_t mbuf = NULL; + const char *domain; + const char *s; + policy_flags_t policy = NULL; + char *submission_to = NULL; + + *r_submission_address = NULL; + *r_policy = NULL; + + domain = strchr (addrspec, '@'); + if (domain) + domain++; + + if (opt.with_colons) + { + s = domain? domain : addrspec; + es_write_sanitized (es_stdout, s, strlen (s), ":", NULL); + es_putc (':', es_stdout); + } + + /* We first try to get the submission address from the policy file + * (this is the new method). If both are available we check that + * they match and print a warning if not. In the latter case we + * keep on using the one from the submission-address file. */ + err = wkd_get_policy_flags (addrspec, &mbuf); + if (err && gpg_err_code (err) != GPG_ERR_NO_DATA + && gpg_err_code (err) != GPG_ERR_NO_NAME) + { + if (!opt.with_colons) + log_error ("error reading policy flags for '%s': %s\n", + domain, gpg_strerror (err)); + goto leave; + } + if (!mbuf) + { + if (!opt.with_colons) + log_error ("provider for '%s' does NOT support the Web Key Directory\n", + addrspec); + err = gpg_error (GPG_ERR_FALSE); + goto leave; + } + + policy = xtrycalloc (1, sizeof *policy); + if (!policy) + err = gpg_error_from_syserror (); + else + err = wks_parse_policy (policy, mbuf, 1); + es_fclose (mbuf); + mbuf = NULL; + if (err) + goto leave; + + err = wkd_get_submission_address (addrspec, &submission_to); + if (err && !policy->submission_address) + { + if (!silent && !opt.with_colons) + log_error (_("error looking up submission address for domain '%s'" + ": %s\n"), domain, gpg_strerror (err)); + if (!silent && gpg_err_code (err) == GPG_ERR_NO_DATA && !opt.with_colons) + log_error (_("this domain probably doesn't support WKS.\n")); + goto leave; + } + + if (submission_to && policy->submission_address + && ascii_strcasecmp (submission_to, policy->submission_address)) + log_info ("Warning: different submission addresses (sa=%s, po=%s)\n", + submission_to, policy->submission_address); + + if (!submission_to && policy->submission_address) + { + submission_to = xtrystrdup (policy->submission_address); + if (!submission_to) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + leave: + *r_submission_address = submission_to; + submission_to = NULL; + *r_policy = policy; + policy = NULL; + + if (opt.with_colons) + { + if (*r_policy && !*r_submission_address) + es_fprintf (es_stdout, "1:0::"); + else if (*r_policy && *r_submission_address) + es_fprintf (es_stdout, "1:1::"); + else if (err && !(gpg_err_code (err) == GPG_ERR_FALSE + || gpg_err_code (err) == GPG_ERR_NO_DATA + || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST)) + es_fprintf (es_stdout, "0:0:%d:", err); + else + es_fprintf (es_stdout, "0:0::"); + if (*r_policy) + { + es_fprintf (es_stdout, "%u:%u:%u:", + (*r_policy)->protocol_version, + (*r_policy)->auth_submit, + (*r_policy)->mailbox_only); + } + es_putc ('\n', es_stdout); + } + + xfree (submission_to); + wks_free_policy (policy); + xfree (policy); + es_fclose (mbuf); + return err; +} + + + +/* Check whether the provider supports the WKS protocol. */ +static gpg_error_t +command_supported (char *userid) +{ + gpg_error_t err; + char *addrspec = NULL; + char *submission_to = NULL; + policy_flags_t policy = NULL; + + if (!strchr (userid, '@')) + { + char *tmp = xstrconcat ("foo@", userid, NULL); + addrspec = mailbox_from_userid (tmp); + xfree (tmp); + } + else + addrspec = mailbox_from_userid (userid); + if (!addrspec) + { + log_error (_("\"%s\" is not a proper mail address\n"), userid); + err = gpg_error (GPG_ERR_INV_USER_ID); + goto leave; + } + + /* Get the submission address. */ + err = get_policy_and_sa (addrspec, 1, &policy, &submission_to); + if (err || !submission_to) + { + if (!submission_to + || gpg_err_code (err) == GPG_ERR_FALSE + || gpg_err_code (err) == GPG_ERR_NO_DATA + || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST + ) + { + /* FALSE is returned if we already figured out that even the + * Web Key Directory is not supported and thus printed an + * error message. */ + if (opt.verbose && gpg_err_code (err) != GPG_ERR_FALSE + && !opt.with_colons) + { + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + log_info ("provider for '%s' does NOT support WKS\n", + addrspec); + else + log_info ("provider for '%s' does NOT support WKS (%s)\n", + addrspec, gpg_strerror (err)); + } + err = gpg_error (GPG_ERR_FALSE); + if (!opt.with_colons) + log_inc_errorcount (); + } + goto leave; + } + + if (opt.verbose && !opt.with_colons) + log_info ("provider for '%s' supports WKS\n", addrspec); + + leave: + wks_free_policy (policy); + xfree (policy); + xfree (submission_to); + xfree (addrspec); + return err; +} + + + +/* Check whether the key for USERID is available in the WKD. */ +static gpg_error_t +command_check (char *userid) +{ + gpg_error_t err; + char *addrspec = NULL; + estream_t key = NULL; + char *fpr = NULL; + uidinfo_list_t mboxes = NULL; + uidinfo_list_t sl; + int found = 0; + + addrspec = mailbox_from_userid (userid); + if (!addrspec) + { + log_error (_("\"%s\" is not a proper mail address\n"), userid); + err = gpg_error (GPG_ERR_INV_USER_ID); + goto leave; + } + + /* Get the submission address. */ + err = wkd_get_key (addrspec, &key); + switch (gpg_err_code (err)) + { + case 0: + if (opt.verbose) + log_info ("public key for '%s' found via WKD\n", addrspec); + /* Fixme: Check that the key contains the user id. */ + break; + + case GPG_ERR_NO_DATA: /* No such key. */ + if (opt.verbose) + log_info ("public key for '%s' NOT found via WKD\n", addrspec); + err = gpg_error (GPG_ERR_NO_PUBKEY); + log_inc_errorcount (); + break; + + case GPG_ERR_UNKNOWN_HOST: + if (opt.verbose) + log_info ("error looking up '%s' via WKD: %s\n", + addrspec, gpg_strerror (err)); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + break; + + default: + log_error ("error looking up '%s' via WKD: %s\n", + addrspec, gpg_strerror (err)); + break; + } + + if (err) + goto leave; + + /* Look closer at the key. */ + err = wks_list_key (key, &fpr, &mboxes); + if (err) + { + log_error ("error parsing key: %s\n", gpg_strerror (err)); + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + + if (opt.verbose) + log_info ("fingerprint: %s\n", fpr); + + for (sl = mboxes; sl; sl = sl->next) + { + if (sl->mbox && !strcmp (sl->mbox, addrspec)) + found = 1; + if (opt.verbose) + { + log_info (" user-id: %s\n", sl->uid); + log_info (" created: %s\n", asctimestamp (sl->created)); + if (sl->mbox) + log_info (" addr-spec: %s\n", sl->mbox); + } + } + if (!found) + { + log_error ("public key for '%s' has no user id with the mail address\n", + addrspec); + err = gpg_error (GPG_ERR_CERT_REVOKED); + } + + leave: + xfree (fpr); + free_uidinfo_list (mboxes); + es_fclose (key); + xfree (addrspec); + return err; +} + + + +/* Locate the key by fingerprint and userid and send a publication + * request. */ +static gpg_error_t +command_send (const char *fingerprint, const char *userid) +{ + gpg_error_t err; + KEYDB_SEARCH_DESC desc; + char *addrspec = NULL; + estream_t key = NULL; + estream_t keyenc = NULL; + char *submission_to = NULL; + mime_maker_t mime = NULL; + policy_flags_t policy = NULL; + int no_encrypt = 0; + int posteo_hack = 0; + const char *domain; + uidinfo_list_t uidlist = NULL; + uidinfo_list_t uid, thisuid; + time_t thistime; + + if (classify_user_id (fingerprint, &desc, 1) + || !(desc.mode == KEYDB_SEARCH_MODE_FPR + || desc.mode == KEYDB_SEARCH_MODE_FPR20)) + { + log_error (_("\"%s\" is not a fingerprint\n"), fingerprint); + err = gpg_error (GPG_ERR_INV_NAME); + goto leave; + } + + addrspec = mailbox_from_userid (userid); + if (!addrspec) + { + log_error (_("\"%s\" is not a proper mail address\n"), userid); + err = gpg_error (GPG_ERR_INV_USER_ID); + goto leave; + } + err = wks_get_key (&key, fingerprint, addrspec, 0); + if (err) + goto leave; + + domain = strchr (addrspec, '@'); + log_assert (domain); + domain++; + + /* Get the submission address. */ + if (fake_submission_addr) + { + policy = xcalloc (1, sizeof *policy); + submission_to = xstrdup (fake_submission_addr); + err = 0; + } + else + { + err = get_policy_and_sa (addrspec, 0, &policy, &submission_to); + if (err) + goto leave; + if (!submission_to) + { + log_error (_("this domain probably doesn't support WKS.\n")); + err = gpg_error (GPG_ERR_NO_DATA); + goto leave; + } + } + + log_info ("submitting request to '%s'\n", submission_to); + + if (policy->auth_submit) + log_info ("no confirmation required for '%s'\n", addrspec); + + /* In case the key has several uids with the same addr-spec we will + * use the newest one. */ + err = wks_list_key (key, NULL, &uidlist); + if (err) + { + log_error ("error parsing key: %s\n",gpg_strerror (err)); + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + thistime = 0; + thisuid = NULL; + for (uid = uidlist; uid; uid = uid->next) + { + if (!uid->mbox) + continue; /* Should not happen anyway. */ + if (policy->mailbox_only && ascii_strcasecmp (uid->uid, uid->mbox)) + continue; /* UID has more than just the mailbox. */ + if (uid->created > thistime) + { + thistime = uid->created; + thisuid = uid; + } + } + if (!thisuid) + thisuid = uidlist; /* This is the case for a missing timestamp. */ + if (opt.verbose) + log_info ("submitting key with user id '%s'\n", thisuid->uid); + + /* If we have more than one user id we need to filter the key to + * include only THISUID. */ + if (uidlist->next) + { + estream_t newkey; + + es_rewind (key); + err = wks_filter_uid (&newkey, key, thisuid->uid, 0); + if (err) + { + log_error ("error filtering key: %s\n", gpg_strerror (err)); + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + es_fclose (key); + key = newkey; + } + + if (policy->mailbox_only + && (!thisuid->mbox || ascii_strcasecmp (thisuid->uid, thisuid->mbox))) + { + log_info ("Warning: policy requires 'mailbox-only'" + " - adding user id '%s'\n", addrspec); + err = add_user_id (fingerprint, addrspec); + if (err) + goto leave; + + /* Need to get the key again. This time we request filtering + * for the full user id, so that we do not need check and filter + * the key again. */ + es_fclose (key); + key = NULL; + err = wks_get_key (&key, fingerprint, addrspec, 1); + if (err) + goto leave; + } + + /* Hack to support posteo but let them disable this by setting the + * new policy-version flag. */ + if (policy->protocol_version < 3 + && !ascii_strcasecmp (domain, "posteo.de")) + { + log_info ("Warning: Using draft-1 method for domain '%s'\n", domain); + no_encrypt = 1; + posteo_hack = 1; + } + + /* Encrypt the key part. */ + if (!no_encrypt) + { + es_rewind (key); + err = encrypt_response (&keyenc, key, submission_to, fingerprint); + if (err) + goto leave; + es_fclose (key); + key = NULL; + } + + /* Send the key. */ + err = mime_maker_new (&mime, NULL); + if (err) + goto leave; + err = mime_maker_add_header (mime, "From", addrspec); + if (err) + goto leave; + err = mime_maker_add_header (mime, "To", submission_to); + if (err) + goto leave; + err = mime_maker_add_header (mime, "Subject", "Key publishing request"); + if (err) + goto leave; + + /* Tell server which draft we support. */ + err = mime_maker_add_header (mime, "Wks-Draft-Version", + STR2(WKS_DRAFT_VERSION)); + if (err) + goto leave; + + if (no_encrypt) + { + void *data; + size_t datalen, n; + + if (posteo_hack) + { + /* Needs a multipart/mixed with one(!) attachment. It does + * not grok a non-multipart mail. */ + err = mime_maker_add_header (mime, "Content-Type", "multipart/mixed"); + if (err) + goto leave; + err = mime_maker_add_container (mime); + if (err) + goto leave; + } + + err = mime_maker_add_header (mime, "Content-type", + "application/pgp-keys"); + if (err) + goto leave; + + if (es_fclose_snatch (key, &data, &datalen)) + { + err = gpg_error_from_syserror (); + goto leave; + } + key = NULL; + /* We need to skip over the first line which has a content-type + * header not needed here. */ + for (n=0; n < datalen ; n++) + if (((const char *)data)[n] == '\n') + { + n++; + break; + } + + err = mime_maker_add_body_data (mime, (char*)data + n, datalen - n); + xfree (data); + if (err) + goto leave; + } + else + { + err = mime_maker_add_header (mime, "Content-Type", + "multipart/encrypted; " + "protocol=\"application/pgp-encrypted\""); + if (err) + goto leave; + err = mime_maker_add_container (mime); + if (err) + goto leave; + + err = mime_maker_add_header (mime, "Content-Type", + "application/pgp-encrypted"); + if (err) + goto leave; + err = mime_maker_add_body (mime, "Version: 1\n"); + if (err) + goto leave; + err = mime_maker_add_header (mime, "Content-Type", + "application/octet-stream"); + if (err) + goto leave; + + err = mime_maker_add_stream (mime, &keyenc); + if (err) + goto leave; + } + + err = wks_send_mime (mime); + + leave: + mime_maker_release (mime); + xfree (submission_to); + free_uidinfo_list (uidlist); + es_fclose (keyenc); + es_fclose (key); + wks_free_policy (policy); + xfree (policy); + xfree (addrspec); + return err; +} + + + +static void +encrypt_response_status_cb (void *opaque, const char *keyword, char *args) +{ + gpg_error_t *failure = opaque; + char *fields[2]; + + if (DBG_CRYPTO) + log_debug ("gpg status: %s %s\n", keyword, args); + + if (!strcmp (keyword, "FAILURE")) + { + if (split_fields (args, fields, DIM (fields)) >= 2 + && !strcmp (fields[0], "encrypt")) + *failure = strtoul (fields[1], NULL, 10); + } + +} + + +/* Encrypt the INPUT stream to a new stream which is stored at success + * at R_OUTPUT. Encryption is done for ADDRSPEC and for FINGERPRINT + * (so that the sent message may later be inspected by the user). We + * currently retrieve that key from the WKD, DANE, or from "local". + * "local" is last to prefer the latest key version but use a local + * copy in case we are working offline. It might be useful for the + * server to send the fingerprint of its encryption key - or even the + * entire key back. */ +static gpg_error_t +encrypt_response (estream_t *r_output, estream_t input, const char *addrspec, + const char *fingerprint) +{ + gpg_error_t err; + ccparray_t ccp; + const char **argv; + estream_t output; + gpg_error_t gpg_err = 0; + + *r_output = NULL; + + output = es_fopenmem (0, "w+b"); + if (!output) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); + return err; + } + + ccparray_init (&ccp, 0); + + ccparray_put (&ccp, "--no-options"); + if (opt.verbose < 2) + ccparray_put (&ccp, "--quiet"); + else + ccparray_put (&ccp, "--verbose"); + ccparray_put (&ccp, "--batch"); + ccparray_put (&ccp, "--status-fd=2"); + ccparray_put (&ccp, "--always-trust"); + ccparray_put (&ccp, "--armor"); + ccparray_put (&ccp, "-z0"); /* No compression for improved robustness. */ + if (fake_submission_addr) + ccparray_put (&ccp, "--auto-key-locate=clear,local"); + else + ccparray_put (&ccp, "--auto-key-locate=clear,wkd,dane,local"); + ccparray_put (&ccp, "--recipient"); + ccparray_put (&ccp, addrspec); + ccparray_put (&ccp, "--recipient"); + ccparray_put (&ccp, fingerprint); + ccparray_put (&ccp, "--encrypt"); + ccparray_put (&ccp, "--"); + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = gnupg_exec_tool_stream (opt.gpg_program, argv, input, + NULL, output, + encrypt_response_status_cb, &gpg_err); + if (err) + { + if (gpg_err) + err = gpg_err; + log_error ("encryption failed: %s\n", gpg_strerror (err)); + goto leave; + } + + es_rewind (output); + *r_output = output; + output = NULL; + + leave: + es_fclose (output); + xfree (argv); + return err; +} + + +static gpg_error_t +send_confirmation_response (const char *sender, const char *address, + const char *nonce, int encrypt, + const char *fingerprint) +{ + gpg_error_t err; + estream_t body = NULL; + estream_t bodyenc = NULL; + mime_maker_t mime = NULL; + + body = es_fopenmem (0, "w+b"); + if (!body) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); + return err; + } + + /* It is fine to use 8 bit encoding because that is encrypted and + * only our client will see it. */ + if (encrypt) + { + es_fputs ("Content-Type: application/vnd.gnupg.wks\n" + "Content-Transfer-Encoding: 8bit\n" + "\n", + body); + } + + es_fprintf (body, ("type: confirmation-response\n" + "sender: %s\n" + "address: %s\n" + "nonce: %s\n"), + sender, + address, + nonce); + + es_rewind (body); + if (encrypt) + { + err = encrypt_response (&bodyenc, body, sender, fingerprint); + if (err) + goto leave; + es_fclose (body); + body = NULL; + } + + err = mime_maker_new (&mime, NULL); + if (err) + goto leave; + err = mime_maker_add_header (mime, "From", address); + if (err) + goto leave; + err = mime_maker_add_header (mime, "To", sender); + if (err) + goto leave; + err = mime_maker_add_header (mime, "Subject", "Key publication confirmation"); + if (err) + goto leave; + err = mime_maker_add_header (mime, "Wks-Draft-Version", + STR2(WKS_DRAFT_VERSION)); + if (err) + goto leave; + + if (encrypt) + { + err = mime_maker_add_header (mime, "Content-Type", + "multipart/encrypted; " + "protocol=\"application/pgp-encrypted\""); + if (err) + goto leave; + err = mime_maker_add_container (mime); + if (err) + goto leave; + + err = mime_maker_add_header (mime, "Content-Type", + "application/pgp-encrypted"); + if (err) + goto leave; + err = mime_maker_add_body (mime, "Version: 1\n"); + if (err) + goto leave; + err = mime_maker_add_header (mime, "Content-Type", + "application/octet-stream"); + if (err) + goto leave; + + err = mime_maker_add_stream (mime, &bodyenc); + if (err) + goto leave; + } + else + { + err = mime_maker_add_header (mime, "Content-Type", + "application/vnd.gnupg.wks"); + if (err) + goto leave; + err = mime_maker_add_stream (mime, &body); + if (err) + goto leave; + } + + err = wks_send_mime (mime); + + leave: + mime_maker_release (mime); + es_fclose (bodyenc); + es_fclose (body); + return err; +} + + +/* Reply to a confirmation request. The MSG has already been + * decrypted and we only need to send the nonce back. MAINFPR is + * either NULL or the primary key fingerprint of the key used to + * decrypt the request. */ +static gpg_error_t +process_confirmation_request (estream_t msg, const char *mainfpr) +{ + gpg_error_t err; + nvc_t nvc; + nve_t item; + const char *value, *sender, *address, *fingerprint, *nonce; + + err = nvc_parse (&nvc, NULL, msg); + if (err) + { + log_error ("parsing the WKS message failed: %s\n", gpg_strerror (err)); + goto leave; + } + + if (DBG_MIME) + { + log_debug ("request follows:\n"); + nvc_write (nvc, log_get_stream ()); + } + + /* Check that this is a confirmation request. */ + if (!((item = nvc_lookup (nvc, "type:")) && (value = nve_value (item)) + && !strcmp (value, "confirmation-request"))) + { + if (item && value) + log_error ("received unexpected wks message '%s'\n", value); + else + log_error ("received invalid wks message: %s\n", "'type' missing"); + err = gpg_error (GPG_ERR_UNEXPECTED_MSG); + goto leave; + } + + /* Get the fingerprint. */ + if (!((item = nvc_lookup (nvc, "fingerprint:")) + && (value = nve_value (item)) + && strlen (value) >= 40)) + { + log_error ("received invalid wks message: %s\n", + "'fingerprint' missing or invalid"); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + fingerprint = value; + + /* Check that the fingerprint matches the key used to decrypt the + * message. In --read mode or with the old format we don't have the + * decryption key; thus we can't bail out. */ + if (!mainfpr || ascii_strcasecmp (mainfpr, fingerprint)) + { + log_info ("target fingerprint: %s\n", fingerprint); + log_info ("but decrypted with: %s\n", mainfpr); + log_error ("confirmation request not decrypted with target key\n"); + if (mainfpr) + { + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + } + + /* Get the address. */ + if (!((item = nvc_lookup (nvc, "address:")) && (value = nve_value (item)) + && is_valid_mailbox (value))) + { + log_error ("received invalid wks message: %s\n", + "'address' missing or invalid"); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + address = value; + /* FIXME: Check that the "address" matches the User ID we want to + * publish. */ + + /* Get the sender. */ + if (!((item = nvc_lookup (nvc, "sender:")) && (value = nve_value (item)) + && is_valid_mailbox (value))) + { + log_error ("received invalid wks message: %s\n", + "'sender' missing or invalid"); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + sender = value; + /* FIXME: Check that the "sender" matches the From: address. */ + + /* Get the nonce. */ + if (!((item = nvc_lookup (nvc, "nonce:")) && (value = nve_value (item)) + && strlen (value) > 16)) + { + log_error ("received invalid wks message: %s\n", + "'nonce' missing or too short"); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + nonce = value; + + /* Send the confirmation. If no key was found, try again without + * encryption. */ + err = send_confirmation_response (sender, address, nonce, 1, fingerprint); + if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY) + { + log_info ("no encryption key found - sending response in the clear\n"); + err = send_confirmation_response (sender, address, nonce, 0, NULL); + } + + leave: + nvc_release (nvc); + return err; +} + + +/* Read a confirmation request and decrypt it if needed. This + * function may not be used with a mail or MIME message but only with + * the actual encrypted or plaintext WKS data. */ +static gpg_error_t +read_confirmation_request (estream_t msg) +{ + gpg_error_t err; + int c; + estream_t plaintext = NULL; + + /* We take a really simple approach to check whether MSG is + * encrypted: We know that an encrypted message is always armored + * and thus starts with a few dashes. It is even sufficient to + * check for a single dash, because that can never be a proper first + * WKS data octet. We need to skip leading spaces, though. */ + while ((c = es_fgetc (msg)) == ' ' || c == '\t' || c == '\r' || c == '\n') + ; + if (c == EOF) + { + log_error ("can't process an empty message\n"); + return gpg_error (GPG_ERR_INV_DATA); + } + if (es_ungetc (c, msg) != c) + { + log_error ("error ungetting octet from message\n"); + return gpg_error (GPG_ERR_INTERNAL); + } + + if (c != '-') + err = process_confirmation_request (msg, NULL); + else + { + struct decrypt_stream_parm_s decinfo; + + err = decrypt_stream (&plaintext, &decinfo, msg); + if (err) + log_error ("decryption failed: %s\n", gpg_strerror (err)); + else if (decinfo.otrust != 'u') + { + err = gpg_error (GPG_ERR_WRONG_SECKEY); + log_error ("key used to decrypt the confirmation request" + " was not generated by us\n"); + } + else + err = process_confirmation_request (plaintext, decinfo.mainfpr); + xfree (decinfo.fpr); + xfree (decinfo.mainfpr); + } + + es_fclose (plaintext); + return err; +} + + +/* Called from the MIME receiver to process the plain text data in MSG. */ +static gpg_error_t +command_receive_cb (void *opaque, const char *mediatype, + estream_t msg, unsigned int flags) +{ + gpg_error_t err; + + (void)opaque; + (void)flags; + + if (!strcmp (mediatype, "application/vnd.gnupg.wks")) + err = read_confirmation_request (msg); + else + { + log_info ("ignoring unexpected message of type '%s'\n", mediatype); + err = gpg_error (GPG_ERR_UNEXPECTED_MSG); + } + + return err; +} + + + +/* An object used to communicate with the mirror_one_key callback. */ +struct +{ + const char *domain; + int anyerror; + unsigned int nkeys; /* Number of keys processed. */ + unsigned int nuids; /* Number of published user ids. */ +} mirror_one_key_parm; + + +/* Return true if the Given a mail DOMAIN and the full addrspec MBOX + * match. */ +static int +domain_matches_mbox (const char *domain, const char *mbox) +{ + const char *s; + + if (!domain || !mbox) + return 0; + s = strchr (domain, '@'); + if (s) + domain = s+1; + if (!*domain) + return 0; /* Not a valid domain. */ + + s = strchr (mbox, '@'); + if (!s || !s[1]) + return 0; /* Not a valid mbox. */ + mbox = s+1; + + return !ascii_strcasecmp (domain, mbox); +} + + +/* Core of mirror_one_key with the goal of mirroring just one uid. + * UIDLIST is used to figure out whether the given MBOX occurs several + * times in UIDLIST and then to single out the newwest one. This is + * so that for a key with + * uid: Joe Someone <joe@example.org> + * uid: Joe <joe@example.org> + * only the news user id (and thus its self-signature) is used. + * UIDLIST is nodified to set all MBOX fields to NULL for a processed + * user id. FPR is the fingerprint of the key. + */ +static gpg_error_t +mirror_one_keys_userid (estream_t key, const char *mbox, uidinfo_list_t uidlist, + const char *fpr) +{ + gpg_error_t err; + uidinfo_list_t uid, thisuid, firstuid; + time_t thistime; + estream_t newkey = NULL; + + /* Find the UID we want to use. */ + thistime = 0; + thisuid = firstuid = NULL; + for (uid = uidlist; uid; uid = uid->next) + { + if ((uid->flags & 1) || !uid->mbox || strcmp (uid->mbox, mbox)) + continue; /* Already processed or no matching mbox. */ + uid->flags |= 1; /* Set "processed" flag. */ + if (!firstuid) + firstuid = uid; + if (uid->created > thistime) + { + thistime = uid->created; + thisuid = uid; + } + } + if (!thisuid) + thisuid = firstuid; /* This is the case for a missing timestamp. */ + if (!thisuid) + { + log_error ("error finding the user id for %s (%s)\n", fpr, mbox); + err = gpg_error (GPG_ERR_NO_USER_ID); + goto leave; + } + /* FIXME: Consult blacklist. */ + + + /* Only if we have more than one user id we bother to run the + * filter. In this case the result will be put into NEWKEY*/ + es_rewind (key); + if (uidlist->next) + { + err = wks_filter_uid (&newkey, key, thisuid->uid, 0); + if (err) + { + log_error ("error filtering key %s: %s\n", fpr, gpg_strerror (err)); + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + } + + err = wks_install_key_core (newkey? newkey : key, mbox); + if (opt.verbose) + log_info ("key %s published for '%s'\n", fpr, mbox); + mirror_one_key_parm.nuids++; + if (!opt.quiet && !(mirror_one_key_parm.nuids % 25)) + log_info ("%u user ids from %d keys so far\n", + mirror_one_key_parm.nuids, mirror_one_key_parm.nkeys); + + leave: + es_fclose (newkey); + return err; +} + + +/* The callback used by command_mirror. It received an estream with + * one key and should return success to process the next key. */ +static gpg_error_t +mirror_one_key (estream_t key) +{ + gpg_error_t err = 0; + char *fpr; + uidinfo_list_t uidlist = NULL; + uidinfo_list_t uid; + const char *domain = mirror_one_key_parm.domain; + + /* List the key to get all user ids. */ + err = wks_list_key (key, &fpr, &uidlist); + if (err) + { + log_error ("error parsing a key: %s - skipped\n", + gpg_strerror (err)); + mirror_one_key_parm.anyerror = 1; + err = 0; + goto leave; + } + for (uid = uidlist; uid; uid = uid->next) + { + if (!uid->mbox || (uid->flags & 1)) + continue; /* No mail box or already processed. */ + if (!domain_matches_mbox (domain, uid->mbox)) + continue; /* We don't want this one. */ + if (is_in_blacklist (uid->mbox)) + continue; + + err = mirror_one_keys_userid (key, uid->mbox, uidlist, fpr); + if (err) + { + log_error ("error processing key %s: %s - skipped\n", + fpr, gpg_strerror (err)); + mirror_one_key_parm.anyerror = 1; + err = 0; + goto leave; + } + } + mirror_one_key_parm.nkeys++; + + + leave: + free_uidinfo_list (uidlist); + xfree (fpr); + return err; +} + + +/* Copy the keys from the configured LDAP server into a local WKD. + * DOMAINLIST is an array of domain names to restrict the copy to only + * the given domains; if it is NULL all keys are mirrored. */ +static gpg_error_t +command_mirror (char *domainlist[]) +{ + gpg_error_t err; + const char *domain; + char *domainbuf = NULL; + + mirror_one_key_parm.anyerror = 0; + mirror_one_key_parm.nkeys = 0; + mirror_one_key_parm.nuids = 0; + + if (!domainlist) + { + mirror_one_key_parm.domain = ""; + err = wkd_dirmngr_ks_get (NULL, mirror_one_key); + } + else + { + while ((domain = *domainlist++)) + { + if (*domain != '.' && domain[1] != '@') + { + /* This does not already specify a mail search by + * domain. Change it. */ + xfree (domainbuf); + domainbuf = xstrconcat (".@", domain, NULL); + domain = domainbuf; + } + mirror_one_key_parm.domain = domain; + if (opt.verbose) + log_info ("mirroring keys for domain '%s'\n", domain+2); + err = wkd_dirmngr_ks_get (domain, mirror_one_key); + if (err) + break; + } + } + + if (!opt.quiet) + log_info ("a total of %u user ids from %d keys published\n", + mirror_one_key_parm.nuids, mirror_one_key_parm.nkeys); + if (err) + log_error ("error mirroring LDAP directory: %s <%s>\n", + gpg_strerror (err), gpg_strsource (err)); + else if (mirror_one_key_parm.anyerror) + log_info ("warning: errors encountered - not all keys are mirrored\n"); + + xfree (domainbuf); + return err; +} diff --git a/tools/gpg-wks-client.w32-manifest.in b/tools/gpg-wks-client.w32-manifest.in new file mode 100644 index 0000000..ba2508e --- /dev/null +++ b/tools/gpg-wks-client.w32-manifest.in @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<description>GNU Privacy Guard (Web Key Service client)</description> +<assemblyIdentity + type="win32" + name="GnuPG.gpg-wks-client" + version="@BUILD_VERSION@" + /> +<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/><!-- 10 --> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/><!-- 8.1 --> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/><!-- 8 --> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/><!-- 7 --> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/><!-- Vista --> + </application> +</compatibility> +</assembly> diff --git a/tools/gpg-wks-server.c b/tools/gpg-wks-server.c new file mode 100644 index 0000000..74bb551 --- /dev/null +++ b/tools/gpg-wks-server.c @@ -0,0 +1,2058 @@ +/* gpg-wks-server.c - A server for the Web Key Service protocols. + * Copyright (C) 2016, 2018 Werner Koch + * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +/* The Web Key Service I-D defines an update protocol to store a + * public key in the Web Key Directory. The current specification is + * draft-koch-openpgp-webkey-service-05.txt. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> + +#define INCLUDED_BY_MAIN_MODULE 1 +#include "../common/util.h" +#include "../common/init.h" +#include "../common/sysutils.h" +#include "../common/userids.h" +#include "../common/ccparray.h" +#include "../common/exectool.h" +#include "../common/zb32.h" +#include "../common/mbox-util.h" +#include "../common/name-value.h" +#include "mime-maker.h" +#include "send-mail.h" +#include "gpg-wks.h" + + +/* The time we wait for a confirmation response. */ +#define PENDING_TTL (86400 * 3) /* 3 days. */ + + +/* Constants to identify the commands and options. */ +enum cmd_and_opt_values + { + aNull = 0, + + oQuiet = 'q', + oVerbose = 'v', + oOutput = 'o', + oDirectory = 'C', + + oDebug = 500, + + aReceive, + aCron, + aListDomains, + aInstallKey, + aRevokeKey, + aRemoveKey, + aCheck, + + oGpgProgram, + oSend, + oFrom, + oHeader, + oWithDir, + oWithFile, + + oDummy + }; + + +/* The list of commands and options. */ +static ARGPARSE_OPTS opts[] = { + ARGPARSE_group (300, ("@Commands:\n ")), + + ARGPARSE_c (aReceive, "receive", + ("receive a submission or confirmation")), + ARGPARSE_c (aCron, "cron", + ("run regular jobs")), + ARGPARSE_c (aListDomains, "list-domains", + ("list configured domains")), + ARGPARSE_c (aCheck, "check", + ("check whether a key is installed")), + ARGPARSE_c (aCheck, "check-key", "@"), + ARGPARSE_c (aInstallKey, "install-key", + "install a key from FILE into the WKD"), + ARGPARSE_c (aRemoveKey, "remove-key", + "remove a key from the WKD"), + ARGPARSE_c (aRevokeKey, "revoke-key", + "mark a key as revoked"), + + ARGPARSE_group (301, ("@\nOptions:\n ")), + + ARGPARSE_s_n (oVerbose, "verbose", ("verbose")), + ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")), + ARGPARSE_s_s (oDebug, "debug", "@"), + ARGPARSE_s_s (oGpgProgram, "gpg", "@"), + ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"), + ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"), + ARGPARSE_s_s (oDirectory, "directory", "|DIR|use DIR as top directory"), + ARGPARSE_s_s (oFrom, "from", "|ADDR|use ADDR as the default sender"), + ARGPARSE_s_s (oHeader, "header" , + "|NAME=VALUE|add \"NAME: VALUE\" as header to all mails"), + ARGPARSE_s_n (oWithDir, "with-dir", "@"), + ARGPARSE_s_n (oWithFile, "with-file", "@"), + + ARGPARSE_end () +}; + + +/* The list of supported debug flags. */ +static struct debug_flags_s debug_flags [] = + { + { DBG_MIME_VALUE , "mime" }, + { DBG_PARSER_VALUE , "parser" }, + { DBG_CRYPTO_VALUE , "crypto" }, + { DBG_MEMORY_VALUE , "memory" }, + { DBG_MEMSTAT_VALUE, "memstat" }, + { DBG_IPC_VALUE , "ipc" }, + { DBG_EXTPROG_VALUE, "extprog" }, + { 0, NULL } + }; + + +/* State for processing a message. */ +struct server_ctx_s +{ + char *fpr; + uidinfo_list_t mboxes; /* List with addr-specs taken from the UIDs. */ + unsigned int draft_version_2:1; /* Client supports the draft 2. */ +}; +typedef struct server_ctx_s *server_ctx_t; + + +/* Flag for --with-dir. */ +static int opt_with_dir; +/* Flag for --with-file. */ +static int opt_with_file; + + +/* Prototypes. */ +static gpg_error_t get_domain_list (strlist_t *r_list); + +static gpg_error_t command_receive_cb (void *opaque, + const char *mediatype, estream_t fp, + unsigned int flags); +static gpg_error_t command_list_domains (void); +static gpg_error_t command_revoke_key (const char *mailaddr); +static gpg_error_t command_check_key (const char *mailaddr); +static gpg_error_t command_cron (void); + + + +/* Print usage information and provide strings for help. */ +static const char * +my_strusage( int level ) +{ + const char *p; + + switch (level) + { + case 9: p = "LGPL-2.1-or-later"; break; + case 11: p = "gpg-wks-server"; break; + case 12: p = "@GNUPG@"; break; + case 13: p = VERSION; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break; + + case 1: + case 40: + p = ("Usage: gpg-wks-server command [options] (-h for help)"); + break; + case 41: + p = ("Syntax: gpg-wks-server command [options]\n" + "Server for the Web Key Service protocol\n"); + break; + + default: p = NULL; break; + } + return p; +} + + +static void +wrong_args (const char *text) +{ + es_fprintf (es_stderr, "usage: %s [options] %s\n", strusage (11), text); + exit (2); +} + + + +/* Command line parsing. */ +static enum cmd_and_opt_values +parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) +{ + enum cmd_and_opt_values cmd = 0; + int no_more_options = 0; + + while (!no_more_options && gnupg_argparse (NULL, pargs, popts)) + { + switch (pargs->r_opt) + { + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + case oDebug: + if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags)) + { + pargs->r_opt = ARGPARSE_INVALID_ARG; + pargs->err = ARGPARSE_PRINT_ERROR; + } + break; + + case oGpgProgram: + opt.gpg_program = pargs->r.ret_str; + break; + case oDirectory: + opt.directory = pargs->r.ret_str; + break; + case oFrom: + opt.default_from = pargs->r.ret_str; + break; + case oHeader: + append_to_strlist (&opt.extra_headers, pargs->r.ret_str); + break; + case oSend: + opt.use_sendmail = 1; + break; + case oOutput: + opt.output = pargs->r.ret_str; + break; + case oWithDir: + opt_with_dir = 1; + break; + case oWithFile: + opt_with_file = 1; + break; + + case aReceive: + case aCron: + case aListDomains: + case aCheck: + case aInstallKey: + case aRemoveKey: + case aRevokeKey: + cmd = pargs->r_opt; + break; + + default: pargs->err = ARGPARSE_PRINT_ERROR; break; + } + } + + return cmd; +} + + + +/* gpg-wks-server main. */ +int +main (int argc, char **argv) +{ + gpg_error_t err, firsterr; + ARGPARSE_ARGS pargs; + enum cmd_and_opt_values cmd; + + gnupg_reopen_std ("gpg-wks-server"); + set_strusage (my_strusage); + log_set_prefix ("gpg-wks-server", GPGRT_LOG_WITH_PREFIX); + + /* Make sure that our subsystems are ready. */ + init_common_subsystems (&argc, &argv); + + /* Parse the command line. */ + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags = ARGPARSE_FLAG_KEEP; + cmd = parse_arguments (&pargs, opts); + gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ + + if (log_get_errorcount (0)) + exit (2); + + /* Print a warning if an argument looks like an option. */ + if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) + { + int i; + + for (i=0; i < argc; i++) + if (argv[i][0] == '-' && argv[i][1] == '-') + log_info (("NOTE: '%s' is not considered an option\n"), argv[i]); + } + + /* Set defaults for non given options. */ + if (!opt.gpg_program) + opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); + + if (!opt.directory) + opt.directory = "/var/lib/gnupg/wks"; + + /* Check for syntax errors in the --header option to avoid later + * error messages with a not easy to find cause */ + if (opt.extra_headers) + { + strlist_t sl; + + for (sl = opt.extra_headers; sl; sl = sl->next) + { + err = mime_maker_add_header (NULL, sl->d, NULL); + if (err) + log_error ("syntax error in \"--header %s\": %s\n", + sl->d, gpg_strerror (err)); + } + } + + if (log_get_errorcount (0)) + exit (2); + + + /* Check that we have a working directory. */ +#if defined(HAVE_STAT) + { + struct stat sb; + + if (gnupg_stat (opt.directory, &sb)) + { + err = gpg_error_from_syserror (); + log_error ("error accessing directory '%s': %s\n", + opt.directory, gpg_strerror (err)); + exit (2); + } + if (!S_ISDIR(sb.st_mode)) + { + log_error ("error accessing directory '%s': %s\n", + opt.directory, "not a directory"); + exit (2); + } + if (sb.st_uid != getuid()) + { + log_error ("directory '%s' not owned by user\n", opt.directory); + exit (2); + } + if ((sb.st_mode & (S_IROTH|S_IWOTH))) + { + log_error ("directory '%s' has too relaxed permissions\n", + opt.directory); + log_info ("Fix by running: chmod o-rw '%s'\n", opt.directory); + exit (2); + } + } +#else /*!HAVE_STAT*/ + log_fatal ("program build w/o stat() call\n"); +#endif /*!HAVE_STAT*/ + + /* Run the selected command. */ + switch (cmd) + { + case aReceive: + if (argc) + wrong_args ("--receive"); + err = wks_receive (es_stdin, command_receive_cb, NULL); + break; + + case aCron: + if (argc) + wrong_args ("--cron"); + err = command_cron (); + break; + + case aListDomains: + err = command_list_domains (); + break; + + case aInstallKey: + if (!argc) + err = wks_cmd_install_key (NULL, NULL); + else if (argc == 2) + err = wks_cmd_install_key (*argv, argv[1]); + else + wrong_args ("--install-key [FILE|FINGERPRINT USER-ID]"); + break; + + case aRemoveKey: + if (argc != 1) + wrong_args ("--remove-key USER-ID"); + err = wks_cmd_remove_key (*argv); + break; + + case aRevokeKey: + if (argc != 1) + wrong_args ("--revoke-key USER-ID"); + err = command_revoke_key (*argv); + break; + + case aCheck: + if (!argc) + wrong_args ("--check USER-IDs"); + firsterr = 0; + for (; argc; argc--, argv++) + { + err = command_check_key (*argv); + if (!firsterr) + firsterr = err; + } + err = firsterr; + break; + + default: + usage (1); + err = gpg_error (GPG_ERR_BUG); + break; + } + + if (err) + log_error ("command failed: %s\n", gpg_strerror (err)); + return log_get_errorcount (0)? 1:0; +} + + +/* Take the key in KEYFILE and write it to OUTFILE in binary encoding. + * If ADDRSPEC is given only matching user IDs are included in the + * output. */ +static gpg_error_t +copy_key_as_binary (const char *keyfile, const char *outfile, + const char *addrspec) +{ + gpg_error_t err; + ccparray_t ccp; + const char **argv = NULL; + char *filterexp = NULL; + + if (addrspec) + { + filterexp = es_bsprintf ("keep-uid=mbox = %s", addrspec); + if (!filterexp) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory buffer: %s\n", + gpg_strerror (err)); + goto leave; + } + } + + ccparray_init (&ccp, 0); + + ccparray_put (&ccp, "--no-options"); + if (!opt.verbose) + ccparray_put (&ccp, "--quiet"); + else if (opt.verbose > 1) + ccparray_put (&ccp, "--verbose"); + ccparray_put (&ccp, "--batch"); + ccparray_put (&ccp, "--yes"); + ccparray_put (&ccp, "--always-trust"); + ccparray_put (&ccp, "--no-keyring"); + ccparray_put (&ccp, "--output"); + ccparray_put (&ccp, outfile); + ccparray_put (&ccp, "--import-options=import-export"); + if (filterexp) + { + ccparray_put (&ccp, "--import-filter"); + ccparray_put (&ccp, filterexp); + } + ccparray_put (&ccp, "--import"); + ccparray_put (&ccp, "--"); + ccparray_put (&ccp, keyfile); + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL, + NULL, NULL, NULL, NULL); + if (err) + { + log_error ("%s failed: %s\n", __func__, gpg_strerror (err)); + goto leave; + } + + leave: + xfree (filterexp); + xfree (argv); + return err; +} + + +/* Take the key in KEYFILE and write it to DANEFILE using the DANE + * output format. */ +static gpg_error_t +copy_key_as_dane (const char *keyfile, const char *danefile) +{ + gpg_error_t err; + ccparray_t ccp; + const char **argv; + + ccparray_init (&ccp, 0); + + ccparray_put (&ccp, "--no-options"); + if (!opt.verbose) + ccparray_put (&ccp, "--quiet"); + else if (opt.verbose > 1) + ccparray_put (&ccp, "--verbose"); + ccparray_put (&ccp, "--batch"); + ccparray_put (&ccp, "--yes"); + ccparray_put (&ccp, "--always-trust"); + ccparray_put (&ccp, "--no-keyring"); + ccparray_put (&ccp, "--output"); + ccparray_put (&ccp, danefile); + ccparray_put (&ccp, "--export-options=export-dane"); + ccparray_put (&ccp, "--import-options=import-export"); + ccparray_put (&ccp, "--import"); + ccparray_put (&ccp, "--"); + ccparray_put (&ccp, keyfile); + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL, + NULL, NULL, NULL, NULL); + if (err) + { + log_error ("%s failed: %s\n", __func__, gpg_strerror (err)); + goto leave; + } + + leave: + xfree (argv); + return err; +} + + +static void +encrypt_stream_status_cb (void *opaque, const char *keyword, char *args) +{ + (void)opaque; + + if (DBG_CRYPTO) + log_debug ("gpg status: %s %s\n", keyword, args); +} + + +/* Encrypt the INPUT stream to a new stream which is stored at success + * at R_OUTPUT. Encryption is done for the key in file KEYFIL. */ +static gpg_error_t +encrypt_stream (estream_t *r_output, estream_t input, const char *keyfile) +{ + gpg_error_t err; + ccparray_t ccp; + const char **argv; + estream_t output; + + *r_output = NULL; + + output = es_fopenmem (0, "w+b"); + if (!output) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); + return err; + } + + ccparray_init (&ccp, 0); + + ccparray_put (&ccp, "--no-options"); + if (!opt.verbose) + ccparray_put (&ccp, "--quiet"); + else if (opt.verbose > 1) + ccparray_put (&ccp, "--verbose"); + ccparray_put (&ccp, "--batch"); + ccparray_put (&ccp, "--status-fd=2"); + ccparray_put (&ccp, "--always-trust"); + ccparray_put (&ccp, "--no-keyring"); + ccparray_put (&ccp, "--armor"); + ccparray_put (&ccp, "-z0"); /* No compression for improved robustness. */ + ccparray_put (&ccp, "--recipient-file"); + ccparray_put (&ccp, keyfile); + ccparray_put (&ccp, "--encrypt"); + ccparray_put (&ccp, "--"); + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = gnupg_exec_tool_stream (opt.gpg_program, argv, input, + NULL, output, + encrypt_stream_status_cb, NULL); + if (err) + { + log_error ("encryption failed: %s\n", gpg_strerror (err)); + goto leave; + } + + es_rewind (output); + *r_output = output; + output = NULL; + + leave: + es_fclose (output); + xfree (argv); + return err; +} + + +static void +sign_stream_status_cb (void *opaque, const char *keyword, char *args) +{ + (void)opaque; + + if (DBG_CRYPTO) + log_debug ("gpg status: %s %s\n", keyword, args); +} + +/* Sign the INPUT stream to a new stream which is stored at success at + * R_OUTPUT. A detached signature is created using the key specified + * by USERID. */ +static gpg_error_t +sign_stream (estream_t *r_output, estream_t input, const char *userid) +{ + gpg_error_t err; + ccparray_t ccp; + const char **argv; + estream_t output; + + *r_output = NULL; + + output = es_fopenmem (0, "w+b"); + if (!output) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); + return err; + } + + ccparray_init (&ccp, 0); + + ccparray_put (&ccp, "--no-options"); + if (!opt.verbose) + ccparray_put (&ccp, "--quiet"); + else if (opt.verbose > 1) + ccparray_put (&ccp, "--verbose"); + ccparray_put (&ccp, "--batch"); + ccparray_put (&ccp, "--status-fd=2"); + ccparray_put (&ccp, "--armor"); + ccparray_put (&ccp, "--local-user"); + ccparray_put (&ccp, userid); + ccparray_put (&ccp, "--detach-sign"); + ccparray_put (&ccp, "--"); + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = gnupg_exec_tool_stream (opt.gpg_program, argv, input, + NULL, output, + sign_stream_status_cb, NULL); + if (err) + { + log_error ("signing failed: %s\n", gpg_strerror (err)); + goto leave; + } + + es_rewind (output); + *r_output = output; + output = NULL; + + leave: + es_fclose (output); + xfree (argv); + return err; +} + + +/* Get the submission address for address MBOX. Caller must free the + * value. If no address can be found NULL is returned. */ +static char * +get_submission_address (const char *mbox) +{ + gpg_error_t err; + const char *domain; + char *fname, *line, *p; + size_t n; + estream_t fp; + + domain = strchr (mbox, '@'); + if (!domain) + return NULL; + domain++; + + fname = make_filename_try (opt.directory, domain, "submission-address", NULL); + if (!fname) + { + err = gpg_error_from_syserror (); + log_error ("make_filename failed in %s: %s\n", + __func__, gpg_strerror (err)); + return NULL; + } + + fp = es_fopen (fname, "r"); + if (!fp) + { + err = gpg_error_from_syserror (); + if (gpg_err_code (err) == GPG_ERR_ENOENT) + log_info ("Note: no specific submission address configured" + " for domain '%s'\n", domain); + else + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + xfree (fname); + return NULL; + } + + line = NULL; + n = 0; + if (es_getline (&line, &n, fp) < 0) + { + err = gpg_error_from_syserror (); + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + xfree (line); + es_fclose (fp); + xfree (fname); + return NULL; + } + es_fclose (fp); + xfree (fname); + + p = strchr (line, '\n'); + if (p) + *p = 0; + trim_spaces (line); + if (!is_valid_mailbox (line)) + { + log_error ("invalid submission address for domain '%s' detected\n", + domain); + xfree (line); + return NULL; + } + + return line; +} + + +/* Get the policy flags for address MBOX and store them in POLICY. */ +static gpg_error_t +get_policy_flags (policy_flags_t policy, const char *mbox) +{ + gpg_error_t err; + const char *domain; + char *fname; + estream_t fp; + + memset (policy, 0, sizeof *policy); + + domain = strchr (mbox, '@'); + if (!domain) + return gpg_error (GPG_ERR_INV_USER_ID); + domain++; + + fname = make_filename_try (opt.directory, domain, "policy", NULL); + if (!fname) + { + err = gpg_error_from_syserror (); + log_error ("make_filename failed in %s: %s\n", + __func__, gpg_strerror (err)); + return err; + } + + fp = es_fopen (fname, "r"); + if (!fp) + { + err = gpg_error_from_syserror (); + if (gpg_err_code (err) == GPG_ERR_ENOENT) + err = 0; + else + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + xfree (fname); + return err; + } + + err = wks_parse_policy (policy, fp, 0); + es_fclose (fp); + xfree (fname); + return err; +} + + +/* Create the name for the pending file from NONCE and ADDRSPEC and + * store it at R_NAME. */ +static gpg_error_t +make_pending_fname (const char *nonce, const char *addrspec, char **r_name) +{ + gpg_error_t err = 0; + const char *domain; + char *addrspechash = NULL; + char sha1buf[20]; + + *r_name = NULL; + + domain = addrspec? strchr (addrspec, '@') : NULL; + if (!domain || !domain[1] || domain == addrspec) + { + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + domain++; + if (strchr (domain, '/') || strchr (domain, '\\')) + { + log_info ("invalid domain detected ('%s')\n", domain); + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, addrspec, domain - addrspec - 1); + addrspechash = zb32_encode (sha1buf, 8*20); + if (!addrspechash) + { + err = gpg_error_from_syserror (); + goto leave; + } + + *r_name = strconcat (nonce, ".", addrspechash, NULL); + if (!*r_name) + { + err = gpg_error_from_syserror (); + goto leave; + } + + leave: + xfree (addrspechash); + return err; +} + + +/* We store the key under the name of the nonce we will then send to + * the user. On success the nonce is stored at R_NONCE and the file + * name at R_FNAME. ADDRSPEC is used as part of the pending file name + * so that the nonce is associated with an address */ +static gpg_error_t +store_key_as_pending (const char *dir, estream_t key, const char *addrspec, + char **r_nonce, char **r_fname) +{ + gpg_error_t err; + char *dname = NULL; + char *fname = NULL; + char *nonce = NULL; + char *pendingname = NULL; + estream_t outfp = NULL; + char buffer[1024]; + size_t nbytes, nwritten; + + *r_nonce = NULL; + *r_fname = NULL; + + dname = make_filename_try (dir, "pending", NULL); + if (!dname) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Create the nonce. We use 20 bytes so that we don't waste a + * character in our zBase-32 encoding. Using the gcrypt's nonce + * function is faster than using the strong random function; this is + * Good Enough for our purpose. */ + log_assert (sizeof buffer > 20); + gcry_create_nonce (buffer, 20); + nonce = zb32_encode (buffer, 8 * 20); + memset (buffer, 0, 20); /* Not actually needed but it does not harm. */ + if (!nonce) + { + err = gpg_error_from_syserror (); + goto leave; + } + + err = make_pending_fname (nonce, addrspec, &pendingname); + if (err) + goto leave; + + fname = strconcat (dname, "/", pendingname, NULL); + if (!fname) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* With 128 bits of random we can expect that no other file exists + * under this name. We use "x" to detect internal errors. */ + outfp = es_fopen (fname, "wbx,mode=-rw"); + if (!outfp) + { + err = gpg_error_from_syserror (); + log_error ("error creating '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + es_rewind (key); + for (;;) + { + if (es_read (key, buffer, sizeof buffer, &nbytes)) + { + err = gpg_error_from_syserror (); + log_error ("error reading '%s': %s\n", + es_fname_get (key), gpg_strerror (err)); + break; + } + + if (!nbytes) + { + err = 0; + goto leave; /* Ready. */ + } + if (es_write (outfp, buffer, nbytes, &nwritten)) + { + err = gpg_error_from_syserror (); + log_error ("error writing '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + else if (nwritten != nbytes) + { + err = gpg_error (GPG_ERR_EIO); + log_error ("error writing '%s': %s\n", fname, "short write"); + goto leave; + } + } + + leave: + if (err) + { + es_fclose (outfp); + gnupg_remove (fname); + } + else if (es_fclose (outfp)) + { + err = gpg_error_from_syserror (); + log_error ("error closing '%s': %s\n", fname, gpg_strerror (err)); + } + + if (!err) + { + *r_nonce = nonce; + *r_fname = fname; + } + else + { + xfree (nonce); + xfree (fname); + } + xfree (dname); + xfree (pendingname); + return err; +} + + +/* Send a confirmation request. DIR is the directory used for the + * address MBOX. NONCE is the nonce we want to see in the response to + * this mail. FNAME the name of the file with the key. */ +static gpg_error_t +send_confirmation_request (server_ctx_t ctx, + const char *mbox, const char *nonce, + const char *keyfile) +{ + gpg_error_t err; + estream_t body = NULL; + estream_t bodyenc = NULL; + estream_t signeddata = NULL; + estream_t signature = NULL; + mime_maker_t mime = NULL; + char *from_buffer = NULL; + const char *from; + strlist_t sl; + + from = from_buffer = get_submission_address (mbox); + if (!from) + { + from = opt.default_from; + if (!from) + { + log_error ("no sender address found for '%s'\n", mbox); + err = gpg_error (GPG_ERR_CONFIGURATION); + goto leave; + } + log_info ("Note: using default sender address '%s'\n", from); + } + + body = es_fopenmem (0, "w+b"); + if (!body) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); + goto leave; + } + + if (!ctx->draft_version_2) + { + /* It is fine to use 8 bit encoding because that is encrypted and + * only our client will see it. */ + es_fputs ("Content-Type: application/vnd.gnupg.wks\n" + "Content-Transfer-Encoding: 8bit\n" + "\n", + body); + } + + es_fprintf (body, ("type: confirmation-request\n" + "sender: %s\n" + "address: %s\n" + "fingerprint: %s\n" + "nonce: %s\n"), + from, + mbox, + ctx->fpr, + nonce); + + es_rewind (body); + err = encrypt_stream (&bodyenc, body, keyfile); + if (err) + goto leave; + es_fclose (body); + body = NULL; + + + err = mime_maker_new (&mime, NULL); + if (err) + goto leave; + err = mime_maker_add_header (mime, "From", from); + if (err) + goto leave; + err = mime_maker_add_header (mime, "To", mbox); + if (err) + goto leave; + err = mime_maker_add_header (mime, "Subject", "Confirm your key publication"); + if (err) + goto leave; + + err = mime_maker_add_header (mime, "Wks-Draft-Version", + STR2(WKS_DRAFT_VERSION)); + if (err) + goto leave; + + /* Help Enigmail to identify messages. Note that this is in no way + * secured. */ + err = mime_maker_add_header (mime, "WKS-Phase", "confirm"); + if (err) + goto leave; + + for (sl = opt.extra_headers; sl; sl = sl->next) + { + err = mime_maker_add_header (mime, sl->d, NULL); + if (err) + goto leave; + } + + if (!ctx->draft_version_2) + { + err = mime_maker_add_header (mime, "Content-Type", + "multipart/encrypted; " + "protocol=\"application/pgp-encrypted\""); + if (err) + goto leave; + err = mime_maker_add_container (mime); + if (err) + goto leave; + + err = mime_maker_add_header (mime, "Content-Type", + "application/pgp-encrypted"); + if (err) + goto leave; + err = mime_maker_add_body (mime, "Version: 1\n"); + if (err) + goto leave; + err = mime_maker_add_header (mime, "Content-Type", + "application/octet-stream"); + if (err) + goto leave; + + err = mime_maker_add_stream (mime, &bodyenc); + if (err) + goto leave; + + } + else + { + unsigned int partid; + + /* FIXME: Add micalg. */ + err = mime_maker_add_header (mime, "Content-Type", + "multipart/signed; " + "protocol=\"application/pgp-signature\""); + if (err) + goto leave; + err = mime_maker_add_container (mime); + if (err) + goto leave; + + err = mime_maker_add_header (mime, "Content-Type", "multipart/mixed"); + if (err) + goto leave; + + err = mime_maker_add_container (mime); + if (err) + goto leave; + partid = mime_maker_get_partid (mime); + + err = mime_maker_add_header (mime, "Content-Type", "text/plain"); + if (err) + goto leave; + + err = mime_maker_add_body + (mime, + "This message has been send to confirm your request\n" + "to publish your key. If you did not request a key\n" + "publication, simply ignore this message.\n" + "\n" + "Most mail software can handle this kind of message\n" + "automatically and thus you would not have seen this\n" + "message. It seems that your client does not fully\n" + "support this service. The web page\n" + "\n" + " https://gnupg.org/faq/wkd.html\n" + "\n" + "explains how you can process this message anyway in\n" + "a few manual steps.\n"); + if (err) + goto leave; + + err = mime_maker_add_header (mime, "Content-Type", + "application/vnd.gnupg.wks"); + if (err) + goto leave; + + err = mime_maker_add_stream (mime, &bodyenc); + if (err) + goto leave; + + err = mime_maker_end_container (mime); + if (err) + goto leave; + + /* mime_maker_dump_tree (mime); */ + err = mime_maker_get_part (mime, partid, &signeddata); + if (err) + goto leave; + + err = sign_stream (&signature, signeddata, from); + if (err) + goto leave; + + err = mime_maker_add_header (mime, "Content-Type", + "application/pgp-signature"); + if (err) + goto leave; + + err = mime_maker_add_stream (mime, &signature); + if (err) + goto leave; + } + + err = wks_send_mime (mime); + + leave: + mime_maker_release (mime); + es_fclose (signature); + es_fclose (signeddata); + es_fclose (bodyenc); + es_fclose (body); + xfree (from_buffer); + return err; +} + + +/* Store the key given by KEY into the pending directory and send a + * confirmation requests. */ +static gpg_error_t +process_new_key (server_ctx_t ctx, estream_t key) +{ + gpg_error_t err; + uidinfo_list_t sl; + const char *s; + char *dname = NULL; + char *nonce = NULL; + char *fname = NULL; + struct policy_flags_s policybuf; + + memset (&policybuf, 0, sizeof policybuf); + + /* First figure out the user id from the key. */ + xfree (ctx->fpr); + free_uidinfo_list (ctx->mboxes); + err = wks_list_key (key, &ctx->fpr, &ctx->mboxes); + if (err) + goto leave; + log_assert (ctx->fpr); + log_info ("fingerprint: %s\n", ctx->fpr); + for (sl = ctx->mboxes; sl; sl = sl->next) + { + if (sl->mbox) + log_info (" addr-spec: %s\n", sl->mbox); + } + + /* Walk over all user ids and send confirmation requests for those + * we support. */ + for (sl = ctx->mboxes; sl; sl = sl->next) + { + if (!sl->mbox) + continue; + s = strchr (sl->mbox, '@'); + log_assert (s && s[1]); + xfree (dname); + dname = make_filename_try (opt.directory, s+1, NULL); + if (!dname) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (gnupg_access (dname, W_OK)) + { + log_info ("skipping address '%s': Domain not configured\n", sl->mbox); + continue; + } + if (get_policy_flags (&policybuf, sl->mbox)) + { + log_info ("skipping address '%s': Bad policy flags\n", sl->mbox); + continue; + } + + if (policybuf.auth_submit) + { + /* Bypass the confirmation stuff and publish the key as is. */ + log_info ("publishing address '%s'\n", sl->mbox); + /* FIXME: We need to make sure that we do this only for the + * address in the mail. */ + log_debug ("auth-submit not yet working!\n"); + } + else + { + log_info ("storing address '%s'\n", sl->mbox); + + xfree (nonce); + xfree (fname); + err = store_key_as_pending (dname, key, sl->mbox, &nonce, &fname); + if (err) + goto leave; + + err = send_confirmation_request (ctx, sl->mbox, nonce, fname); + if (err) + goto leave; + } + } + + leave: + if (nonce) + wipememory (nonce, strlen (nonce)); + xfree (nonce); + xfree (fname); + xfree (dname); + wks_free_policy (&policybuf); + return err; +} + + + +/* Send a message to tell the user at MBOX that their key has been + * published. FNAME the name of the file with the key. */ +static gpg_error_t +send_congratulation_message (const char *mbox, const char *keyfile) +{ + gpg_error_t err; + estream_t body = NULL; + estream_t bodyenc = NULL; + mime_maker_t mime = NULL; + char *from_buffer = NULL; + const char *from; + strlist_t sl; + + from = from_buffer = get_submission_address (mbox); + if (!from) + { + from = opt.default_from; + if (!from) + { + log_error ("no sender address found for '%s'\n", mbox); + err = gpg_error (GPG_ERR_CONFIGURATION); + goto leave; + } + log_info ("Note: using default sender address '%s'\n", from); + } + + body = es_fopenmem (0, "w+b"); + if (!body) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); + goto leave; + } + /* It is fine to use 8 bit encoding because that is encrypted and + * only our client will see it. */ + es_fputs ("Content-Type: text/plain; charset=utf-8\n" + "Content-Transfer-Encoding: 8bit\n" + "\n", + body); + + es_fprintf (body, + "Hello!\n\n" + "The key for your address '%s' has been published\n" + "and can now be retrieved from the Web Key Directory.\n" + "\n" + "For more information on this system see:\n" + "\n" + " https://gnupg.org/faq/wkd.html\n" + "\n" + "Best regards\n" + "\n" + " Gnu Key Publisher\n\n\n" + "-- \n" + "The GnuPG Project welcomes donations: %s\n", + mbox, "https://gnupg.org/donate"); + + es_rewind (body); + err = encrypt_stream (&bodyenc, body, keyfile); + if (err) + goto leave; + es_fclose (body); + body = NULL; + + err = mime_maker_new (&mime, NULL); + if (err) + goto leave; + err = mime_maker_add_header (mime, "From", from); + if (err) + goto leave; + err = mime_maker_add_header (mime, "To", mbox); + if (err) + goto leave; + err = mime_maker_add_header (mime, "Subject", "Your key has been published"); + if (err) + goto leave; + err = mime_maker_add_header (mime, "Wks-Draft-Version", + STR2(WKS_DRAFT_VERSION)); + if (err) + goto leave; + err = mime_maker_add_header (mime, "WKS-Phase", "done"); + if (err) + goto leave; + for (sl = opt.extra_headers; sl; sl = sl->next) + { + err = mime_maker_add_header (mime, sl->d, NULL); + if (err) + goto leave; + } + + err = mime_maker_add_header (mime, "Content-Type", + "multipart/encrypted; " + "protocol=\"application/pgp-encrypted\""); + if (err) + goto leave; + err = mime_maker_add_container (mime); + if (err) + goto leave; + + err = mime_maker_add_header (mime, "Content-Type", + "application/pgp-encrypted"); + if (err) + goto leave; + err = mime_maker_add_body (mime, "Version: 1\n"); + if (err) + goto leave; + err = mime_maker_add_header (mime, "Content-Type", + "application/octet-stream"); + if (err) + goto leave; + + err = mime_maker_add_stream (mime, &bodyenc); + if (err) + goto leave; + + err = wks_send_mime (mime); + + leave: + mime_maker_release (mime); + es_fclose (bodyenc); + es_fclose (body); + xfree (from_buffer); + return err; +} + + +/* Check that we have send a request with NONCE and publish the key. */ +static gpg_error_t +check_and_publish (server_ctx_t ctx, const char *address, const char *nonce) +{ + gpg_error_t err; + char *fname = NULL; + char *fnewname = NULL; + estream_t key = NULL; + char *hash = NULL; + char *pendingname = NULL; + const char *domain; + const char *s; + uidinfo_list_t sl; + char shaxbuf[32]; /* Used for SHA-1 and SHA-256 */ + + /* FIXME: There is a bug in name-value.c which adds white space for + * the last pair and thus we strip the nonce here until this has + * been fixed. */ + char *nonce2 = xstrdup (nonce); + trim_trailing_spaces (nonce2); + nonce = nonce2; + + + domain = strchr (address, '@'); + log_assert (domain && domain[1]); + domain++; + if (strchr (domain, '/') || strchr (domain, '\\') + || strchr (nonce, '/') || strchr (nonce, '\\')) + { + log_info ("invalid domain or nonce received ('%s', '%s')\n", + domain, nonce); + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + + err = make_pending_fname (nonce, address, &pendingname); + if (err) + goto leave; + + fname = make_filename_try (opt.directory, domain, "pending", pendingname, + NULL); + if (!fname) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Try to open the file with the key. */ + key = es_fopen (fname, "rb"); + if (!key) + { + err = gpg_error_from_syserror (); + if (gpg_err_code (err) == GPG_ERR_ENOENT) + { + log_info ("no pending request for '%s'\n", address); + err = gpg_error (GPG_ERR_NOT_FOUND); + } + else + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + + /* We need to get the fingerprint from the key. */ + xfree (ctx->fpr); + free_uidinfo_list (ctx->mboxes); + err = wks_list_key (key, &ctx->fpr, &ctx->mboxes); + if (err) + goto leave; + log_assert (ctx->fpr); + log_info ("fingerprint: %s\n", ctx->fpr); + for (sl = ctx->mboxes; sl; sl = sl->next) + if (sl->mbox) + log_info (" addr-spec: %s\n", sl->mbox); + + /* Check that the key has 'address' as a user id. We use + * case-insensitive matching because the client is expected to + * return the address verbatim. */ + for (sl = ctx->mboxes; sl; sl = sl->next) + if (sl->mbox && !strcmp (sl->mbox, address)) + break; + if (!sl) + { + log_error ("error publishing key: '%s' is not a user ID of %s\n", + address, ctx->fpr); + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + + /* Hash user ID and create filename. */ + err = wks_compute_hu_fname (&fnewname, address); + if (err) + goto leave; + + /* Publish. */ + err = copy_key_as_binary (fname, fnewname, address); + if (err) + { + err = gpg_error_from_syserror (); + log_error ("copying '%s' to '%s' failed: %s\n", + fname, fnewname, gpg_strerror (err)); + goto leave; + } + + /* Make sure it is world readable. */ + if (gnupg_chmod (fnewname, "-rw-r--r--")) + log_error ("can't set permissions of '%s': %s\n", + fnewname, gpg_strerror (gpg_err_code_from_syserror())); + + log_info ("key %s published for '%s'\n", ctx->fpr, address); + send_congratulation_message (address, fnewname); + + /* Try to publish as DANE record if the DANE directory exists. */ + xfree (fname); + fname = fnewname; + fnewname = make_filename_try (opt.directory, domain, "dane", NULL); + if (!fnewname) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (!gnupg_access (fnewname, W_OK)) + { + /* Yes, we have a dane directory. */ + s = strchr (address, '@'); + log_assert (s); + gcry_md_hash_buffer (GCRY_MD_SHA256, shaxbuf, address, s - address); + xfree (hash); + hash = bin2hex (shaxbuf, 28, NULL); + if (!hash) + { + err = gpg_error_from_syserror (); + goto leave; + } + xfree (fnewname); + fnewname = make_filename_try (opt.directory, domain, "dane", hash, NULL); + if (!fnewname) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = copy_key_as_dane (fname, fnewname); + if (err) + goto leave; + log_info ("key %s published for '%s' (DANE record)\n", ctx->fpr, address); + } + + leave: + es_fclose (key); + xfree (hash); + xfree (fnewname); + xfree (fname); + xfree (nonce2); + xfree (pendingname); + return err; +} + + +/* Process a confirmation response in MSG. */ +static gpg_error_t +process_confirmation_response (server_ctx_t ctx, estream_t msg) +{ + gpg_error_t err; + nvc_t nvc; + nve_t item; + const char *value, *sender, *address, *nonce; + + err = nvc_parse (&nvc, NULL, msg); + if (err) + { + log_error ("parsing the WKS message failed: %s\n", gpg_strerror (err)); + goto leave; + } + + if (opt.debug) + { + log_debug ("response follows:\n"); + nvc_write (nvc, log_get_stream ()); + } + + /* Check that this is a confirmation response. */ + if (!((item = nvc_lookup (nvc, "type:")) && (value = nve_value (item)) + && !strcmp (value, "confirmation-response"))) + { + if (item && value) + log_error ("received unexpected wks message '%s'\n", value); + else + log_error ("received invalid wks message: %s\n", "'type' missing"); + err = gpg_error (GPG_ERR_UNEXPECTED_MSG); + goto leave; + } + + /* Get the sender. */ + if (!((item = nvc_lookup (nvc, "sender:")) && (value = nve_value (item)) + && is_valid_mailbox (value))) + { + log_error ("received invalid wks message: %s\n", + "'sender' missing or invalid"); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + sender = value; + (void)sender; + /* FIXME: Do we really need the sender?. */ + + /* Get the address. */ + if (!((item = nvc_lookup (nvc, "address:")) && (value = nve_value (item)) + && is_valid_mailbox (value))) + { + log_error ("received invalid wks message: %s\n", + "'address' missing or invalid"); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + address = value; + + /* Get the nonce. */ + if (!((item = nvc_lookup (nvc, "nonce:")) && (value = nve_value (item)) + && strlen (value) > 16)) + { + log_error ("received invalid wks message: %s\n", + "'nonce' missing or too short"); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + nonce = value; + + err = check_and_publish (ctx, address, nonce); + + + leave: + nvc_release (nvc); + return err; +} + + + +/* Called from the MIME receiver to process the plain text data in MSG . */ +static gpg_error_t +command_receive_cb (void *opaque, const char *mediatype, + estream_t msg, unsigned int flags) +{ + gpg_error_t err; + struct server_ctx_s ctx; + + (void)opaque; + + memset (&ctx, 0, sizeof ctx); + if ((flags & WKS_RECEIVE_DRAFT2)) + ctx.draft_version_2 = 1; + + if (!strcmp (mediatype, "application/pgp-keys")) + err = process_new_key (&ctx, msg); + else if (!strcmp (mediatype, "application/vnd.gnupg.wks")) + err = process_confirmation_response (&ctx, msg); + else + { + log_info ("ignoring unexpected message of type '%s'\n", mediatype); + err = gpg_error (GPG_ERR_UNEXPECTED_MSG); + } + + xfree (ctx.fpr); + free_uidinfo_list (ctx.mboxes); + + return err; +} + + + +/* Return a list of all configured domains. Each list element is the + * top directory for the domain. To figure out the actual domain + * name strrchr(name, '/') can be used. */ +static gpg_error_t +get_domain_list (strlist_t *r_list) +{ + gpg_error_t err; + gnupg_dir_t dir = NULL; + char *fname = NULL; + gnupg_dirent_t dentry; + struct stat sb; + strlist_t list = NULL; + + *r_list = NULL; + + dir = gnupg_opendir (opt.directory); + if (!dir) + { + err = gpg_error_from_syserror (); + goto leave; + } + + while ((dentry = gnupg_readdir (dir))) + { + if (*dentry->d_name == '.') + continue; + if (!strchr (dentry->d_name, '.')) + continue; /* No dot - can't be a domain subdir. */ + + xfree (fname); + fname = make_filename_try (opt.directory, dentry->d_name, NULL); + if (!fname) + { + err = gpg_error_from_syserror (); + log_error ("make_filename failed in %s: %s\n", + __func__, gpg_strerror (err)); + goto leave; + } + + if (gnupg_stat (fname, &sb)) + { + err = gpg_error_from_syserror (); + log_error ("error accessing '%s': %s\n", fname, gpg_strerror (err)); + continue; + } + if (!S_ISDIR(sb.st_mode)) + continue; + + if (!add_to_strlist_try (&list, fname)) + { + err = gpg_error_from_syserror (); + log_error ("add_to_strlist failed in %s: %s\n", + __func__, gpg_strerror (err)); + goto leave; + } + } + err = 0; + *r_list = list; + list = NULL; + + leave: + free_strlist (list); + gnupg_closedir (dir); + xfree (fname); + return err; +} + + + +static gpg_error_t +expire_one_domain (const char *top_dirname, const char *domain) +{ + gpg_error_t err; + char *dirname; + char *fname = NULL; + gnupg_dir_t dir = NULL; + gnupg_dirent_t dentry; + struct stat sb; + time_t now = gnupg_get_time (); + + dirname = make_filename_try (top_dirname, "pending", NULL); + if (!dirname) + { + err = gpg_error_from_syserror (); + log_error ("make_filename failed in %s: %s\n", + __func__, gpg_strerror (err)); + goto leave; + } + + dir = gnupg_opendir (dirname); + if (!dir) + { + err = gpg_error_from_syserror (); + log_error (("can't access directory '%s': %s\n"), + dirname, gpg_strerror (err)); + goto leave; + } + + while ((dentry = gnupg_readdir (dir))) + { + if (*dentry->d_name == '.') + continue; + xfree (fname); + fname = make_filename_try (dirname, dentry->d_name, NULL); + if (!fname) + { + err = gpg_error_from_syserror (); + log_error ("make_filename failed in %s: %s\n", + __func__, gpg_strerror (err)); + goto leave; + } + /* The old files are 32 bytes, those created since 2.3.8 are 65 bytes. */ + if (strlen (dentry->d_name) != 32 && strlen (dentry->d_name) != 65) + { + log_info ("garbage file '%s' ignored\n", fname); + continue; + } + if (gnupg_stat (fname, &sb)) + { + err = gpg_error_from_syserror (); + log_error ("error accessing '%s': %s\n", fname, gpg_strerror (err)); + continue; + } + if (S_ISDIR(sb.st_mode)) + { + log_info ("garbage directory '%s' ignored\n", fname); + continue; + } + if (sb.st_mtime + PENDING_TTL < now) + { + if (opt.verbose) + log_info ("domain %s: removing pending key '%s'\n", + domain, dentry->d_name); + if (remove (fname)) + { + err = gpg_error_from_syserror (); + /* In case the file has just been renamed or another + * processes is cleaning up, we don't print a diagnostic + * for ENOENT. */ + if (gpg_err_code (err) != GPG_ERR_ENOENT) + log_error ("error removing '%s': %s\n", + fname, gpg_strerror (err)); + } + } + } + err = 0; + + leave: + gnupg_closedir (dir); + xfree (dirname); + xfree (fname); + return err; + +} + + +/* Scan spool directories and expire too old pending keys. */ +static gpg_error_t +expire_pending_confirmations (strlist_t domaindirs) +{ + gpg_error_t err = 0; + strlist_t sl; + const char *domain; + + for (sl = domaindirs; sl; sl = sl->next) + { + domain = strrchr (sl->d, '/'); + log_assert (domain); + domain++; + + expire_one_domain (sl->d, domain); + } + + return err; +} + + +/* List all configured domains. */ +static gpg_error_t +command_list_domains (void) +{ + static struct { + const char *name; + const char *perm; + } requireddirs[] = { + { "pending", "-rwx" }, + { "hu", "-rwxr-xr-x" } + }; + gpg_err_code_t ec; + gpg_error_t err; + strlist_t domaindirs; + strlist_t sl; + const char *domain; + char *fname = NULL; + int i; + estream_t fp; + + err = get_domain_list (&domaindirs); + if (err) + { + log_error ("error reading list of domains: %s\n", gpg_strerror (err)); + return err; + } + + for (sl = domaindirs; sl; sl = sl->next) + { + domain = strrchr (sl->d, '/'); + log_assert (domain); + domain++; + if (opt_with_dir) + es_printf ("%s %s\n", domain, sl->d); + else + es_printf ("%s\n", domain); + + + /* Check that the required directories are there. */ + for (i=0; i < DIM (requireddirs); i++) + { + xfree (fname); + fname = make_filename_try (sl->d, requireddirs[i].name, NULL); + if (!fname) + { + err = gpg_error_from_syserror (); + goto leave; + } + if ((ec = gnupg_access (fname, W_OK))) + { + err = gpg_error (ec); + if (gpg_err_code (err) == GPG_ERR_ENOENT) + { + if (gnupg_mkdir (fname, requireddirs[i].perm)) + { + err = gpg_error_from_syserror (); + log_error ("domain %s: error creating subdir '%s': %s\n", + domain, requireddirs[i].name, + gpg_strerror (err)); + } + else + log_info ("domain %s: subdir '%s' created\n", + domain, requireddirs[i].name); + } + else if (err) + log_error ("domain %s: problem with subdir '%s': %s\n", + domain, requireddirs[i].name, gpg_strerror (err)); + } + } + + /* Print a warning if the submission address is not configured. */ + xfree (fname); + fname = make_filename_try (sl->d, "submission-address", NULL); + if (!fname) + { + err = gpg_error_from_syserror (); + goto leave; + } + if ((ec = gnupg_access (fname, F_OK))) + { + err = gpg_error (ec); + if (gpg_err_code (err) == GPG_ERR_ENOENT) + log_error ("domain %s: submission address not configured\n", + domain); + else + log_error ("domain %s: problem with '%s': %s\n", + domain, fname, gpg_strerror (err)); + } + + /* Check the syntax of the optional policy file. */ + xfree (fname); + fname = make_filename_try (sl->d, "policy", NULL); + if (!fname) + { + err = gpg_error_from_syserror (); + goto leave; + } + fp = es_fopen (fname, "r"); + if (!fp) + { + err = gpg_error_from_syserror (); + if (gpg_err_code (err) == GPG_ERR_ENOENT) + { + fp = es_fopen (fname, "w"); + if (!fp) + log_error ("domain %s: can't create policy file: %s\n", + domain, gpg_strerror (err)); + else + es_fclose (fp); + fp = NULL; + } + else + log_error ("domain %s: error in policy file: %s\n", + domain, gpg_strerror (err)); + } + else + { + struct policy_flags_s policy; + err = wks_parse_policy (&policy, fp, 0); + es_fclose (fp); + wks_free_policy (&policy); + } + } + err = 0; + + leave: + xfree (fname); + free_strlist (domaindirs); + return err; +} + + +/* Run regular maintenance jobs. */ +static gpg_error_t +command_cron (void) +{ + gpg_error_t err; + strlist_t domaindirs; + + err = get_domain_list (&domaindirs); + if (err) + { + log_error ("error reading list of domains: %s\n", gpg_strerror (err)); + return err; + } + + err = expire_pending_confirmations (domaindirs); + + free_strlist (domaindirs); + return err; +} + + +/* Check whether the key with USER_ID is installed. */ +static gpg_error_t +command_check_key (const char *userid) +{ + gpg_err_code_t ec; + gpg_error_t err; + char *addrspec = NULL; + char *fname = NULL; + + err = wks_fname_from_userid (userid, 0, &fname, &addrspec); + if (err) + goto leave; + + if ((ec = gnupg_access (fname, R_OK))) + { + err = gpg_error (ec); + if (opt_with_file) + es_printf ("%s n %s\n", addrspec, fname); + if (gpg_err_code (err) == GPG_ERR_ENOENT) + { + if (!opt.quiet) + log_info ("key for '%s' is NOT installed\n", addrspec); + log_inc_errorcount (); + err = 0; + } + else + log_error ("error stating '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + + if (opt_with_file) + es_printf ("%s i %s\n", addrspec, fname); + + if (opt.verbose) + log_info ("key for '%s' is installed\n", addrspec); + err = 0; + + leave: + xfree (fname); + xfree (addrspec); + return err; +} + + +/* Revoke the key with mail address MAILADDR. */ +static gpg_error_t +command_revoke_key (const char *mailaddr) +{ + /* Remove should be different from removing but we have not yet + * defined a suitable way to do this. */ + return wks_cmd_remove_key (mailaddr); +} diff --git a/tools/gpg-wks.h b/tools/gpg-wks.h new file mode 100644 index 0000000..32aa8c3 --- /dev/null +++ b/tools/gpg-wks.h @@ -0,0 +1,128 @@ +/* gpg-wks.h - Common definitions for wks server and client. + * Copyright (C) 2016 g10 Code GmbH + * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef GNUPG_GPG_WKS_H +#define GNUPG_GPG_WKS_H + +#include "../common/util.h" +#include "../common/strlist.h" +#include "mime-maker.h" + +/* The draft version we implement. */ +#define WKS_DRAFT_VERSION 3 + + +/* We keep all global options in the structure OPT. */ +EXTERN_UNLESS_MAIN_MODULE +struct +{ + int verbose; + unsigned int debug; + int quiet; + int use_sendmail; + int with_colons; + int no_autostart; + const char *output; + const char *gpg_program; + const char *directory; + const char *default_from; + strlist_t extra_headers; +} opt; + +/* Debug values and macros. */ +#define DBG_MIME_VALUE 1 /* Debug the MIME structure. */ +#define DBG_PARSER_VALUE 2 /* Debug the Mail parser. */ +#define DBG_CRYPTO_VALUE 4 /* Debug low level crypto. */ +#define DBG_MEMORY_VALUE 32 /* Debug memory allocation stuff. */ +#define DBG_MEMSTAT_VALUE 128 /* Show memory statistics. */ +#define DBG_IPC_VALUE 1024 /* Debug assuan communication. */ +#define DBG_EXTPROG_VALUE 16384 /* debug external program calls */ + +#define DBG_MIME (opt.debug & DBG_MIME_VALUE) +#define DBG_PARSER (opt.debug & DBG_PARSER_VALUE) +#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) + + +/* The parsed policy flags. */ +struct policy_flags_s +{ + char *submission_address; + unsigned int mailbox_only : 1; + unsigned int dane_only : 1; + unsigned int auth_submit : 1; + unsigned int protocol_version; /* The supported WKS_DRAFT_VERION or 0 */ + unsigned int max_pending; /* Seconds to wait for a confirmation. */ +}; +typedef struct policy_flags_s *policy_flags_t; + + +/* An object to convey user ids of a key. */ +struct uidinfo_list_s +{ + struct uidinfo_list_s *next; + time_t created; /* Time the userid was created. */ + char *mbox; /* NULL or the malloced mailbox from UID. */ + unsigned int flags; /* These flags are cleared on creation. */ + char uid[1]; +}; +typedef struct uidinfo_list_s *uidinfo_list_t; + + + +/*-- wks-util.c --*/ +void wks_set_status_fd (int fd); +void wks_write_status (int no, const char *format, ...) GPGRT_ATTR_PRINTF(2,3); +void free_uidinfo_list (uidinfo_list_t list); +gpg_error_t wks_get_key (estream_t *r_key, const char *fingerprint, + const char *addrspec, int exact); +gpg_error_t wks_list_key (estream_t key, char **r_fpr, + uidinfo_list_t *r_mboxes); +gpg_error_t wks_filter_uid (estream_t *r_newkey, estream_t key, + const char *uid, int binary); +gpg_error_t wks_send_mime (mime_maker_t mime); +gpg_error_t wks_parse_policy (policy_flags_t flags, estream_t stream, + int ignore_unknown); +void wks_free_policy (policy_flags_t policy); + +gpg_error_t wks_fname_from_userid (const char *userid, int hash_only, + char **r_fname, char **r_addrspec); +gpg_error_t wks_compute_hu_fname (char **r_fname, const char *addrspec); +gpg_error_t wks_install_key_core (estream_t key, const char *addrspec); +gpg_error_t wks_cmd_install_key (const char *fname, const char *userid); +gpg_error_t wks_cmd_remove_key (const char *userid); +gpg_error_t wks_cmd_print_wkd_hash (const char *userid); +gpg_error_t wks_cmd_print_wkd_url (const char *userid); + + +/*-- wks-receive.c --*/ + +/* Flag values for the receive callback. */ +#define WKS_RECEIVE_DRAFT2 1 + +gpg_error_t wks_receive (estream_t fp, + gpg_error_t (*result_cb)(void *opaque, + const char *mediatype, + estream_t data, + unsigned int flags), + void *cb_data); + + + +#endif /*GNUPG_GPG_WKS_H*/ diff --git a/tools/gpg-zip.in b/tools/gpg-zip.in new file mode 100644 index 0000000..9047e36 --- /dev/null +++ b/tools/gpg-zip.in @@ -0,0 +1,151 @@ +#!/bin/sh + +# gpg-archive - gpg-ized tar using the same format as PGP's PGP Zip. +# Copyright (C) 2005 Free Software Foundation, Inc. +# +# This file is part of GnuPG. +# +# GnuPG is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# GnuPG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. +# Despite the name, PGP Zip format is actually an OpenPGP-wrapped tar +# file. To be compatible with PGP itself, this must be a USTAR format +# tar file. Unclear on whether there is a distinction here between +# the GNU or POSIX variant of USTAR. + +VERSION=@VERSION@ +TAR=@TAR@ +GPG=gpg + +usage="\ +Usage: gpg-zip [--help] [--version] [--encrypt] [--decrypt] [--symmetric] + [--list-archive] [--output FILE] [--gpg GPG] [--gpg-args ARGS] + [--tar TAR] [--tar-args ARGS] filename1 [filename2, ...] + directory1 [directory2, ...] + +Encrypt or sign files into an archive." + + +echo "gpg-zip: This script is deprecated - please use gpgtar instead." >&2 + +tar_verbose_opt="v" + +while test $# -gt 0 ; do + case $1 in + -h | --help | --h*) + echo "$usage" + exit 0 + ;; + --list-archive) + list=yes + create=no + unpack=no + shift + ;; + --encrypt | -e) + gpg_args="$gpg_args --encrypt" + list=no + create=yes + unpack=no + shift + ;; + --decrypt | -d) + gpg_args="$gpg_args --decrypt" + list=no + create=no + unpack=yes + shift + ;; + --symmetric | -c) + gpg_args="$gpg_args --symmetric" + list=no + create=yes + unpack=no + shift + ;; + --sign | -s) + gpg_args="$gpg_args --sign" + list=no + create=yes + unpack=no + shift + ;; + --recipient | -r) + gpg_args="$gpg_args --recipient $2" + shift + shift + ;; + --local-user | -u) + gpg_args="$gpg_args --local-user $2" + shift + shift + ;; + --output | -o) + gpg_args="$gpg_args --output $2" + shift + shift + ;; + --version) + echo "gpg-zip (GnuPG) $VERSION" + exit 0 + ;; + --gpg) + GPG=$2 + shift + shift + ;; + --gpg-args) + gpg_args="$gpg_args $2" + shift + shift + ;; + --tar) + TAR=$2 + shift + shift + ;; + --tar-args) + tar_args="$tar_args $2" + shift + shift + ;; + --quiet) + tar_verbose_opt="" + shift + ;; + --) + shift + break + ;; + -*) + echo "$usage" 1>&2 + exit 1 + ;; + *) + break + ;; + esac +done + +if test x$create = xyes ; then +# echo "$TAR $tar_args -cf - "$@" | $GPG --set-filename x.tar $gpg_args" 1>&2 + $TAR $tar_args -cf - "$@" | $GPG --set-filename x.tar $gpg_args +elif test x$list = xyes ; then +# echo "cat \"$1\" | $GPG $gpg_args | $TAR $tar_args -tf -" 1>&2 + cat "$1" | $GPG $gpg_args | $TAR $tar_args -tf - +elif test x$unpack = xyes ; then +# echo "cat \"$1\" | $GPG $gpg_args | $TAR $tar_args -xvf -" 1>&2 + cat "$1" | $GPG $gpg_args | $TAR $tar_args -x${tar_verbose_opt}f - +else + echo "$usage" 1>&2 + exit 1 +fi diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c new file mode 100644 index 0000000..c9bdfeb --- /dev/null +++ b/tools/gpgconf-comp.c @@ -0,0 +1,3462 @@ +/* gpgconf-comp.c - Configuration utility for GnuPG. + * Copyright (C) 2004, 2007-2011 Free Software Foundation, Inc. + * Copyright (C) 2016 Werner Koch + * Copyright (C) 2020-2022 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GnuPG; if not, see <https://www.gnu.org/licenses/>. + */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <errno.h> +#include <time.h> +#include <stdarg.h> +#ifdef HAVE_SIGNAL_H +# include <signal.h> +#endif +#include <ctype.h> +#ifdef HAVE_W32_SYSTEM +# define WIN32_LEAN_AND_MEAN 1 +# include <windows.h> +#else +# include <pwd.h> +# include <grp.h> +#endif + +#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 + + + +#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_LOG_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); + + + + +/* STRING_ARRAY is a malloced array with malloced strings. It is used + * a space to store strings so that other objects may point to these + * strings. It shall never be shrinked or any items changes. + * STRING_ARRAY itself may be reallocated to increase the size of the + * table. STRING_ARRAY_USED is the number of items currently used, + * STRING_ARRAY_SIZE is the number of calloced slots. */ +static char **string_array; +static size_t string_array_used; +static size_t string_array_size; + + + +/* 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 components 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 emitted option list are not options, but mark + the beginning of a new group of options. These entries have the + GROUP flag set. Note that this is internally also known as a + header line. */ +#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) +/* The RUNTIME flag for an option indicates that the option can be + changed at runtime. */ +#define GC_OPT_FLAG_RUNTIME (1UL << 3) + + + +/* 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" } + }; + + + +/* Each option we want to support in gpgconf has the needed + * information in a static list per componenet. This struct describes + * the info for a single option. */ +struct known_option_s +{ + /* If this is NULL, then this is a terminator in an array of unknown + * length. 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. */ + unsigned long flags; + + /* The expert level. */ + gc_expert_level_t level; + + /* The complex type of the option argument; the default of 0 is used + * for a standard type as returned by --dump-option-table. */ + gc_arg_type_t arg_type; +}; +typedef struct known_option_s known_option_t; + + +/* The known options of the GC_COMPONENT_GPG_AGENT component. */ +static known_option_t known_options_gpg_agent[] = + { + { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, + { "disable-scdaemon", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, + GC_LEVEL_ADVANCED }, + { "enable-ssh-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, + { "ssh-fingerprint-digest", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, + { "enable-putty-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, + { "enable-extended-key-format", GC_OPT_FLAG_RUNTIME, GC_LEVEL_INVISIBLE }, + { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED}, + { "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, + /**/ GC_ARG_TYPE_FILENAME }, + { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + + { "default-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, + { "default-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, + { "max-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, + { "max-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, + { "ignore-cache-for-signing", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, + { "allow-emacs-pinentry", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, + { "grab", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, + { "no-allow-external-cache", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, + { "no-allow-mark-trusted", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, + { "no-allow-loopback-pinentry", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, + + { "enforce-passphrase-constraints", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, + { "min-passphrase-len", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, + { "min-passphrase-nonalpha", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, + { "check-passphrase-pattern", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, + /**/ GC_ARG_TYPE_FILENAME }, + { "check-sym-passphrase-pattern", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, + /**/ GC_ARG_TYPE_FILENAME }, + { "max-passphrase-days", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, + { "enable-passphrase-history", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, + { "pinentry-timeout", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, + + { NULL } + }; + + +/* The known options of the GC_COMPONENT_SCDAEMON component. */ +static known_option_t known_options_scdaemon[] = + { + { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, + { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + { "reader-port", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, + { "ctapi-driver", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, + { "pcsc-driver", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, + { "disable-ccid", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, + { "disable-pinpad", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, + { "enable-pinpad-varlen", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, + { "card-timeout", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, + { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED}, + { "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, + GC_ARG_TYPE_FILENAME }, + { "deny-admin", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, + + { NULL } + }; + + +/* The known options of the GC_COMPONENT_GPG component. */ +static known_option_t known_options_gpg[] = + { + { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC }, + { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + { "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, + { "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, + { "group", GC_OPT_FLAG_LIST, GC_LEVEL_ADVANCED, + GC_ARG_TYPE_ALIAS_LIST}, + { "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, + { "default-new-key-algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + { "trust-model", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED }, + { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + GC_ARG_TYPE_FILENAME }, + { "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + { "auto-key-locate", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, + { "auto-key-import", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, + { "no-auto-key-import", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + { "auto-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, + { "no-auto-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + { "include-key-block", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, + { "no-include-key-block", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + { "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, + { "max-cert-depth", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + { "completes-needed", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + { "marginals-needed", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + + /* The next is a pseudo option which we read via --gpgconf-list. + * The meta information is taken from the table below. */ + { "default_pubkey_algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + { "compliance_de_vs", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + + { NULL } + }; +static const char *known_pseudo_options_gpg[] = + {/* v-- ARGPARSE_TYPE_STRING */ + "default_pubkey_algo:0:2:@:", + /* A basic compliance check for gpg. We use gpg here but the + * result is valid for all components. + * v-- ARGPARSE_TYPE_INT */ + "compliance_de_vs:0:1:@:", + NULL + }; + + +/* The known options of the GC_COMPONENT_GPGSM component. */ +static known_option_t known_options_gpgsm[] = + { + { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC }, + { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + { "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, + { "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, + { "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, + { "p12-charset", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, + { "keyserver", GC_OPT_FLAG_LIST, GC_LEVEL_INVISIBLE, + GC_ARG_TYPE_LDAP_SERVER }, + { "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, + { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED }, + { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + GC_ARG_TYPE_FILENAME }, + { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + { "disable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, + { "enable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + { "enable-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, + { "include-certs", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, + { "disable-policy-checks", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, + { "auto-issuer-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, + { "cipher-algo", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, + { "disable-trusted-cert-crl-check", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, + + /* Pseudo option follows. See also table below. */ + { "default_pubkey_algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + + { NULL } + }; +static const char *known_pseudo_options_gpgsm[] = + {/* v-- ARGPARSE_TYPE_STRING */ + "default_pubkey_algo:0:2:@:", + NULL + }; + + +/* The known options of the GC_COMPONENT_DIRMNGR component. */ +static known_option_t known_options_dirmngr[] = + { + { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC }, + { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + { "resolver-timeout", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + { "nameserver", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED }, + { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + GC_ARG_TYPE_FILENAME }, + { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + { "force", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, + { "use-tor", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, + { "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, + { "ldapserver", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, + GC_ARG_TYPE_LDAP_SERVER }, + { "disable-http", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, + { "ignore-http-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, + { "http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, + { "honor-http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, + { "disable-ldap", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, + { "ignore-ldap-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, + { "ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, + { "only-ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, + { "add-servers", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, + { "ldaptimeout", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, + { "max-replies", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, + { "allow-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, + { "ocsp-responder", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, + { "ocsp-signer", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, + { "allow-version-check", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, + { "ignore-ocsp-service-url", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, + + + { NULL } + }; + + +/* The known options of the GC_COMPONENT_PINENTRY component. */ +static known_option_t known_options_pinentry[] = + { + { NULL } + }; + + + +/* Our main option info object. We copy all required information from the + * gpgrt_opt_t items but convert the flags value to bit flags. */ +struct gc_option_s +{ + const char *name; /* The same as gpgrt_opt_t.long_opt. */ + const char *desc; /* The same as gpgrt_opt_t.description. */ + + unsigned int is_header:1; /* This is a header item. */ + unsigned int is_list:1; /* This is a list style option. */ + unsigned int opt_arg:1; /* The option's argument is optional. */ + unsigned int runtime:1; /* The option is runtime changeable. */ + + unsigned int gpgconf_list:1; /* Has been announced in gpgconf-list. */ + + unsigned int has_default:1; /* The option has a default value. */ + unsigned int def_in_desc:1; /* The default is in the descrition. */ + unsigned int no_arg_desc:1; /* The argument has a default ???. */ + unsigned int no_change:1; /* User shall not change the option. */ + + unsigned int attr_ignore:1; /* The ARGPARSE_ATTR_IGNORE. */ + unsigned int attr_force:1; /* The ARGPARSE_ATTR_FORCE. */ + + /* The expert level - copied from known_options. */ + gc_expert_level_t level; + + /* The complex type - copied from known_options. */ + gc_arg_type_t arg_type; + + /* The default value for this option. This is NULL if the option is + not present in the component, the empty string if no default is + available, and otherwise a quoted string. This is currently + malloced.*/ + char *default_value; + + /* 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_s gc_option_t; + + + +/* The information associated with each component. */ +static struct +{ + /* The name of the component. Some components don't have an + * associated program, but are implemented directly by GPGConf. In + * this case, PROGRAM is NULL. */ + char *program; + + /* The displayed 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 of this component. */ + const char *desc; + + /* 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 program for the component + * is available. */ + char module_name; + + /* The name for the configuration filename of this component. */ + const char *option_config_filename; + + /* The static table of known options for this component. */ + known_option_t *known_options; + + /* The static table of known pseudo options for this component or NULL. */ + const char **known_pseudo_options; + + /* The runtime change callback. If KILLFLAG is true the component + is killed and not just reloaded. */ + void (*runtime_change) (int killflag); + + /* The table of known options as read from the component including + * header lines and such. This is suitable to be passed to + * gpgrt_argparser. Will be filled in by + * retrieve_options_from_program. */ + gnupg_opt_t *opt_table; + + /* The full table including data from OPT_TABLE. The end of the + * table is marked by NULL entry for NAME. Will be filled in by + * retrieve_options_from_program. */ + gc_option_t *options; + +} gc_component[GC_COMPONENT_NR] = + { + /* Note: The order of the items must match the order given in the + * gc_component_id_t enumeration. The order is often used by + * frontends to display the backend options thus do not change the + * order without considering the user experience. */ + { NULL }, /* DUMMY for GC_COMPONENT_ANY */ + + { GPG_NAME, GPG_DISP_NAME, "gnupg", N_("OpenPGP"), + GNUPG_MODULE_NAME_GPG, GPG_NAME ".conf", + known_options_gpg, known_pseudo_options_gpg }, + + { GPGSM_NAME, GPGSM_DISP_NAME, "gnupg", N_("S/MIME"), + GNUPG_MODULE_NAME_GPGSM, GPGSM_NAME ".conf", + known_options_gpgsm, known_pseudo_options_gpgsm }, + + { GPG_AGENT_NAME, GPG_AGENT_DISP_NAME, "gnupg", N_("Private Keys"), + GNUPG_MODULE_NAME_AGENT, GPG_AGENT_NAME ".conf", + known_options_gpg_agent, NULL, gpg_agent_runtime_change }, + + { SCDAEMON_NAME, SCDAEMON_DISP_NAME, "gnupg", N_("Smartcards"), + GNUPG_MODULE_NAME_SCDAEMON, SCDAEMON_NAME ".conf", + known_options_scdaemon, NULL, scdaemon_runtime_change}, + + { DIRMNGR_NAME, DIRMNGR_DISP_NAME, "gnupg", N_("Network"), + GNUPG_MODULE_NAME_DIRMNGR, DIRMNGR_NAME ".conf", + known_options_dirmngr, NULL, dirmngr_runtime_change }, + + { "pinentry", "Pinentry", "gnupg", N_("Passphrase Entry"), + GNUPG_MODULE_NAME_PINENTRY, NULL, + known_options_pinentry } + }; + + + +/* Structure used to collect error output of the component 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); + int i = 0; + + pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); + if (!gnupg_default_homedir_p ()) + { + argv[i++] = "--homedir"; + argv[i++] = gnupg_homedir (); + } + argv[i++] = "--no-autostart"; + argv[i++] = killflag? "KILLAGENT" : "RELOADAGENT"; + argv[i++] = NULL; + + if (!err) + err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid); + if (!err) + err = gnupg_wait_process (pgmname, pid, 1, NULL); + if (err) + gc_error (0, 0, "error running '%s %s': %s", + pgmname, argv[1], gpg_strerror (err)); + gnupg_release_process (pid); +} + + +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); + int i = 0; + + (void)killflag; /* For scdaemon kill and reload are synonyms. */ + + /* We use "GETINFO app_running" to see whether the agent is already + running and kill it only in this case. This avoids an explicit + starting of the agent in case it is not yet running. There is + obviously a race condition but that should not harm too much. */ + + pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); + if (!gnupg_default_homedir_p ()) + { + argv[i++] = "--homedir"; + argv[i++] = gnupg_homedir (); + } + argv[i++] = "-s"; + argv[i++] = "--no-autostart"; + argv[i++] = "GETINFO scd_running"; + argv[i++] = "/if ${! $?}"; + argv[i++] = "scd killscd"; + argv[i++] = "/end"; + argv[i++] = NULL; + + if (!err) + err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid); + if (!err) + err = gnupg_wait_process (pgmname, pid, 1, NULL); + if (err) + gc_error (0, 0, "error running '%s %s': %s", + pgmname, argv[4], gpg_strerror (err)); + gnupg_release_process (pid); +} + + +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); + int i = 0; + int cmdidx; + + pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); + if (!gnupg_default_homedir_p ()) + { + argv[i++] = "--homedir"; + argv[i++] = gnupg_homedir (); + } + argv[i++] = "--no-autostart"; + argv[i++] = "--dirmngr"; + cmdidx = i; + argv[i++] = killflag? "KILLDIRMNGR" : "RELOADDIRMNGR"; + argv[i] = NULL; + log_assert (i < DIM(argv)); + + 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[cmdidx], gpg_strerror (err)); + gnupg_release_process (pid); +} + + +/* 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[6]; + int i; + pid_t pid; + + if (component < 0) + { + err = gc_component_launch (GC_COMPONENT_GPG_AGENT); + if (!err) + err = gc_component_launch (GC_COMPONENT_DIRMNGR); + return err; + } + + if (!(component == GC_COMPONENT_GPG_AGENT + || component == GC_COMPONENT_DIRMNGR)) + { + log_error ("%s\n", _("Component not suitable for launching")); + gpgconf_failure (0); + } + + if (gc_component_check_options (component, NULL, NULL)) + { + log_error (_("Configuration file of component %s is broken\n"), + gc_component[component].name); + if (!opt.quiet) + log_info (_("Note: Use the command \"%s%s\" to get details.\n"), + gc_component[component].name, " --gpgconf-test"); + gpgconf_failure (0); + } + + pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); + i = 0; + if (!gnupg_default_homedir_p ()) + { + argv[i++] = "--homedir"; + argv[i++] = gnupg_homedir (); + } + if (component == GC_COMPONENT_DIRMNGR) + argv[i++] = "--dirmngr"; + argv[i++] = "NOP"; + argv[i] = NULL; + log_assert (i < DIM(argv)); + + 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; +} + + +static void +do_runtime_change (int component, int killflag) +{ + int runtime[GC_COMPONENT_NR] = { 0 }; + + if (component < 0) + { + for (component = 0; component < GC_COMPONENT_NR; component++) + runtime [component] = 1; + } + else + { + log_assert (component >= 0 && component < GC_COMPONENT_NR); + runtime [component] = 1; + } + + /* Do the restart for the selected components. */ + for (component = GC_COMPONENT_NR-1; component >= 0; component--) + { + if (runtime[component] && gc_component[component].runtime_change) + (*gc_component[component].runtime_change) (killflag); + } +} + + +/* Unconditionally restart COMPONENT. */ +void +gc_component_kill (int component) +{ + do_runtime_change (component, 1); +} + + +/* Unconditionally reload COMPONENT or all components if COMPONENT is -1. */ +void +gc_component_reload (int component) +{ + do_runtime_change (component, 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) +{ + if (!msgid || !*msgid) + return msgid; /* Shortcut form "" which has the PO files meta data. */ + +#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, gnupg_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_id_t component; + const char *desc; + const char *pgmname; + + for (component = 0; component < GC_COMPONENT_NR; component++) + { + if (!gc_component[component].program) + continue; + if (gc_component[component].module_name) + pgmname = gnupg_module_name (gc_component[component].module_name); + else + pgmname = ""; + + desc = gc_component[component].desc; + desc = my_dgettext (gc_component[component].desc_domain, desc); + es_fprintf (out, "%s:%s:", + gc_component[component].program, gc_percent_escape (desc)); + es_fprintf (out, "%s\n", gc_percent_escape (pgmname)); + } +} + + + +static int +all_digits_p (const char *p, size_t len) +{ + if (!len) + return 0; /* No. */ + for (; len; len--, p++) + if (!isascii (*p) || !isdigit (*p)) + return 0; /* No. */ + return 1; /* Yes. */ +} + + +/* Collect all error lines from stream FP. Only lines prefixed with + TAG are considered. Returns a list of error line items (which may + be empty). There is no error return. */ +static error_line_t +collect_error_output (estream_t fp, const char *tag) +{ + char buffer[1024]; + char *p, *p2, *p3; + int c, cont_line; + unsigned int pos; + error_line_t eitem, errlines, *errlines_tail; + size_t taglen = strlen (tag); + + errlines = NULL; + errlines_tail = &errlines; + pos = 0; + cont_line = 0; + while ((c=es_getc (fp)) != EOF) + { + buffer[pos++] = c; + if (pos >= sizeof buffer - 5 || c == '\n') + { + buffer[pos - (c == '\n')] = 0; + if (cont_line) + ; /*Ignore continuations of previous line. */ + else if (!strncmp (buffer, tag, taglen) && buffer[taglen] == ':') + { + /* "gpgsm: foo:4: bla" */ + /* Yep, we are interested in this line. */ + p = buffer + taglen + 1; + while (*p == ' ' || *p == '\t') + p++; + trim_trailing_spaces (p); /* Get rid of extra CRs. */ + if (!*p) + ; /* Empty lines are ignored. */ + else if ( (p2 = strchr (p, ':')) && (p3 = strchr (p2+1, ':')) + && all_digits_p (p2+1, p3 - (p2+1))) + { + /* Line in standard compiler format. */ + p3++; + while (*p3 == ' ' || *p3 == '\t') + p3++; + eitem = xmalloc (sizeof *eitem + strlen (p)); + eitem->next = NULL; + strcpy (eitem->buffer, p); + eitem->fname = eitem->buffer; + eitem->buffer[p2-p] = 0; + eitem->errtext = eitem->buffer + (p3 - p); + /* (we already checked that there are only ascii + digits followed by a colon) */ + eitem->lineno = 0; + for (p2++; isdigit (*p2); p2++) + eitem->lineno = eitem->lineno*10 + (*p2 - '0'); + *errlines_tail = eitem; + errlines_tail = &eitem->next; + } + else + { + /* Other error output. */ + eitem = xmalloc (sizeof *eitem + strlen (p)); + eitem->next = NULL; + strcpy (eitem->buffer, p); + eitem->fname = NULL; + eitem->errtext = eitem->buffer; + eitem->lineno = 0; + *errlines_tail = eitem; + errlines_tail = &eitem->next; + } + } + pos = 0; + /* If this was not a complete line mark that we are in a + continuation. */ + cont_line = (c != '\n'); + } + } + + /* We ignore error lines not terminated by a LF. */ + return errlines; +} + + +/* Check the options of a single component. If CONF_FILE is NULL the + * standard config file is used. If OUT is not NULL the output is + * written to that stream. Returns 0 if everything is OK. */ +int +gc_component_check_options (int component, estream_t out, const char *conf_file) +{ + gpg_error_t err; + unsigned int result; + const char *pgmname; + const char *argv[6]; + int i; + pid_t pid; + int exitcode; + estream_t errfp; + error_line_t errlines; + + log_assert (component >= 0 && component < GC_COMPONENT_NR); + + if (!gc_component[component].program) + return 0; + if (!gc_component[component].module_name) + return 0; + + pgmname = gnupg_module_name (gc_component[component].module_name); + i = 0; + if (!gnupg_default_homedir_p () + && component != GC_COMPONENT_PINENTRY) + { + argv[i++] = "--homedir"; + argv[i++] = gnupg_homedir (); + } + 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].program, 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_id_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_id_t idx; + + for (idx = 0; idx < GC_COMPONENT_NR; idx++) + { + if (gc_component[idx].program + && !strcmp (name, gc_component[idx].program)) + return idx; + } + return -1; +} + + +/* List the option OPTION. */ +static void +list_one_option (gc_component_id_t component, + const gc_option_t *option, estream_t out) +{ + const char *desc = NULL; + char *arg_name = NULL; + unsigned long flags; + const char *desc_domain = gc_component[component].desc_domain; + + /* Don't show options with the ignore attribute. */ + if (option->attr_ignore && !option->attr_force) + return; + + if (option->desc) + { + desc = my_dgettext (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. */ + flags = 0; + if (option->is_header) flags |= GC_OPT_FLAG_GROUP; + if (option->is_list) flags |= GC_OPT_FLAG_LIST; + if (option->runtime) flags |= GC_OPT_FLAG_RUNTIME; + if (option->has_default) flags |= GC_OPT_FLAG_DEFAULT; + if (option->def_in_desc) flags |= GC_OPT_FLAG_DEF_DESC; + if (option->no_arg_desc) flags |= GC_OPT_FLAG_NO_ARG_DESC; + if (option->no_change) flags |= GC_OPT_FLAG_NO_CHANGE; + if (option->attr_force) flags |= GC_OPT_FLAG_NO_CHANGE; + es_fprintf (out, ":%lu", flags); + if (opt.verbose) + { + es_putc (' ', out); + + if (!flags) + es_fprintf (out, "none"); + else + { + 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. This was never used and is thus empty. */ + es_fprintf (out, ":"); + + /* The value field. */ + if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE + && option->is_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; + + for ( ; option && option->name; option++) + { + /* Do not output unknown or internal options. */ + if (!option->is_header + && option->level == GC_LEVEL_INTERNAL) + continue; + + if (option->is_header) + { + 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. */ + + for ( ; group_option->name; group_option++) + { + if (group_option->is_header) + break; + if (group_option->level < level) + level = group_option->level; + } + + /* Check if group is empty. */ + if (level != GC_LEVEL_NR) + { + gc_option_t opt_copy; + + /* Fix up the group level. */ + opt_copy = *option; + opt_copy.level = level; + list_one_option (component, &opt_copy, out); + } + } + else + list_one_option (component, option, out); + } +} + + +/* Return true if the option NAME is known and that we want it as + * gpgconf managed option. */ +static known_option_t * +is_known_option (gc_component_id_t component, const char *name) +{ + known_option_t *option = gc_component[component].known_options; + if (option) + { + for (; option->name; option++) + if (!strcmp (option->name, name)) + break; + } + return (option && option->name)? option : NULL; +} + + +/* Find the option NAME in component COMPONENT. Returns pointer to + * the option descriptor or NULL if not found. */ +static gc_option_t * +find_option (gc_component_id_t component, const char *name) +{ + gc_option_t *option = gc_component[component].options; + + if (option) + { + for (; option->name; option++) + { + if (!option->is_header + && !strcmp (option->name, name)) + return option; + } + } + return NULL; +} + + + +struct read_line_wrapper_parm_s +{ + const char *pgmname; + estream_t fp; + char *line; + size_t line_len; + const char **extra_lines; + int extra_lines_idx; + char *extra_line_buffer; +}; + + +/* Helper for retrieve_options_from_program. */ +static ssize_t +read_line_wrapper (struct read_line_wrapper_parm_s *parm) +{ + ssize_t length; + const char *extra_line; + + if (parm->fp) + { + length = es_read_line (parm->fp, &parm->line, &parm->line_len, NULL); + if (length > 0) + return length; + if (length < 0 || es_ferror (parm->fp)) + gc_error (1, errno, "error reading from %s", parm->pgmname); + if (es_fclose (parm->fp)) + gc_error (1, errno, "error closing %s", parm->pgmname); + /* EOF seen. */ + parm->fp = NULL; + } + /* Return the made up lines. */ + if (!parm->extra_lines + || !(extra_line = parm->extra_lines[parm->extra_lines_idx])) + return -1; /* This is really the EOF. */ + parm->extra_lines_idx++; + xfree (parm->extra_line_buffer); + parm->extra_line_buffer = xstrdup (extra_line); + return strlen (parm->extra_line_buffer); +} + +/* Retrieve the options for the component COMPONENT. With + * ONLY_INSTALLED set components which are not installed are silently + * ignored. */ +static void +retrieve_options_from_program (gc_component_id_t component, int only_installed) +{ + gpg_error_t err; + const char *pgmname; + const char *argv[4]; + estream_t outfp; + int exitcode; + pid_t pid; + known_option_t *known_option; + gc_option_t *option; + char *line = NULL; + size_t line_len; + ssize_t length; + const char *config_name; + gnupg_argparse_t pargs; + int dummy_argc; + char *twopartconfig_name = NULL; + gnupg_opt_t *opt_table = NULL; /* A malloced option table. */ + size_t opt_table_used = 0; /* Its current length. */ + size_t opt_table_size = 0; /* Its allocated length. */ + gc_option_t *opt_info = NULL; /* A malloced options table. */ + size_t opt_info_used = 0; /* Its current length. */ + size_t opt_info_size = 0; /* Its allocated length. */ + int i; + struct read_line_wrapper_parm_s read_line_parm; + int pseudo_count; + + pgmname = (gc_component[component].module_name + ? gnupg_module_name (gc_component[component].module_name) + : gc_component[component].program ); + + if (only_installed && gnupg_access (pgmname, X_OK)) + { + return; /* The component is not installed. */ + } + + + /* First we need to read the option table from the program. */ + argv[0] = "--dump-option-table"; + argv[1] = NULL; + err = gnupg_spawn_process (pgmname, argv, NULL, NULL, 0, + NULL, &outfp, NULL, &pid); + if (err) + { + gc_error (1, 0, "could not gather option table from '%s': %s", + pgmname, gpg_strerror (err)); + } + + read_line_parm.pgmname = pgmname; + read_line_parm.fp = outfp; + read_line_parm.line = line; + read_line_parm.line_len = line_len = 0; + read_line_parm.extra_line_buffer = NULL; + read_line_parm.extra_lines = gc_component[component].known_pseudo_options; + read_line_parm.extra_lines_idx = 0; + pseudo_count = 0; + while ((length = read_line_wrapper (&read_line_parm)) > 0) + { + char *fields[4]; + char *optname, *optdesc; + unsigned int optflags; + int short_opt; + gc_arg_type_t arg_type; + int pseudo = 0; + + if (read_line_parm.extra_line_buffer) + { + line = read_line_parm.extra_line_buffer; + pseudo = 1; + pseudo_count++; + } + else + line = read_line_parm.line; + + /* Strip newline and carriage return, if present. */ + while (length > 0 + && (line[length - 1] == '\n' || line[length - 1] == '\r')) + line[--length] = '\0'; + + if (split_fields_colon (line, fields, DIM (fields)) < 4) + { + gc_error (0,0, "WARNING: invalid line in option table of '%s'\n", + pgmname); + continue; + } + + optname = fields[0]; + short_opt = atoi (fields[1]); + if (short_opt < 1 && !pseudo) + { + gc_error (0,0, "WARNING: bad short option in option table of '%s'\n", + pgmname); + continue; + } + + optflags = strtoul (fields[2], NULL, 10); + if ((optflags & ARGPARSE_OPT_HEADER)) + known_option = NULL; /* We want all header-only options. */ + else if ((known_option = is_known_option (component, optname))) + ; /* Yes we want this one. */ + else + continue; /* No need to store this option description. */ + + /* The +1 here is to make sure that we will have a zero item at + * the end of the table. */ + if (opt_table_used + 1 >= opt_table_size) + { + /* Note that this also does the initial allocation. */ + opt_table_size += 128; + opt_table = xreallocarray (opt_table, + opt_table_used, + opt_table_size, + sizeof *opt_table); + } + /* The +1 here is to make sure that we will have a zero item at + * the end of the table. */ + if (opt_info_used + 1 >= opt_info_size) + { + /* Note that this also does the initial allocation. */ + opt_info_size += 128; + opt_info = xreallocarray (opt_info, + opt_info_used, + opt_info_size, + sizeof *opt_info); + } + /* The +1 here accounts for the two items we are going to add to + * the global string table. */ + if (string_array_used + 1 >= string_array_size) + { + string_array_size += 256; + string_array = xreallocarray (string_array, + string_array_used, + string_array_size, + sizeof *string_array); + } + string_array[string_array_used++] = optname = xstrdup (fields[0]); + string_array[string_array_used++] = optdesc = xstrdup (fields[3]); + + /* Create an option table which can then be supplied to + * gpgrt_parser. Unfortunately there is no private pointer in + * the public option table struct so that we can't add extra + * data we need here. Thus we need to build up another table + * for such info and for ease of use we also copy the tehre the + * data from the option table. It is not possible to use the + * known_option_s for this because that one does not carry + * header lines and it might also be problematic to use such + * static tables for caching options and default values. */ + if (!pseudo) + { + opt_table[opt_table_used].long_opt = optname; + opt_table[opt_table_used].short_opt = short_opt; + opt_table[opt_table_used].description = optdesc; + opt_table[opt_table_used].flags = optflags; + opt_table_used++; + } + + /* Note that as per argparser specs the opt_table uses "@" to + * specifify an empty description. In the DESC script of + * options (opt_info_t) we want to have a real empty string. */ + opt_info[opt_info_used].name = optname; + if (*optdesc == '@' && !optdesc[1]) + opt_info[opt_info_used].desc = optdesc+1; + else + opt_info[opt_info_used].desc = optdesc; + + /* Unfortunately we need to remap the types. */ + switch ((optflags & ARGPARSE_TYPE_MASK)) + { + case ARGPARSE_TYPE_INT: arg_type = GC_ARG_TYPE_INT32; break; + case ARGPARSE_TYPE_LONG: arg_type = GC_ARG_TYPE_INT32; break; + case ARGPARSE_TYPE_ULONG: arg_type = GC_ARG_TYPE_UINT32; break; + case ARGPARSE_TYPE_STRING: arg_type = GC_ARG_TYPE_STRING; break; + default: arg_type = GC_ARG_TYPE_NONE; break; + } + opt_info[opt_info_used].arg_type = arg_type; + if (pseudo) /* Pseudo options are always no_change. */ + opt_info[opt_info_used].no_change = 1; + + if ((optflags & ARGPARSE_OPT_HEADER)) + opt_info[opt_info_used].is_header = 1; + if (known_option) + { + if ((known_option->flags & GC_OPT_FLAG_LIST)) + opt_info[opt_info_used].is_list = 1; + /* FIXME: The next can also be taken from opt_table->flags. + * We need to check the code whether both specifications match. */ + if ((known_option->flags & GC_OPT_FLAG_ARG_OPT)) + opt_info[opt_info_used].opt_arg = 1; + /* Same here. */ + if ((known_option->flags & GC_OPT_FLAG_RUNTIME)) + opt_info[opt_info_used].runtime = 1; + + opt_info[opt_info_used].level = known_option->level; + /* Override the received argtype by a complex type. */ + if (known_option->arg_type) + opt_info[opt_info_used].arg_type = known_option->arg_type; + } + opt_info_used++; + } + xfree (read_line_parm.extra_line_buffer); + line = read_line_parm.line; + line_len = read_line_parm.line_len; + log_assert (opt_table_used + pseudo_count == opt_info_used); + + 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); + + /* Make the gpgrt option table and the internal option table available. */ + gc_component[component].opt_table = opt_table; + gc_component[component].options = opt_info; + + + /* Now read the default options. */ + argv[0] = "--gpgconf-list"; + argv[1] = NULL; + 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) + { + 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); + if (option) + { + if (option->gpgconf_list) + gc_error (1, errno, + "option %s returned twice from \"%s --gpgconf-list\"", + line, pgmname); + option->gpgconf_list = 1; + + if ((flags & GC_OPT_FLAG_DEFAULT)) + option->has_default = 1; + if ((flags & GC_OPT_FLAG_DEF_DESC)) + option->def_in_desc = 1; + if ((flags & GC_OPT_FLAG_NO_ARG_DESC)) + option->no_arg_desc = 1; + if ((flags & GC_OPT_FLAG_NO_CHANGE)) + option->no_change = 1; + + 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_name = gc_component[component].option_config_filename; + if (!config_name) + gc_error (1, 0, "name of config file for %s is not known\n", pgmname); + + if (!gnupg_default_homedir_p ()) + { + /* This is not the default homedir. We need to take an absolute + * config name for the user config file; gpgrt_argparser + * fortunately supports this. */ + char *tmp = make_filename (gnupg_homedir (), config_name, NULL); + twopartconfig_name = xstrconcat (config_name, PATHSEP_S, tmp, NULL); + xfree (tmp); + config_name = twopartconfig_name; + } + + memset (&pargs, 0, sizeof pargs); + dummy_argc = 0; + pargs.argc = &dummy_argc; + pargs.flags = (ARGPARSE_FLAG_KEEP + | ARGPARSE_FLAG_SYS + | ARGPARSE_FLAG_USER + | ARGPARSE_FLAG_WITHATTR); + if (opt.verbose) + pargs.flags |= ARGPARSE_FLAG_VERBOSE; + + while (gnupg_argparser (&pargs, opt_table, config_name)) + { + char *opt_value; + + if (pargs.r_opt == ARGPARSE_CONFFILE) + { + /* log_debug ("current conffile='%s'\n", */ + /* pargs.r_type? pargs.r.ret_str: "[cmdline]"); */ + continue; + } + if ((pargs.r_type & ARGPARSE_OPT_IGNORE)) + continue; + + /* We only have the short option. Search in the option table + * for the long option name. */ + for (i=0; opt_table[i].short_opt; i++) + if (opt_table[i].short_opt == pargs.r_opt) + break; + if (!opt_table[i].short_opt || !opt_table[i].long_opt) + continue; /* No or only a short option - ignore. */ + + /* Look up the option from the config file in our list of + * supported options. */ + option= find_option (component, opt_table[i].long_opt); + if (!option) + continue; /* We don't want to handle this option. */ + + /* Set the force and ignore attributes. The idea is that there + * is no way to clear them again, thus we set them when first + * encountered. */ + if ((pargs.r_type & ARGPARSE_ATTR_FORCE)) + option->attr_force = 1; + if ((pargs.r_type & ARGPARSE_ATTR_IGNORE)) + option->attr_ignore = 1; + + /* If an option has been ignored, there is no need to return + * that option with gpgconf --list-options. */ + if (option->attr_ignore) + continue; + + switch ((pargs.r_type & ARGPARSE_TYPE_MASK)) + { + case ARGPARSE_TYPE_INT: + opt_value = xasprintf ("%d", pargs.r.ret_int); + break; + case ARGPARSE_TYPE_LONG: + opt_value = xasprintf ("%ld", pargs.r.ret_long); + break; + case ARGPARSE_TYPE_ULONG: + opt_value = xasprintf ("%lu", pargs.r.ret_ulong); + break; + case ARGPARSE_TYPE_STRING: + if (!pargs.r.ret_str) + opt_value = xstrdup ("\"(none)"); /* We should not see this. */ + else + opt_value = xasprintf ("\"%s", gc_percent_escape (pargs.r.ret_str)); + break; + default: /* ARGPARSE_TYPE_NONE or any unknown type. */ + opt_value = xstrdup ("1"); /* Make sure we have some value. */ + break; + } + + /* Now enter the value read from the config file into the table. */ + if (!option->is_list) + { + xfree (option->value); + option->value = opt_value; + } + else if (!option->value) /* LIST but first item. */ + option->value = opt_value; + else + { + char *old = option->value; + option->value = xstrconcat (old, ",", opt_value, NULL); + xfree (old); + xfree (opt_value); + } + } + + xfree (line); + xfree (twopartconfig_name); +} + + +/* Retrieve the currently active options and their defaults 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; + + if (component == -1) + { + process_all = 1; + component = 0; + } + + do + { + if (component == GC_COMPONENT_PINENTRY) + continue; /* Skip this dummy component. */ + + if (gc_component[component].program) + retrieve_options_from_program (component, process_all); + } + 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_component_id_t component, + gc_option_t *option, unsigned long flags, + char *new_value, unsigned long *new_value_nr, + int verbatim) +{ + char *arg; + + (void)component; + + 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->is_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->opt_arg) + gc_error (1, 0, "argument required for option %s", option->name); + + if (*arg == ',' && !verbatim && !option->is_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 + * 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_id_t component, + 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. */ + if (!gc_component[component].option_config_filename) + gc_error (1, 0, "name of config file for %s is not known\n", + gc_component[component].name); + + dest_filename = make_absfilename + (gnupg_homedir (), gc_component[component].option_config_filename, NULL); + + 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. + * FIXME: gpgrt has an x flag for quite some time now - use that. */ + fd = gnupg_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 (component == GC_COMPONENT_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); + *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 (component == GC_COMPONENT_GPG && ! utf8strings_seen) + gpgrt_fprintf (src_file, "utf8-strings\n"); + + option = gc_component[component].options; + for ( ; option->name; option++) + { + if (!option->is_header && 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) + { + log_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; + } + + log_assert (arg == NULL || *arg == '\0' || *arg == ','); + if (arg && *arg == ',') + arg++; + } + while (arg && *arg); + } + } + + 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_component_id_t component, + gc_option_t *option, int *r_runtime, + unsigned long flags, char *new_value, int verbatim) +{ + unsigned long new_value_nr = 0; + + option_check_validity (component, option, + flags, new_value, &new_value_nr, verbatim); + + if (option->runtime) + *r_runtime = 1; + + option->new_flags = flags; + if (!(flags & GC_OPT_FLAG_DEFAULT)) + { + if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE + && option->is_list) + { + char *str; + + /* We convert the number to a list of 1's for convenient + list handling. */ + log_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 = 0; + char *src_filename = NULL; + char *dest_filename = NULL; + char *orig_filename = NULL; + 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. */ + + 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); + if (!option) + gc_error (1, 0, "unknown option %s", line); + + if (option->no_change) + { + gc_error (0, 0, "ignoring new value for option %s", + option->name); + continue; + } + + change_one_value (component, 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 there is nothing to do. */ + if (src_filename || !(option->new_flags || option->new_value)) + { + option++; + continue; + } + + if (gc_component[component].program) + { + err = change_options_program (component, + &src_filename, + &dest_filename, + &orig_filename, + verbatim); + if (! err) + { + /* External verification. */ + err = gc_component_check_options (component, out, src_filename); + if (err) + { + gc_error (0, 0, + _("External verification of component %s failed"), + gc_component[component].name); + gpg_err_set_errno (EINVAL); + } + } + + } + 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) + { + if (src_filename) + { + /* FIXME: Make a verification here. */ + + log_assert (dest_filename); + + if (orig_filename) + err = gnupg_rename_file (src_filename, dest_filename, 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, dest_filename, 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, dest_filename); + if (!err) + err = unlink (src_filename); +#endif /* !HAVE_W32_SYSTEM */ + } + if (!err) + { + xfree (src_filename); + src_filename = NULL; + } + } + } + + if (err || opt.dry_run) + { + int saved_errno = errno; + + /* An error occurred or a dry-run is requested. */ + if (src_filename) + { + /* The change was not yet committed. */ + unlink (src_filename); + if (orig_filename) + unlink (orig_filename); + } + 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) + gnupg_rename_file (orig_filename, dest_filename, NULL); + else + unlink (dest_filename); + } + 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) + do_runtime_change (component, 0); + + + /* Move the per-process backup file into its place. */ + if (orig_filename) + { + char *backup_filename; + + log_assert (dest_filename); + backup_filename = xasprintf ("%s.%s.bak", + dest_filename, GPGCONF_NAME); + gnupg_rename_file (orig_filename, backup_filename, NULL); + xfree (backup_filename); + } + + leave: + if (block) + gnupg_unblock_all_signals (); + xfree (line); + xfree (src_filename); + xfree (dest_filename); + xfree (orig_filename); +} + + +/* 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_COMPONENT_NR] = { 0 }; + int component_id; + char *fname; + + if (fname_arg) + fname = xstrdup (fname_arg); + else + fname = make_filename (gnupg_sysconfdir (), GPGCONF_NAME EXTSEP_S "conf", + NULL); + + 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, *compname, *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; + compname = p; + } + else if (!in_rule) + { + gc_error (0, 0, "continuation but no rule at '%s', line %d", + fname, lineno); + result = -1; + continue; + } + else + { + compname = key; + key = NULL; + } + + in_rule = 1; + + /* Parse the component. */ + while (*compname == ' ' || *compname == '\t') + compname++; + for (p=compname; *p && !strchr (" \t\r\n", *p); p++) + ; + if (p == compname) + { + 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 (compname); + 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) + { + /* We need to make sure that we got the option list for the + * component. */ + if (!gc_component[component_id].options) + gc_component_retrieve_options (component_id); + option_info = find_option (component_id, option); + if (!option_info) + { + gc_error (0, 0, "unknown option '%s' at '%s', line %d", + option, 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->no_change = 1; + else if (!strcmp (flags, "change")) + option_info->no_change = 0; + + 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 (component_id, 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 (component_id = 0; component_id < GC_COMPONENT_NR; component_id++) + if (runtime[component_id] + && gc_component[component_id].runtime_change) + (*gc_component[component_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_COMPONENT_NR] = { 0 }; + int component_id = -1; + int skip_section = 0; + int error_count = 0; + int newflags; + + if (!fname) + fname = "-"; + + + 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 (!gnupg_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); + 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 (component_id, 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 (component_id = 0; component_id < GC_COMPONENT_NR; component_id++) + if (runtime[component_id] + && gc_component[component_id].runtime_change) + (*gc_component[component_id].runtime_change) (0); + } + } + + xfree (fname_buffer); + return err; +} diff --git a/tools/gpgconf-w32info.rc b/tools/gpgconf-w32info.rc new file mode 100644 index 0000000..cd8df29 --- /dev/null +++ b/tools/gpgconf-w32info.rc @@ -0,0 +1,52 @@ +/* gpgconf-w32info.rc -*- c -*- + * Copyright (C) 2020 g10 Code GmbH + * + * This file is free software; as a special exception the author gives + * unlimited permission to copy and/or distribute it, with or without + * modifications, as long as this notice is preserved. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "afxres.h" +#include "../common/w32info-rc.h" + +1 ICON "../common/gnupg.ico" + +1 VERSIONINFO + FILEVERSION W32INFO_VI_FILEVERSION + PRODUCTVERSION W32INFO_VI_PRODUCTVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ + FILETYPE 0x1L /* VFT_APP (0x1) */ + FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ + BEGIN + VALUE "FileDescription", L"GnuPG\x2019s config tool\0" + VALUE "InternalName", "gpgconf\0" + VALUE "OriginalFilename", "gpgconf.exe\0" + VALUE "ProductName", W32INFO_PRODUCTNAME + VALUE "ProductVersion", W32INFO_PRODUCTVERSION + VALUE "CompanyName", W32INFO_COMPANYNAME + VALUE "FileVersion", W32INFO_FILEVERSION + VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT + VALUE "Comments", W32INFO_COMMENTS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END + END + +1 RT_MANIFEST "gpgconf.w32-manifest" diff --git a/tools/gpgconf.c b/tools/gpgconf.c new file mode 100644 index 0000000..1b3f2be --- /dev/null +++ b/tools/gpgconf.c @@ -0,0 +1,1605 @@ +/* gpgconf.c - Configuration utility for GnuPG + * Copyright (C) 2003, 2007, 2009, 2011 Free Software Foundation, Inc. + * Copyright (C) 2016, 2020 g10 Code GmbH. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define INCLUDED_BY_MAIN_MODULE 1 +#include "gpgconf.h" +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "../common/init.h" +#include "../common/status.h" +#include "../common/exechelp.h" + + +/* Constants to identify the commands and options. */ +enum cmd_and_opt_values + { + aNull = 0, + oDryRun = 'n', + oOutput = 'o', + oQuiet = 'q', + oVerbose = 'v', + oRuntime = 'r', + oComponent = 'c', + oNull = '0', + aListDirs = 'L', + aKill = 'K', + aReload = 'R', + aShowVersions = 'V', + aShowConfigs = 'X', + + oNoVerbose = 500, + oHomedir, + oBuilddir, + oStatusFD, + oShowSocket, + + aListComponents, + aCheckPrograms, + aListOptions, + aChangeOptions, + aCheckOptions, + aApplyDefaults, + aListConfig, + aCheckConfig, + aQuerySWDB, + aLaunch, + aCreateSocketDir, + aRemoveSocketDir, + aApplyProfile + }; + + +/* The list of commands and options. */ +static ARGPARSE_OPTS opts[] = + { + { 300, NULL, 0, N_("@Commands:\n ") }, + + { aListComponents, "list-components", 256, N_("list all components") }, + { aCheckPrograms, "check-programs", 256, N_("check all programs") }, + { aListOptions, "list-options", 256, N_("|COMPONENT|list options") }, + { aChangeOptions, "change-options", 256, N_("|COMPONENT|change options") }, + { aCheckOptions, "check-options", 256, N_("|COMPONENT|check options") }, + { aApplyDefaults, "apply-defaults", 256, + N_("apply global default values") }, + { aApplyProfile, "apply-profile", 256, + N_("|FILE|update configuration files using FILE") }, + { aListDirs, "list-dirs", 256, + N_("get the configuration directories for @GPGCONF@") }, + { aListConfig, "list-config", 256, + N_("list global configuration file") }, + { aCheckConfig, "check-config", 256, + N_("check global configuration file") }, + { aQuerySWDB, "query-swdb", 256, + N_("query the software version database") }, + { aReload, "reload", 256, N_("reload all or a given component")}, + { aLaunch, "launch", 256, N_("launch a given component")}, + { aKill, "kill", 256, N_("kill a given component")}, + { aCreateSocketDir, "create-socketdir", 256, "@"}, + { aRemoveSocketDir, "remove-socketdir", 256, "@"}, + ARGPARSE_c (aShowVersions, "show-versions", ""), + ARGPARSE_c (aShowConfigs, "show-configs", ""), + + { 301, NULL, 0, N_("@\nOptions:\n ") }, + + { oOutput, "output", 2, N_("use as output file") }, + { oVerbose, "verbose", 0, N_("verbose") }, + { oQuiet, "quiet", 0, N_("quiet") }, + { oDryRun, "dry-run", 0, N_("do not make any changes") }, + { oRuntime, "runtime", 0, N_("activate changes at runtime, if possible") }, + ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), + /* hidden options */ + { oHomedir, "homedir", 2, "@" }, + { oBuilddir, "build-prefix", 2, "@" }, + { oNull, "null", 0, "@" }, + { oNoVerbose, "no-verbose", 0, "@"}, + ARGPARSE_s_n (oShowSocket, "show-socket", "@"), + + ARGPARSE_end(), + }; + + + +#define CUTLINE_FMT \ + "--8<---------------cut here---------------%s------------->8---\n" + + +/* The stream to output the status information. Status Output is disabled if + * this is NULL. */ +static estream_t statusfp; + +static void show_versions (estream_t fp); +static void show_configs (estream_t fp); + + + +/* Print usage information and provide strings for help. */ +static const char * +my_strusage( int level ) +{ + const char *p; + + switch (level) + { + case 9: p = "GPL-3.0-or-later"; break; + case 11: p = "@GPGCONF@ (@GNUPG@)"; + break; + case 13: p = VERSION; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; + + case 1: + case 40: p = _("Usage: @GPGCONF@ [options] (-h for help)"); + break; + case 41: + p = _("Syntax: @GPGCONF@ [options]\n" + "Manage configuration options for tools of the @GNUPG@ system\n"); + break; + + default: p = NULL; break; + } + return p; +} + + +/* Return the fp for the output. This is usually stdout unless + --output has been used. In the latter case this function opens + that file. */ +static estream_t +get_outfp (estream_t *fp) +{ + if (!*fp) + { + if (opt.outfile) + { + *fp = es_fopen (opt.outfile, "w"); + if (!*fp) + gc_error (1, errno, "can not open '%s'", opt.outfile); + } + else + *fp = es_stdout; + } + return *fp; +} + + +/* Set the status FD. */ +static void +set_status_fd (int fd) +{ + static int last_fd = -1; + + if (fd != -1 && last_fd == fd) + return; + + if (statusfp && statusfp != es_stdout && statusfp != es_stderr) + es_fclose (statusfp); + statusfp = NULL; + if (fd == -1) + return; + + if (fd == 1) + statusfp = es_stdout; + else if (fd == 2) + statusfp = es_stderr; + else + statusfp = es_fdopen (fd, "w"); + if (!statusfp) + { + log_fatal ("can't open fd %d for status output: %s\n", + fd, gpg_strerror (gpg_error_from_syserror ())); + } + last_fd = fd; +} + + +/* Write a status line with code NO followed by the output of the + * printf style FORMAT. The caller needs to make sure that LFs and + * CRs are not printed. */ +void +gpgconf_write_status (int no, const char *format, ...) +{ + va_list arg_ptr; + + if (!statusfp) + return; /* Not enabled. */ + + es_fputs ("[GNUPG:] ", statusfp); + es_fputs (get_status_string (no), statusfp); + if (format) + { + es_putc (' ', statusfp); + va_start (arg_ptr, format); + es_vfprintf (statusfp, format, arg_ptr); + va_end (arg_ptr); + } + es_putc ('\n', statusfp); +} + + +static void +list_dirs (estream_t fp, char **names, int special) +{ + static struct { + const char *name; + const char *(*fnc)(void); + const char *extra; + } list[] = { + { "sysconfdir", gnupg_sysconfdir, NULL }, + { "bindir", gnupg_bindir, NULL }, + { "libexecdir", gnupg_libexecdir, NULL }, + { "libdir", gnupg_libdir, NULL }, + { "datadir", gnupg_datadir, NULL }, + { "localedir", gnupg_localedir, NULL }, + { "socketdir", gnupg_socketdir, NULL }, + { "dirmngr-socket", dirmngr_socket_name, NULL,}, + { "agent-ssh-socket", gnupg_socketdir, GPG_AGENT_SSH_SOCK_NAME }, + { "agent-extra-socket", gnupg_socketdir, GPG_AGENT_EXTRA_SOCK_NAME }, + { "agent-browser-socket",gnupg_socketdir, GPG_AGENT_BROWSER_SOCK_NAME }, + { "agent-socket", gnupg_socketdir, GPG_AGENT_SOCK_NAME }, + { "homedir", gnupg_homedir, NULL } + }; + int idx, j; + char *tmp; + const char *s; + + + for (idx = 0; idx < DIM (list); idx++) + { + s = list[idx].fnc (); + if (list[idx].extra) + { + tmp = make_filename (s, list[idx].extra, NULL); + s = tmp; + } + else + tmp = NULL; + if (!names) + es_fprintf (fp, "%s:%s\n", list[idx].name, gc_percent_escape (s)); + else + { + for (j=0; names[j]; j++) + if (!strcmp (names[j], list[idx].name)) + { + es_fputs (s, fp); + es_putc (opt.null? '\0':'\n', fp); + } + } + + xfree (tmp); + } + + +#ifdef HAVE_W32_SYSTEM + tmp = read_w32_registry_string (NULL, + GNUPG_REGISTRY_DIR, + "HomeDir"); + if (tmp) + { + int hkcu = 0; + int hklm = 0; + + xfree (tmp); + if ((tmp = read_w32_registry_string ("HKEY_CURRENT_USER", + GNUPG_REGISTRY_DIR, + "HomeDir"))) + { + xfree (tmp); + hkcu = 1; + } + if ((tmp = read_w32_registry_string ("HKEY_LOCAL_MACHINE", + GNUPG_REGISTRY_DIR, + "HomeDir"))) + { + xfree (tmp); + hklm = 1; + } + + es_fflush (fp); + if (special) + es_fprintf (fp, "\n" + "### Note: homedir taken from registry key %s%s\\%s:%s\n" + "\n", + hkcu?" HKCU":"", hklm?" HKLM":"", + GNUPG_REGISTRY_DIR, "HomeDir"); + else + log_info ("Warning: homedir taken from registry key (%s:%s) in%s%s\n", + GNUPG_REGISTRY_DIR, "HomeDir", + hkcu?" HKCU":"", + hklm?" HKLM":""); + } + else if ((tmp = read_w32_registry_string (NULL, + GNUPG_REGISTRY_DIR, + NULL))) + { + xfree (tmp); + es_fflush (fp); + if (special) + es_fprintf (fp, "\n" + "### Note: registry key %s without value in HKCU or HKLM\n" + "\n", GNUPG_REGISTRY_DIR); + else + log_info ("Warning: registry key (%s) without value in HKCU or HKLM\n", + GNUPG_REGISTRY_DIR); + } + +#else /*!HAVE_W32_SYSTEM*/ + (void)special; +#endif /*!HAVE_W32_SYSTEM*/ +} + + + +/* Check whether NAME is valid argument for query_swdb(). Valid names + * start with a letter and contain only alphanumeric characters or an + * underscore. */ +static int +valid_swdb_name_p (const char *name) +{ + if (!name || !*name || !alphap (name)) + return 0; + + for (name++; *name; name++) + if (!alnump (name) && *name != '_') + return 0; + + return 1; +} + + +/* Query the SWDB file. If necessary and possible this functions asks + * the dirmngr to load an updated version of that file. The caller + * needs to provide the NAME to query (e.g. "gnupg", "libgcrypt") and + * optional the currently installed version in CURRENT_VERSION. The + * output written to OUT is a colon delimited line with these fields: + * + * name :: The name of the package + * curvers:: The installed version if given. + * status :: This value tells the status of the software package + * '-' :: No information available + * (error or CURRENT_VERSION not given) + * '?' :: Unknown NAME + * 'u' :: Update available + * 'c' :: The version is Current + * 'n' :: The current version is already Newer than the + * available one. + * urgency :: If the value is greater than zero an urgent update is required. + * error :: 0 on success or an gpg_err_code_t + * Common codes seen: + * GPG_ERR_TOO_OLD :: The SWDB file is to old to be used. + * GPG_ERR_ENOENT :: The SWDB file is not available. + * GPG_ERR_BAD_SIGNATURE :: Currupted SWDB file. + * filedate:: Date of the swdb file (yyyymmddThhmmss) + * verified:: Date we checked the validity of the file (yyyyymmddThhmmss) + * version :: The version string from the swdb. + * reldate :: Release date of that version (yyyymmddThhmmss) + * size :: Size of the package in bytes. + * hash :: SHA-2 hash of the package. + * + */ +static void +query_swdb (estream_t out, const char *name, const char *current_version) +{ + gpg_error_t err; + const char *search_name; + char *fname = NULL; + estream_t fp = NULL; + char *line = NULL; + char *self_version = NULL; + size_t length_of_line = 0; + size_t maxlen; + ssize_t len; + char *fields[2]; + char *p; + gnupg_isotime_t filedate = {0}; + gnupg_isotime_t verified = {0}; + char *value_ver = NULL; + gnupg_isotime_t value_date = {0}; + char *value_size = NULL; + char *value_sha2 = NULL; + unsigned long value_size_ul = 0; + int status, i; + + + if (!valid_swdb_name_p (name)) + { + log_error ("error in package name '%s': %s\n", + name, gpg_strerror (GPG_ERR_INV_NAME)); + goto leave; + } + if (!strcmp (name, "gnupg")) + search_name = GNUPG_SWDB_TAG; + else if (!strcmp (name, "gnupg1")) + search_name = "gnupg1"; + else + search_name = name; + + if (!current_version && !strcmp (name, "gnupg")) + { + /* Use our own version but string a possible beta string. */ + self_version = xstrdup (PACKAGE_VERSION); + p = strchr (self_version, '-'); + if (p) + *p = 0; + current_version = self_version; + } + + if (current_version && (strchr (current_version, ':') + || compare_version_strings (current_version, NULL))) + { + log_error ("error in version string '%s': %s\n", + current_version, gpg_strerror (GPG_ERR_INV_ARG)); + goto leave; + } + + fname = make_filename (gnupg_homedir (), "swdb.lst", NULL); + fp = es_fopen (fname, "r"); + if (!fp) + { + err = gpg_error_from_syserror (); + es_fprintf (out, "%s:%s:-::%u:::::::\n", + name, + current_version? current_version : "", + gpg_err_code (err)); + if (gpg_err_code (err) != GPG_ERR_ENOENT) + log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err)); + goto leave; + } + + /* Note that the parser uses the first occurrence of a matching + * values and ignores possible duplicated values. */ + + maxlen = 2048; /* Set limit. */ + while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0) + { + if (!maxlen) + { + err = gpg_error (GPG_ERR_LINE_TOO_LONG); + log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); + goto leave; + } + /* Strip newline and carriage return, if present. */ + while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) + line[--len] = '\0'; + + if (split_fields (line, fields, DIM (fields)) < DIM(fields)) + continue; /* Skip empty lines and names w/o a value. */ + if (*fields[0] == '#') + continue; /* Skip comments. */ + + /* Record the meta data. */ + if (!*filedate && !strcmp (fields[0], ".filedate")) + { + string2isotime (filedate, fields[1]); + continue; + } + if (!*verified && !strcmp (fields[0], ".verified")) + { + string2isotime (verified, fields[1]); + continue; + } + + /* Tokenize the name. */ + p = strrchr (fields[0], '_'); + if (!p) + continue; /* Name w/o an underscore. */ + *p++ = 0; + + /* Wait for the requested name. */ + if (!strcmp (fields[0], search_name)) + { + if (!strcmp (p, "ver") && !value_ver) + value_ver = xstrdup (fields[1]); + else if (!strcmp (p, "date") && !*value_date) + string2isotime (value_date, fields[1]); + else if (!strcmp (p, "size") && !value_size) + value_size = xstrdup (fields[1]); + else if (!strcmp (p, "sha2") && !value_sha2) + value_sha2 = xstrdup (fields[1]); + } + } + if (len < 0 || es_ferror (fp)) + { + err = gpg_error_from_syserror (); + log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); + goto leave; + } + + if (!*filedate || !*verified) + { + err = gpg_error (GPG_ERR_INV_TIME); + es_fprintf (out, "%s:%s:-::%u:::::::\n", + name, + current_version? current_version : "", + gpg_err_code (err)); + goto leave; + } + + if (!value_ver) + { + es_fprintf (out, "%s:%s:?:::::::::\n", + name, + current_version? current_version : ""); + goto leave; + } + + if (value_size) + { + gpg_err_set_errno (0); + value_size_ul = strtoul (value_size, &p, 10); + if (errno) + value_size_ul = 0; + else if (*p == 'k') + value_size_ul *= 1024; + } + + err = 0; + status = '-'; + if (compare_version_strings (value_ver, NULL)) + err = gpg_error (GPG_ERR_INV_VALUE); + else if (!current_version) + ; + else if (!(i = compare_version_strings (value_ver, current_version))) + status = 'c'; + else if (i > 0) + status = 'u'; + else + status = 'n'; + + es_fprintf (out, "%s:%s:%c::%d:%s:%s:%s:%s:%lu:%s:\n", + name, + current_version? current_version : "", + status, + err, + filedate, + verified, + value_ver, + value_date, + value_size_ul, + value_sha2? value_sha2 : ""); + + leave: + xfree (value_ver); + xfree (value_size); + xfree (value_sha2); + xfree (line); + es_fclose (fp); + xfree (fname); + xfree (self_version); +} + + +/* gpgconf main. */ +int +main (int argc, char **argv) +{ + gpg_error_t err; + ARGPARSE_ARGS pargs; + const char *fname; + int no_more_options = 0; + enum cmd_and_opt_values cmd = 0; + estream_t outfp = NULL; + int show_socket = 0; + + early_system_init (); + gnupg_reopen_std (GPGCONF_NAME); + set_strusage (my_strusage); + log_set_prefix (GPGCONF_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY); + + /* Make sure that our subsystems are ready. */ + i18n_init(); + init_common_subsystems (&argc, &argv); + gc_components_init (); + + /* Parse the command line. */ + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags = ARGPARSE_FLAG_KEEP; + while (!no_more_options && gnupg_argparse (NULL, &pargs, opts)) + { + switch (pargs.r_opt) + { + case oOutput: opt.outfile = pargs.r.ret_str; break; + case oQuiet: opt.quiet = 1; break; + case oDryRun: opt.dry_run = 1; break; + case oRuntime: opt.runtime = 1; break; + case oVerbose: opt.verbose++; break; + case oNoVerbose: opt.verbose = 0; break; + case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; + case oBuilddir: gnupg_set_builddir (pargs.r.ret_str); break; + case oNull: opt.null = 1; break; + case oStatusFD: + set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); + break; + case oShowSocket: show_socket = 1; break; + + case aListDirs: + case aListComponents: + case aCheckPrograms: + case aListOptions: + case aChangeOptions: + case aCheckOptions: + case aApplyDefaults: + case aApplyProfile: + case aListConfig: + case aCheckConfig: + case aQuerySWDB: + case aReload: + case aLaunch: + case aKill: + case aCreateSocketDir: + case aRemoveSocketDir: + case aShowVersions: + case aShowConfigs: + cmd = pargs.r_opt; + break; + + default: pargs.err = ARGPARSE_PRINT_ERROR; break; + } + } + gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ + + if (log_get_errorcount (0)) + gpgconf_failure (GPG_ERR_USER_2); + + /* Print a warning if an argument looks like an option. */ + if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) + { + int i; + + for (i=0; i < argc; i++) + if (argv[i][0] == '-' && argv[i][1] == '-') + log_info (_("Note: '%s' is not considered an option\n"), argv[i]); + } + + fname = argc ? *argv : NULL; + + /* Set the configuraton directories for use by gpgrt_argparser. We + * don't have a configuration file for this program but we have code + * which reads the component's config files. */ + gnupg_set_confdir (GNUPG_CONFDIR_SYS, gnupg_sysconfdir ()); + gnupg_set_confdir (GNUPG_CONFDIR_USER, gnupg_homedir ()); + + switch (cmd) + { + case aListComponents: + default: + /* List all components. */ + gc_component_list_components (get_outfp (&outfp)); + break; + + case aCheckPrograms: + /* Check all programs. */ + gc_check_programs (get_outfp (&outfp)); + break; + + case aListOptions: + case aChangeOptions: + case aCheckOptions: + if (!fname) + { + es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); + es_putc ('\n', es_stderr); + es_fputs (_("Need one component argument"), es_stderr); + es_putc ('\n', es_stderr); + gpgconf_failure (GPG_ERR_USER_2); + } + else + { + int idx = gc_component_find (fname); + if (idx < 0) + { + es_fputs (_("Component not found"), es_stderr); + es_putc ('\n', es_stderr); + gpgconf_failure (0); + } + if (cmd == aCheckOptions) + gc_component_check_options (idx, get_outfp (&outfp), NULL); + else + { + gc_component_retrieve_options (idx); + if (gc_process_gpgconf_conf (NULL, 1, 0, NULL)) + gpgconf_failure (0); + if (cmd == aListOptions) + gc_component_list_options (idx, get_outfp (&outfp)); + else if (cmd == aChangeOptions) + gc_component_change_options (idx, es_stdin, + get_outfp (&outfp), 0); + } + } + break; + + case aLaunch: + case aKill: + if (!fname) + { + es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); + es_putc ('\n', es_stderr); + es_fputs (_("Need one component argument"), es_stderr); + es_putc ('\n', es_stderr); + gpgconf_failure (GPG_ERR_USER_2); + } + else if (!strcmp (fname, "all")) + { + if (cmd == aLaunch) + { + if (gc_component_launch (-1)) + gpgconf_failure (0); + } + else + { + gc_component_kill (-1); + } + } + else + { + /* Launch/Kill a given component. */ + int idx; + + idx = gc_component_find (fname); + if (idx < 0) + { + es_fputs (_("Component not found"), es_stderr); + es_putc ('\n', es_stderr); + gpgconf_failure (0); + } + else if (cmd == aLaunch) + { + err = gc_component_launch (idx); + if (show_socket) + { + char *names[2]; + + if (idx == GC_COMPONENT_GPG_AGENT) + names[0] = "agent-socket"; + else if (idx == GC_COMPONENT_DIRMNGR) + names[0] = "dirmngr-socket"; + else + names[0] = NULL; + names[1] = NULL; + get_outfp (&outfp); + list_dirs (outfp, names, 0); + } + if (err) + gpgconf_failure (0); + } + else + { + /* We don't error out if the kill failed because this + command should do nothing if the component is not + running. */ + gc_component_kill (idx); + } + } + break; + + case aReload: + if (!fname || !strcmp (fname, "all")) + { + /* Reload all. */ + gc_component_reload (-1); + } + else + { + /* Reload given component. */ + int idx; + + idx = gc_component_find (fname); + if (idx < 0) + { + es_fputs (_("Component not found"), es_stderr); + es_putc ('\n', es_stderr); + gpgconf_failure (0); + } + else + { + gc_component_reload (idx); + } + } + break; + + case aListConfig: + if (gc_process_gpgconf_conf (fname, 0, 0, get_outfp (&outfp))) + gpgconf_failure (0); + break; + + case aCheckConfig: + if (gc_process_gpgconf_conf (fname, 0, 0, NULL)) + gpgconf_failure (0); + break; + + case aApplyDefaults: + if (fname) + { + es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); + es_putc ('\n', es_stderr); + es_fputs (_("No argument allowed"), es_stderr); + es_putc ('\n', es_stderr); + gpgconf_failure (GPG_ERR_USER_2); + } + if (!opt.dry_run && gnupg_access (gnupg_homedir (), F_OK)) + gnupg_maybe_make_homedir (gnupg_homedir (), opt.quiet); + gc_component_retrieve_options (-1); + if (gc_process_gpgconf_conf (NULL, 1, 1, NULL)) + gpgconf_failure (0); + break; + + case aApplyProfile: + if (!opt.dry_run && gnupg_access (gnupg_homedir (), F_OK)) + gnupg_maybe_make_homedir (gnupg_homedir (), opt.quiet); + gc_component_retrieve_options (-1); + if (gc_apply_profile (fname)) + gpgconf_failure (0); + break; + + case aListDirs: + /* Show the system configuration directories for gpgconf. */ + get_outfp (&outfp); + list_dirs (outfp, argc? argv : NULL, 0); + break; + + case aQuerySWDB: + /* Query the software version database. */ + if (!fname || argc > 2) + { + es_fprintf (es_stderr, "usage: %s --query-swdb NAME [VERSION]\n", + GPGCONF_NAME); + gpgconf_failure (GPG_ERR_USER_2); + } + get_outfp (&outfp); + query_swdb (outfp, fname, argc > 1? argv[1] : NULL); + break; + + case aCreateSocketDir: + { + char *socketdir; + unsigned int flags; + + /* Make sure that the top /run/user/UID/gnupg dir has been + * created. */ + gnupg_socketdir (); + + /* Check the /var/run dir. */ + socketdir = _gnupg_socketdir_internal (1, &flags); + if ((flags & 64) && !opt.dry_run) + { + /* No sub dir - create it. */ + if (gnupg_mkdir (socketdir, "-rwx")) + gc_error (1, errno, "error creating '%s'", socketdir); + /* Try again. */ + xfree (socketdir); + socketdir = _gnupg_socketdir_internal (1, &flags); + } + + /* Give some info. */ + if ( (flags & ~32) || opt.verbose || opt.dry_run) + { + log_info ("socketdir is '%s'\n", socketdir); + if ((flags & 1)) log_info ("\tgeneral error\n"); + if ((flags & 2)) log_info ("\tno /run/user dir\n"); + if ((flags & 4)) log_info ("\tbad permissions\n"); + if ((flags & 8)) log_info ("\tbad permissions (subdir)\n"); + if ((flags & 16)) log_info ("\tmkdir failed\n"); + if ((flags & 32)) log_info ("\tnon-default homedir\n"); + if ((flags & 64)) log_info ("\tno such subdir\n"); + if ((flags & 128)) log_info ("\tusing homedir as fallback\n"); + } + + if ((flags & ~32) && !opt.dry_run) + gc_error (1, 0, "error creating socket directory"); + + xfree (socketdir); + } + break; + + case aRemoveSocketDir: + { + char *socketdir; + unsigned int flags; + + /* Check the /var/run dir. */ + socketdir = _gnupg_socketdir_internal (1, &flags); + if ((flags & 128)) + log_info ("ignoring request to remove non /run/user socket dir\n"); + else if (opt.dry_run) + ; + else if (gnupg_rmdir (socketdir)) + { + /* If the director is not empty we first try to delet + * socket files. */ + err = gpg_error_from_syserror (); + if (gpg_err_code (err) == GPG_ERR_ENOTEMPTY + || gpg_err_code (err) == GPG_ERR_EEXIST) + { + static const char * const names[] = { + GPG_AGENT_SOCK_NAME, + GPG_AGENT_EXTRA_SOCK_NAME, + GPG_AGENT_BROWSER_SOCK_NAME, + GPG_AGENT_SSH_SOCK_NAME, + SCDAEMON_SOCK_NAME, + DIRMNGR_SOCK_NAME + }; + int i; + char *p; + + for (i=0; i < DIM(names); i++) + { + p = strconcat (socketdir , "/", names[i], NULL); + if (p) + gnupg_remove (p); + xfree (p); + } + if (gnupg_rmdir (socketdir)) + gc_error (1, 0, "error removing '%s': %s", + socketdir, gpg_strerror (err)); + } + else if (gpg_err_code (err) == GPG_ERR_ENOENT) + gc_error (0, 0, "warning: removing '%s' failed: %s", + socketdir, gpg_strerror (err)); + else + gc_error (1, 0, "error removing '%s': %s", + socketdir, gpg_strerror (err)); + } + + xfree (socketdir); + } + break; + + case aShowVersions: + { + get_outfp (&outfp); + show_versions (outfp); + } + break; + + case aShowConfigs: + { + get_outfp (&outfp); + show_configs (outfp); + } + break; + + } + + if (outfp != es_stdout) + if (es_fclose (outfp)) + gc_error (1, errno, "error closing '%s'", opt.outfile); + + + if (log_get_errorcount (0)) + gpgconf_failure (0); + else + gpgconf_write_status (STATUS_SUCCESS, NULL); + return 0; +} + + +void +gpgconf_failure (gpg_error_t err) +{ + log_flush (); + if (!err) + err = gpg_error (GPG_ERR_GENERAL); + gpgconf_write_status + (STATUS_FAILURE, "- %u", + gpg_err_code (err) == GPG_ERR_USER_2? GPG_ERR_EINVAL : err); + exit (gpg_err_code (err) == GPG_ERR_USER_2? 2 : 1); +} + + + +/* Parse the revision part from the extended version blurb. */ +static const char * +get_revision_from_blurb (const char *blurb, int *r_len) +{ + const char *s = blurb? blurb : ""; + int n; + + for (; *s; s++) + if (*s == '\n' && s[1] == '(') + break; + if (s) + { + s += 2; + for (n=0; s[n] && s[n] != ' '; n++) + ; + } + else + { + s = "?"; + n = 1; + } + *r_len = n; + return s; +} + + +static void +show_version_gnupg (estream_t fp, const char *prefix) +{ + char *fname, *p; + size_t n; + estream_t verfp; + char line[100]; + + es_fprintf (fp, "%s%sGnuPG %s (%s)\n%s%s\n", prefix, *prefix?"":"* ", + strusage (13), BUILD_REVISION, prefix, strusage (17)); + + /* Show the GnuPG VS-Desktop version in --show-configs mode */ + if (prefix && *prefix == '#') + { + fname = make_filename (gnupg_bindir (), NULL); + n = strlen (fname); + if (n > 10 && (!ascii_strcasecmp (fname + n - 10, "/GnuPG/bin") + || !ascii_strcasecmp (fname + n - 10, "\\GnuPG\\bin"))) + { + /* Append VERSION to the ../../ direcory. Note that VERSION + * is only 7 bytes and thus fits. */ + strcpy (fname + n - 9, "VERSION"); + verfp = es_fopen (fname, "r"); + if (!verfp) + es_fprintf (fp, "%s[VERSION file not found]\n", prefix); + else if (!es_fgets (line, sizeof line, verfp)) + es_fprintf (fp, "%s[VERSION file is empty]\n", prefix); + else + { + trim_spaces (line); + for (p=line; *p; p++) + if (*p < ' ' || *p > '~' || *p == '[') + *p = '?'; + es_fprintf (fp, "%s%s\n", prefix, line); + } + es_fclose (verfp); + } + xfree (fname); + } + +#ifdef HAVE_W32_SYSTEM + { + OSVERSIONINFO osvi = { sizeof (osvi) }; + + GetVersionEx (&osvi); + es_fprintf (fp, "%sWindows %lu.%lu build %lu%s%s%s\n", + prefix, + (unsigned long)osvi.dwMajorVersion, + (unsigned long)osvi.dwMinorVersion, + (unsigned long)osvi.dwBuildNumber, + *osvi.szCSDVersion? " (":"", + osvi.szCSDVersion, + *osvi.szCSDVersion? ")":"" + ); + } +#endif /*HAVE_W32_SYSTEM*/ +} + + +static void +show_version_libgcrypt (estream_t fp) +{ + const char *s; + int n; + + s = get_revision_from_blurb (gcry_check_version ("\x01\x01"), &n); + es_fprintf (fp, "* Libgcrypt %s (%.*s)\n", + gcry_check_version (NULL), n, s); + s = gcry_get_config (0, NULL); + if (s) + es_fputs (s, fp); +} + + +static void +show_version_gpgrt (estream_t fp) +{ + const char *s; + int n; + + s = get_revision_from_blurb (gpg_error_check_version ("\x01\x01"), &n); + es_fprintf (fp, "* GpgRT %s (%.*s)\n", + gpg_error_check_version (NULL), n, s); +} + + +/* Printing version information for other libraries is problematic + * because we don't want to link gpgconf to all these libraries. The + * best solution is delegating this to dirmngr which uses libassuan, + * libksba, libnpth and ntbtls anyway. */ +static void +show_versions_via_dirmngr (estream_t fp) +{ + gpg_error_t err; + const char *pgmname; + const char *argv[2]; + estream_t outfp; + pid_t pid; + char *line = NULL; + size_t line_len = 0; + ssize_t length; + int exitcode; + + pgmname = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR); + argv[0] = "--gpgconf-versions"; + argv[1] = NULL; + err = gnupg_spawn_process (pgmname, argv, NULL, NULL, 0, + NULL, &outfp, NULL, &pid); + if (err) + { + log_error ("error spawning %s: %s", pgmname, gpg_strerror (err)); + es_fprintf (fp, "[error: can't get further info]\n"); + return; + } + + while ((length = es_read_line (outfp, &line, &line_len, NULL)) > 0) + { + /* Strip newline and carriage return, if present. */ + while (length > 0 + && (line[length - 1] == '\n' || line[length - 1] == '\r')) + line[--length] = '\0'; + es_fprintf (fp, "%s\n", line); + } + if (length < 0 || es_ferror (outfp)) + { + err = gpg_error_from_syserror (); + log_error ("error reading from %s: %s\n", pgmname, gpg_strerror (err)); + } + if (es_fclose (outfp)) + { + err = gpg_error_from_syserror (); + log_error ("error closing output stream of %s: %s\n", + pgmname, gpg_strerror (err)); + } + + err = gnupg_wait_process (pgmname, pid, 1, &exitcode); + if (err) + { + log_error ("running %s failed (exitcode=%d): %s\n", + pgmname, exitcode, gpg_strerror (err)); + es_fprintf (fp, "[error: can't get further info]\n"); + } + gnupg_release_process (pid); + xfree (line); +} + + +/* Show all kind of version information. */ +static void +show_versions (estream_t fp) +{ + show_version_gnupg (fp, ""); + es_fputc ('\n', fp); + show_version_libgcrypt (fp); + es_fputc ('\n', fp); + show_version_gpgrt (fp); + es_fputc ('\n', fp); + show_versions_via_dirmngr (fp); +} + + + +/* Copy data from file SRC to DST. Returns 0 on success or an error + * code on failure. If LISTP is not NULL, that strlist is updated + * with the variabale or registry key names detected. Flag bit 0 + * indicates a registry entry. */ +static gpg_error_t +my_copy_file (estream_t src, estream_t dst, strlist_t *listp) +{ + gpg_error_t err; + char *line = NULL; + size_t line_len = 0; + ssize_t length; + int written; + + while ((length = es_read_line (src, &line, &line_len, NULL)) > 0) + { + /* Strip newline and carriage return, if present. */ + written = gpgrt_fwrite (line, 1, length, dst); + if (written != length) + return gpg_error_from_syserror (); + trim_spaces (line); + if (*line == '[' && listp) + { + char **tokens; + char *p; + + for (p=line+1; *p; p++) + if (*p != ' ' && *p != '\t') + break; + if (*p && p[strlen (p)-1] == ']') + p[strlen (p)-1] = 0; + tokens = strtokenize (p, " \t"); + if (!tokens) + { + err = gpg_error_from_syserror (); + log_error ("strtokenize failed: %s\n", gpg_strerror (err)); + return err; + } + + /* Check whether we have a getreg or getenv statement and + * store the third token to later retrieval. */ + if (tokens[0] && tokens[1] && tokens[2] + && (!strcmp (tokens[0], "getreg") + || !strcmp (tokens[0], "getenv"))) + { + int isreg = (tokens[0][3] == 'r'); + strlist_t sl = *listp; + + for (sl = *listp; sl; sl = sl->next) + if (!strcmp (sl->d, tokens[2]) && (sl->flags & 1) == isreg) + break; + if (!sl) /* Not yet in the respective list. */ + { + sl = add_to_strlist (listp, tokens[2]); + if (isreg) + sl->flags = 1; + } + } + + xfree (tokens); + } + } + if (length < 0 || es_ferror (src)) + return gpg_error_from_syserror (); + + if (gpgrt_fflush (dst)) + return gpg_error_from_syserror (); + + return 0; +} + + +/* Helper for show_configs */ +static void +show_configs_one_file (const char *fname, int global, estream_t outfp, + strlist_t *listp) +{ + gpg_error_t err; + estream_t fp; + + fp = es_fopen (fname, "r"); + if (!fp) + { + err = gpg_error_from_syserror (); + es_fprintf (outfp, "###\n### %s config \"%s\": %s\n###\n", + global? "global":"local", fname, + (gpg_err_code (err) == GPG_ERR_ENOENT)? + "not installed" : gpg_strerror (err)); + } + else + { + es_fprintf (outfp, "###\n### %s config \"%s\"\n###\n", + global? "global":"local", fname); + es_fprintf (outfp, CUTLINE_FMT, "start"); + err = my_copy_file (fp, outfp, listp); + if (err) + log_error ("error copying file \"%s\": %s\n", + fname, gpg_strerror (err)); + es_fprintf (outfp, CUTLINE_FMT, "end--"); + es_fclose (fp); + } +} + + +#ifdef HAVE_W32_SYSTEM +/* Print registry entries relevant to the GnuPG system and related + * software. */ +static void +show_other_registry_entries (estream_t outfp) +{ + static struct { + int group; + const char *name; + } names[] = + { + { 1, "HKLM\\Software\\Gpg4win:Install Directory" }, + { 1, "HKLM\\Software\\Gpg4win:Desktop-Version" }, + { 1, "HKLM\\Software\\Gpg4win:VS-Desktop-Version" }, + { 1, "\\" GNUPG_REGISTRY_DIR ":HomeDir" }, + { 1, "\\" GNUPG_REGISTRY_DIR ":DefaultLogFile" }, + { 2, "\\Software\\Microsoft\\Office\\Outlook\\Addins\\GNU.GpgOL" + ":LoadBehavior" }, + { 2, "HKCU\\Software\\Microsoft\\Office\\16.0\\Outlook\\Options\\Mail:" + "ReadAsPlain" }, + { 2, "HKCU\\Software\\Policies\\Microsoft\\Office\\16.0\\Outlook\\" + "Options\\Mail:ReadAsPlain" }, + { 3, "logFile" }, + { 3, "enableDebug" }, + { 3, "searchSmimeServers" }, + { 3, "smimeInsecureReplyAllowed" }, + { 3, "enableSmime" }, + { 3, "preferSmime" }, + { 3, "encryptDefault" }, + { 3, "signDefault" }, + { 3, "inlinePGP" }, + { 3, "replyCrypt" }, + { 3, "autoresolve" }, + { 3, "autoretrieve" }, + { 3, "automation" }, + { 3, "autosecure" }, + { 3, "autotrust" }, + { 3, "autoencryptUntrusted" }, + { 3, "autoimport" }, + { 3, "splitBCCMails" }, + { 3, "combinedOpsEnabled" }, + { 3, "encryptSubject" }, + { 0, NULL } + }; + int idx; + int group = 0; + char *namebuf = NULL; + const char *name; + int from_hklm; + + for (idx=0; (name = names[idx].name); idx++) + { + char *value; + + if (names[idx].group == 3) + { + xfree (namebuf); + namebuf = xstrconcat ("\\Software\\GNU\\GpgOL", ":", + names[idx].name, NULL); + name = namebuf; + } + + value = read_w32_reg_string (name, &from_hklm); + if (!value) + continue; + + if (names[idx].group != group) + { + group = names[idx].group; + es_fprintf (outfp, "###\n### %s related:\n", + group == 1 ? "GnuPG Desktop" : + group == 2 ? "Outlook" : + group == 3 ? "\\Software\\GNU\\GpgOL" + : "System" ); + } + + if (group == 3) + es_fprintf (outfp, "### %s=%s%s\n", names[idx].name, value, + from_hklm? " [hklm]":""); + else + es_fprintf (outfp, "### %s\n### ->%s<-%s\n", name, value, + from_hklm? " [hklm]":""); + + xfree (value); + } + + es_fprintf (outfp, "###\n"); + xfree (namebuf); +} + + +/* Print registry entries take from a configuration file. */ +static void +show_registry_entries_from_file (estream_t outfp) +{ + gpg_error_t err; + char *fname; + estream_t fp; + char *line = NULL; + size_t length_of_line = 0; + size_t maxlen; + ssize_t len; + char *value = NULL; + int from_hklm; + int any = 0; + + fname = make_filename (gnupg_datadir (), "gpgconf.rnames", NULL); + fp = es_fopen (fname, "r"); + if (!fp) + { + err = gpg_error_from_syserror (); + if (gpg_err_code (err) != GPG_ERR_ENOENT) + log_error ("error opening '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + + maxlen = 2048; /* Set limit. */ + while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0) + { + if (!maxlen) + { + err = gpg_error (GPG_ERR_LINE_TOO_LONG); + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + trim_spaces (line); + if (*line == '#') + continue; + + xfree (value); + value = read_w32_reg_string (line, &from_hklm); + if (!value) + continue; + + if (!any) + { + any = 1; + es_fprintf (outfp, "### Taken from gpgconf.rnames:\n"); + } + + es_fprintf (outfp, "### %s\n### ->%s<-%s\n", line, value, + from_hklm? " [hklm]":""); + + } + if (len < 0 || es_ferror (fp)) + { + err = gpg_error_from_syserror (); + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + } + + leave: + if (any) + es_fprintf (outfp, "###\n"); + xfree (value); + xfree (line); + es_fclose (fp); + xfree (fname); +} +#endif /*HAVE_W32_SYSTEM*/ + + +/* Show all config files. */ +static void +show_configs (estream_t outfp) +{ + static const char *names[] = { "common.conf", "gpg-agent.conf", + "scdaemon.conf", "dirmngr.conf", + "gpg.conf", "gpgsm.conf" }; + gpg_error_t err; + int idx; + char *fname; + gnupg_dir_t dir; + gnupg_dirent_t dir_entry; + size_t n; + int any; + strlist_t list = NULL; + strlist_t sl; + const char *s; + + es_fprintf (outfp, "### Dump of all standard config files\n"); + show_version_gnupg (outfp, "### "); + es_fprintf (outfp, "### Libgcrypt %s\n", gcry_check_version (NULL)); + es_fprintf (outfp, "### GpgRT %s\n", gpg_error_check_version (NULL)); +#ifdef HAVE_W32_SYSTEM + es_fprintf (outfp, "### Codepages:"); + if (GetConsoleCP () != GetConsoleOutputCP ()) + es_fprintf (outfp, " %u/%u", GetConsoleCP (), GetConsoleOutputCP ()); + else + es_fprintf (outfp, " %u", GetConsoleCP ()); + es_fprintf (outfp, " %u", GetACP ()); + es_fprintf (outfp, " %u\n", GetOEMCP ()); +#endif + es_fprintf (outfp, "###\n\n"); + + list_dirs (outfp, NULL, 1); + es_fprintf (outfp, "\n"); + + for (idx = 0; idx < DIM (names); idx++) + { + fname = make_filename (gnupg_sysconfdir (), names[idx], NULL); + show_configs_one_file (fname, 1, outfp, &list); + xfree (fname); + fname = make_filename (gnupg_homedir (), names[idx], NULL); + show_configs_one_file (fname, 0, outfp, &list); + xfree (fname); + es_fprintf (outfp, "\n"); + } + + /* Print the encountered registry values and envvars. */ + if (list) + { + any = 0; + for (sl = list; sl; sl = sl->next) + if (!(sl->flags & 1)) + { + if (!any) + { + any = 1; + es_fprintf (outfp, + "###\n" + "### List of encountered environment variables:\n"); + } + if ((s = getenv (sl->d))) + es_fprintf (outfp, "### %-12s ->%s<-\n", sl->d, s); + else + es_fprintf (outfp, "### %-12s [not set]\n", sl->d); + } + if (any) + es_fprintf (outfp, "###\n"); + } + +#ifdef HAVE_W32_SYSTEM + es_fprintf (outfp, "###\n### Registry entries:\n"); + any = 0; + if (list) + { + for (sl = list; sl; sl = sl->next) + if ((sl->flags & 1)) + { + char *p; + int from_hklm; + + if (!any) + { + any = 1; + es_fprintf (outfp, "###\n### Encountered in config files:\n"); + } + if ((p = read_w32_reg_string (sl->d, &from_hklm))) + es_fprintf (outfp, "### %s ->%s<-%s\n", sl->d, p, + from_hklm? " [hklm]":""); + else + es_fprintf (outfp, "### %s [not set]\n", sl->d); + xfree (p); + } + } + if (!any) + es_fprintf (outfp, "###\n"); + show_other_registry_entries (outfp); + show_registry_entries_from_file (outfp); +#endif /*HAVE_W32_SYSTEM*/ + + free_strlist (list); + + /* Check for uncommon files in the home directory. */ + dir = gnupg_opendir (gnupg_homedir ()); + if (!dir) + { + err = gpg_error_from_syserror (); + log_error ("error reading directory \"%s\": %s\n", + gnupg_homedir (), gpg_strerror (err)); + return; + } + + any = 0; + while ((dir_entry = gnupg_readdir (dir))) + { + for (idx = 0; idx < DIM (names); idx++) + { + n = strlen (names[idx]); + if (!ascii_strncasecmp (dir_entry->d_name, names[idx], n) + && dir_entry->d_name[n] == '-' + && ascii_strncasecmp (dir_entry->d_name, "gpg.conf-1", 10)) + { + if (!any) + { + any = 1; + es_fprintf (outfp, + "###\n" + "### Warning: suspicious files in \"%s\":\n", + gnupg_homedir ()); + } + es_fprintf (outfp, "### %s\n", dir_entry->d_name); + } + } + } + if (any) + es_fprintf (outfp, "###\n"); + gnupg_closedir (dir); +} diff --git a/tools/gpgconf.h b/tools/gpgconf.h new file mode 100644 index 0000000..83aee9a --- /dev/null +++ b/tools/gpgconf.h @@ -0,0 +1,131 @@ +/* gpgconf.h - Global definitions for gpgconf + * Copyright (C) 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef GPGCONF_H +#define GPGCONF_H + +#include "../common/util.h" + +/* We keep all global options in the structure OPT. */ +EXTERN_UNLESS_MAIN_MODULE +struct +{ + int verbose; /* Verbosity level. */ + int quiet; /* Be extra quiet. */ + int dry_run; /* Don't change any persistent data. */ + int runtime; /* Make changes active at runtime. */ + int null; /* Option -0 active. */ + char *outfile; /* Name of output file. */ + + int component; /* The active component. */ +} opt; + + +/*-- gpgconf.c --*/ +void gpgconf_write_status (int no, const char *format, + ...) GPGRT_ATTR_PRINTF(2,3); +void gpgconf_failure (gpg_error_t err) GPGRT_ATTR_NORETURN; + +/*-- gpgconf-comp.c --*/ + +/* Component system. Each component is a set of options that can be + * configured at the same time. If you change this, don't forget to + * update gc_component[] in gpgconf-comp.c. */ +typedef enum + { + /* Any component, used as a wildcard arg. */ + GC_COMPONENT_ANY, + + /* The classic GPG for OpenPGP. */ + GC_COMPONENT_GPG, + + /* GPG for S/MIME. */ + GC_COMPONENT_GPGSM, + + /* The GPG Agent. */ + GC_COMPONENT_GPG_AGENT, + + /* The Smardcard Daemon. */ + GC_COMPONENT_SCDAEMON, + + /* The LDAP Directory Manager for CRLs. */ + GC_COMPONENT_DIRMNGR, + + /* The external Pinentry. */ + GC_COMPONENT_PINENTRY, + + /* The number of components. */ + GC_COMPONENT_NR + } gc_component_id_t; + + +/* Initialize the components. */ +void gc_components_init (void); + +/* Percent-Escape special characters. The string is valid until the + next invocation of the function. */ +char *gc_percent_escape (const char *src); + + +void gc_error (int status, int errnum, const char *fmt, ...); + +/* Launch given component. */ +gpg_error_t gc_component_launch (int component); + +/* Kill given component. */ +void gc_component_kill (int component); + +/* Reload given component. */ +void gc_component_reload (int component); + +/* List all components that are available. */ +void gc_component_list_components (estream_t out); + +/* List all programs along with their status. */ +void gc_check_programs (estream_t out); + +/* Find the component with the name NAME. Returns -1 if not + found. */ +int gc_component_find (const char *name); + +/* Retrieve the currently active options and their defaults from all + involved backends for this component. */ +void gc_component_retrieve_options (int component); + +/* List all options of the component COMPONENT. */ +void gc_component_list_options (int component, estream_t out); + +/* Read the modifications from IN and apply them. */ +void gc_component_change_options (int component, estream_t in, estream_t out, + int verbatim); + +/* 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); + +/* Process global configuration file. */ +int gc_process_gpgconf_conf (const char *fname, int update, int defaults, + estream_t listfp); + +/* Apply a profile. */ +gpg_error_t gc_apply_profile (const char *fname); + + +#endif /*GPGCONF_H*/ diff --git a/tools/gpgconf.w32-manifest.in b/tools/gpgconf.w32-manifest.in new file mode 100644 index 0000000..d7a1b01 --- /dev/null +++ b/tools/gpgconf.w32-manifest.in @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<description>GNU Privacy Guard (Config tool)</description> +<assemblyIdentity + type="win32" + name="GnuPG.gpgconf" + version="@BUILD_VERSION@" + /> +<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/><!-- 10 --> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/><!-- 8.1 --> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/><!-- 8 --> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/><!-- 7 --> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/><!-- Vista --> + </application> +</compatibility> +</assembly> diff --git a/tools/gpgparsemail.c b/tools/gpgparsemail.c new file mode 100644 index 0000000..b122097 --- /dev/null +++ b/tools/gpgparsemail.c @@ -0,0 +1,816 @@ +/* gpgparsemail.c - Standalone crypto mail parser + * Copyright (C) 2004, 2007 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + + +/* This utility prints an RFC822, possible MIME structured, message + in an annotated format with the first column having an indicator + for the content of the line. Several options are available to + scrutinize the message. S/MIME and OpenPGP support is included. */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <errno.h> +#include <stdarg.h> +#include <assert.h> +#include <time.h> +#include <signal.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/wait.h> + +#include "rfc822parse.h" + + +#define PGM "gpgparsemail" + +/* Option flags. */ +static int verbose; +static int debug; +static int opt_crypto; /* Decrypt or verify messages. */ +static int opt_no_header; /* Don't output the header lines. */ + +/* Structure used to communicate with the parser callback. */ +struct parse_info_s { + int show_header; /* Show the header lines. */ + int show_data; /* Show the data lines. */ + unsigned int skip_show; /* Temporary disable above for these + number of lines. */ + int show_data_as_note; /* The next data line should be shown + as a note. */ + int show_boundary; + int nesting_level; + + int is_pkcs7; /* Old style S/MIME message. */ + + int smfm_state; /* State of PGP/MIME or S/MIME parsing. */ + int is_smime; /* This is S/MIME and not PGP/MIME. */ + + const char *signing_protocol; + const char *signing_protocol_2; /* there are two ways to present + PKCS7 */ + int hashing_level; /* The nesting level we are hashing. */ + int hashing; + FILE *hash_file; + + FILE *sig_file; /* Signature part with MIME or full + pkcs7 data if IS_PCKS7 is set. */ + int verify_now; /* Flag set when all signature data is + available. */ +}; + + +/* Print diagnostic message and exit with failure. */ +static void +die (const char *format, ...) +{ + va_list arg_ptr; + + fflush (stdout); + fprintf (stderr, "%s: ", PGM); + + va_start (arg_ptr, format); + vfprintf (stderr, format, arg_ptr); + va_end (arg_ptr); + putc ('\n', stderr); + + exit (1); +} + + +/* Print diagnostic message. */ +static void +err (const char *format, ...) +{ + va_list arg_ptr; + + fflush (stdout); + fprintf (stderr, "%s: ", PGM); + + va_start (arg_ptr, format); + vfprintf (stderr, format, arg_ptr); + va_end (arg_ptr); + putc ('\n', stderr); +} + +static void * +xmalloc (size_t n) +{ + void *p = malloc (n); + if (!p) + die ("out of core: %s", strerror (errno)); + return p; +} + +/* static void * */ +/* xcalloc (size_t n, size_t m) */ +/* { */ +/* void *p = calloc (n, m); */ +/* if (!p) */ +/* die ("out of core: %s", strerror (errno)); */ +/* return p; */ +/* } */ + +/* static void * */ +/* xrealloc (void *old, size_t n) */ +/* { */ +/* void *p = realloc (old, n); */ +/* if (!p) */ +/* die ("out of core: %s", strerror (errno)); */ +/* return p; */ +/* } */ + +/* static char * */ +/* xstrdup (const char *string) */ +/* { */ +/* void *p = malloc (strlen (string)+1); */ +/* if (!p) */ +/* die ("out of core: %s", strerror (errno)); */ +/* strcpy (p, string); */ +/* return p; */ +/* } */ + +#ifndef HAVE_STPCPY +static char * +stpcpy (char *a,const char *b) +{ + while (*b) + *a++ = *b++; + *a = 0; + + return (char*)a; +} +#endif + +static int +run_gnupg (int smime, int sig_fd, int data_fd, int *close_list) +{ + int rp[2]; + pid_t pid; + int i, c, is_status; + unsigned int pos; + char status_buf[10]; + FILE *fp; + + if (pipe (rp) == -1) + die ("error creating a pipe: %s", strerror (errno)); + + pid = fork (); + if (pid == -1) + die ("error forking process: %s", strerror (errno)); + + if (!pid) + { /* Child. */ + char data_fd_buf[50]; + int fd; + + /* Connect our signature fd to stdin. */ + if (sig_fd != 0) + { + if (dup2 (sig_fd, 0) == -1) + die ("dup2 stdin failed: %s", strerror (errno)); + } + + /* Keep our data fd and format it for gpg/gpgsm use. */ + if (data_fd == -1) + *data_fd_buf = 0; + else + sprintf (data_fd_buf, "-&%d", data_fd); + + /* Send stdout to the bit bucket. */ + fd = open ("/dev/null", O_WRONLY); + if (fd == -1) + die ("can't open '/dev/null': %s", strerror (errno)); + if (fd != 1) + { + if (dup2 (fd, 1) == -1) + die ("dup2 stderr failed: %s", strerror (errno)); + } + + /* Connect stderr to our pipe. */ + if (rp[1] != 2) + { + if (dup2 (rp[1], 2) == -1) + die ("dup2 stderr failed: %s", strerror (errno)); + } + + /* Close other files. */ + for (i=0; (fd=close_list[i]) != -1; i++) + if (fd > 2 && fd != data_fd) + close (fd); + errno = 0; + + if (smime) + execlp ("gpgsm", "gpgsm", + "--enable-special-filenames", + "--status-fd", "2", + "--assume-base64", + "--verify", + "--", + "-", data_fd == -1? NULL : data_fd_buf, + NULL); + else + execlp ("gpg", "gpg", + "--enable-special-filenames", + "--status-fd", "2", + "--verify", + "--debug=512", + "--", + "-", data_fd == -1? NULL : data_fd_buf, + NULL); + + die ("failed to exec the crypto command: %s", strerror (errno)); + } + + /* Parent. */ + close (rp[1]); + + fp = fdopen (rp[0], "r"); + if (!fp) + die ("can't fdopen pipe for reading: %s", strerror (errno)); + + pos = 0; + is_status = 0; + assert (sizeof status_buf > 9); + while ((c=getc (fp)) != EOF) + { + if (pos < 9) + status_buf[pos] = c; + else + { + if (pos == 9) + { + is_status = !memcmp (status_buf, "[GNUPG:] ", 9); + if (is_status) + fputs ( "c ", stdout); + else if (verbose) + fputs ( "# ", stdout); + fwrite (status_buf, 9, 1, stdout); + } + putchar (c); + } + if (c == '\n') + { + if (verbose && pos < 9) + { + fputs ( "# ", stdout); + fwrite (status_buf, pos+1, 1, stdout); + } + pos = 0; + } + else + pos++; + } + if (pos) + { + if (verbose && pos < 9) + { + fputs ( "# ", stdout); + fwrite (status_buf, pos+1, 1, stdout); + } + putchar ('\n'); + } + fclose (fp); + + while ( (i=waitpid (pid, NULL, 0)) == -1 && errno == EINTR) + ; + if (i == -1) + die ("waiting for child failed: %s", strerror (errno)); + + return 0; +} + + + + +/* Verify the signature in the current temp files. */ +static void +verify_signature (struct parse_info_s *info) +{ + int close_list[10]; + + if (info->is_pkcs7) + { + assert (!info->hash_file); + assert (info->sig_file); + rewind (info->sig_file); + } + else + { + assert (info->hash_file); + assert (info->sig_file); + rewind (info->hash_file); + rewind (info->sig_file); + } + +/* printf ("# Begin hashed data\n"); */ +/* while ( (c=getc (info->hash_file)) != EOF) */ +/* putchar (c); */ +/* printf ("# End hashed data signature\n"); */ +/* printf ("# Begin signature\n"); */ +/* while ( (c=getc (info->sig_file)) != EOF) */ +/* putchar (c); */ +/* printf ("# End signature\n"); */ +/* rewind (info->hash_file); */ +/* rewind (info->sig_file); */ + + close_list[0] = -1; + run_gnupg (info->is_smime, fileno (info->sig_file), + info->hash_file ? fileno (info->hash_file) : -1, close_list); +} + + + + + +/* Prepare for a multipart/signed. + FIELD_CTX is the parsed context of the content-type header.*/ +static void +mime_signed_begin (struct parse_info_s *info, rfc822parse_t msg, + rfc822parse_field_t field_ctx) +{ + const char *s; + + (void)msg; + + s = rfc822parse_query_parameter (field_ctx, "protocol", 1); + if (s) + { + printf ("h signed.protocol: %s\n", s); + if (!strcmp (s, "application/pgp-signature")) + { + if (info->smfm_state) + err ("note: ignoring nested PGP/MIME or S/MIME signature"); + else + { + info->smfm_state = 1; + info->is_smime = 0; + info->signing_protocol = "application/pgp-signature"; + info->signing_protocol_2 = NULL; + } + } + else if (!strcmp (s, "application/pkcs7-signature") + || !strcmp (s, "application/x-pkcs7-signature")) + { + if (info->smfm_state) + err ("note: ignoring nested PGP/MIME or S/MIME signature"); + else + { + info->smfm_state = 1; + info->is_smime = 1; + info->signing_protocol = "application/pkcs7-signature"; + info->signing_protocol_2 = "application/x-pkcs7-signature"; + } + } + else if (verbose) + printf ("# this protocol is not supported\n"); + } +} + + +/* Prepare for a multipart/encrypted. + FIELD_CTX is the parsed context of the content-type header.*/ +static void +mime_encrypted_begin (struct parse_info_s *info, rfc822parse_t msg, + rfc822parse_field_t field_ctx) +{ + const char *s; + + (void)info; + (void)msg; + + s = rfc822parse_query_parameter (field_ctx, "protocol", 0); + if (s) + printf ("h encrypted.protocol: %s\n", s); +} + + +/* Prepare for old-style pkcs7 messages. */ +static void +pkcs7_begin (struct parse_info_s *info, rfc822parse_t msg, + rfc822parse_field_t field_ctx) +{ + const char *s; + + (void)msg; + + s = rfc822parse_query_parameter (field_ctx, "name", 0); + if (s) + printf ("h pkcs7.name: %s\n", s); + if (info->is_pkcs7) + err ("note: ignoring nested pkcs7 data"); + else + { + info->is_pkcs7 = 1; + if (opt_crypto) + { + assert (!info->sig_file); + info->sig_file = tmpfile (); + if (!info->sig_file) + die ("error creating temp file: %s", strerror (errno)); + } + } +} + + +/* Print the event received by the parser for debugging as comment + line. */ +static void +show_event (rfc822parse_event_t event) +{ + const char *s; + + switch (event) + { + case RFC822PARSE_OPEN: s= "Open"; break; + case RFC822PARSE_CLOSE: s= "Close"; break; + case RFC822PARSE_CANCEL: s= "Cancel"; break; + case RFC822PARSE_T2BODY: s= "T2Body"; break; + case RFC822PARSE_FINISH: s= "Finish"; break; + case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break; + case RFC822PARSE_LEVEL_DOWN: s= "Level_Down"; break; + case RFC822PARSE_LEVEL_UP: s= "Level_Up"; break; + case RFC822PARSE_BOUNDARY: s= "Boundary"; break; + case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break; + case RFC822PARSE_BEGIN_HEADER: s= "Begin_Header"; break; + case RFC822PARSE_PREAMBLE: s= "Preamble"; break; + case RFC822PARSE_EPILOGUE: s= "Epilogue"; break; + default: s= "[unknown event]"; break; + } + printf ("# *** got RFC822 event %s\n", s); +} + +/* This function is called by the parser to communicate events. This + callback comminucates with the main program using a structure + passed in OPAQUE. Should return 0 or set errno and return -1. */ +static int +message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg) +{ + struct parse_info_s *info = opaque; + + if (debug) + show_event (event); + + if (event == RFC822PARSE_BEGIN_HEADER || event == RFC822PARSE_T2BODY) + { + /* We need to check here whether to start collecting signed data + because attachments might come without header lines and thus + we won't see the BEGIN_HEADER event. */ + if (info->smfm_state == 1) + { + printf ("c begin_hash\n"); + info->hashing = 1; + info->hashing_level = info->nesting_level; + info->smfm_state++; + + if (opt_crypto) + { + assert (!info->hash_file); + info->hash_file = tmpfile (); + if (!info->hash_file) + die ("failed to create temporary file: %s", strerror (errno)); + } + } + } + + + if (event == RFC822PARSE_OPEN) + { + /* Initialize for a new message. */ + info->show_header = 1; + } + else if (event == RFC822PARSE_T2BODY) + { + rfc822parse_field_t ctx; + + ctx = rfc822parse_parse_field (msg, "Content-Type", -1); + if (ctx) + { + const char *s1, *s2; + s1 = rfc822parse_query_media_type (ctx, &s2); + if (s1) + { + printf ("h media: %*s%s %s\n", + info->nesting_level*2, "", s1, s2); + if (info->smfm_state == 3) + { + char *buf = xmalloc (strlen (s1) + strlen (s2) + 2); + strcpy (stpcpy (stpcpy (buf, s1), "/"), s2); + assert (info->signing_protocol); + if (strcmp (buf, info->signing_protocol) && + (!info->signing_protocol_2 + || strcmp (buf,info->signing_protocol_2))) + err ("invalid %s structure; expected %s%s%s, found '%s'", + info->is_smime? "S/MIME":"PGP/MIME", + info->signing_protocol, + info->signing_protocol_2 ? " or " : "", + info->signing_protocol_2 ? info->signing_protocol_2:"", + buf); + else + { + printf ("c begin_signature\n"); + info->smfm_state++; + if (opt_crypto) + { + assert (!info->sig_file); + info->sig_file = tmpfile (); + if (!info->sig_file) + die ("error creating temp file: %s", + strerror (errno)); + } + } + free (buf); + } + else if (!strcmp (s1, "multipart")) + { + if (!strcmp (s2, "signed")) + mime_signed_begin (info, msg, ctx); + else if (!strcmp (s2, "encrypted")) + mime_encrypted_begin (info, msg, ctx); + } + else if (!strcmp (s1, "application") + && (!strcmp (s2, "pkcs7-mime") + || !strcmp (s2, "x-pkcs7-mime"))) + pkcs7_begin (info, msg, ctx); + } + else + printf ("h media: %*s none\n", info->nesting_level*2, ""); + + rfc822parse_release_field (ctx); + } + else + printf ("h media: %*stext plain [assumed]\n", + info->nesting_level*2, ""); + + + info->show_header = 0; + info->show_data = 1; + info->skip_show = 1; + } + else if (event == RFC822PARSE_PREAMBLE) + info->show_data_as_note = 1; + else if (event == RFC822PARSE_LEVEL_DOWN) + { + printf ("b down\n"); + info->nesting_level++; + } + else if (event == RFC822PARSE_LEVEL_UP) + { + printf ("b up\n"); + if (info->nesting_level) + info->nesting_level--; + else + err ("invalid structure (bad nesting level)"); + } + else if (event == RFC822PARSE_BOUNDARY || event == RFC822PARSE_LAST_BOUNDARY) + { + info->show_data = 0; + info->show_boundary = 1; + if (event == RFC822PARSE_BOUNDARY) + { + info->show_header = 1; + info->skip_show = 1; + printf ("b part\n"); + } + else + printf ("b last\n"); + + if (info->smfm_state == 2 && info->nesting_level == info->hashing_level) + { + printf ("c end_hash\n"); + info->smfm_state++; + info->hashing = 0; + } + else if (info->smfm_state == 4) + { + printf ("c end_signature\n"); + info->verify_now = 1; + } + } + + return 0; +} + + +/* Read a message from FP and process it according to the global + options. */ +static void +parse_message (FILE *fp) +{ + char line[5000]; + size_t length; + rfc822parse_t msg; + unsigned int lineno = 0; + int no_cr_reported = 0; + struct parse_info_s info; + + memset (&info, 0, sizeof info); + + msg = rfc822parse_open (message_cb, &info); + if (!msg) + die ("can't open parser: %s", strerror (errno)); + + /* Fixme: We should not use fgets because it can't cope with + embedded nul characters. */ + while (fgets (line, sizeof (line), fp)) + { + lineno++; + if (lineno == 1 && !strncmp (line, "From ", 5)) + continue; /* We better ignore a leading From line. */ + + length = strlen (line); + if (length && line[length - 1] == '\n') + line[--length] = 0; + else + err ("line number %u too long or last line not terminated", lineno); + if (length && line[length - 1] == '\r') + line[--length] = 0; + else if (verbose && !no_cr_reported) + { + err ("non canonical ended line detected (line %u)", lineno); + no_cr_reported = 1; + } + + + if (rfc822parse_insert (msg, line, length)) + die ("parser failed: %s", strerror (errno)); + + if (info.hashing) + { + /* Delay hashing of the CR/LF because the last line ending + belongs to the next boundary. */ + if (debug) + printf ("# hashing %s'%s'\n", info.hashing==2?"CR,LF+":"", line); + if (opt_crypto) + { + if (info.hashing == 2) + fputs ("\r\n", info.hash_file); + fputs (line, info.hash_file); + if (ferror (info.hash_file)) + die ("error writing to temporary file: %s", strerror (errno)); + } + + info.hashing = 2; + } + + if (info.sig_file && opt_crypto) + { + if (info.verify_now) + { + verify_signature (&info); + if (info.hash_file) + fclose (info.hash_file); + info.hash_file = NULL; + fclose (info.sig_file); + info.sig_file = NULL; + info.smfm_state = 0; + info.is_smime = 0; + info.is_pkcs7 = 0; + } + else + { + fputs (line, info.sig_file); + fputs ("\r\n", info.sig_file); + if (ferror (info.sig_file)) + die ("error writing to temporary file: %s", strerror (errno)); + } + } + + if (info.show_boundary) + { + if (!opt_no_header) + printf (":%s\n", line); + info.show_boundary = 0; + } + + if (info.skip_show) + info.skip_show--; + else if (info.show_data) + { + if (info.show_data_as_note) + { + if (verbose) + printf ("# DATA: %s\n", line); + info.show_data_as_note = 0; + } + else + printf (" %s\n", line); + } + else if (info.show_header && !opt_no_header) + printf (".%s\n", line); + + } + + if (info.sig_file && opt_crypto && info.is_pkcs7) + { + verify_signature (&info); + fclose (info.sig_file); + info.sig_file = NULL; + info.is_pkcs7 = 0; + } + + rfc822parse_close (msg); +} + + +int +main (int argc, char **argv) +{ + int last_argc = -1; + + if (argc) + { + argc--; argv++; + } + while (argc && last_argc != argc ) + { + last_argc = argc; + if (!strcmp (*argv, "--")) + { + argc--; argv++; + break; + } + else if (!strcmp (*argv, "--help")) + { + puts ( + "Usage: " PGM " [OPTION] [FILE]\n" + "Parse a mail message into an annotated format.\n\n" + " --crypto decrypt or verify messages\n" + " --no-header don't output the header lines\n" + " --verbose enable extra informational output\n" + " --debug enable additional debug output\n" + " --help display this help and exit\n\n" + "With no FILE, or when FILE is -, read standard input.\n\n" + "WARNING: This tool is under development.\n" + " The semantics may change without notice\n\n" + "Report bugs to <bug-gnupg@gnu.org>."); + exit (0); + } + else if (!strcmp (*argv, "--verbose")) + { + verbose = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--debug")) + { + verbose = debug = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--crypto")) + { + opt_crypto = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--no-header")) + { + opt_no_header = 1; + argc--; argv++; + } + } + + if (argc > 1) + die ("usage: " PGM " [OPTION] [FILE] (try --help for more information)\n"); + + signal (SIGPIPE, SIG_IGN); + + if (argc && strcmp (*argv, "-")) + { + FILE *fp = fopen (*argv, "rb"); + if (!fp) + die ("can't open '%s': %s", *argv, strerror (errno)); + parse_message (fp); + fclose (fp); + } + else + parse_message (stdin); + + return 0; +} + + +/* +Local Variables: +compile-command: "gcc -Wall -Wno-pointer-sign -g -o gpgparsemail rfc822parse.c gpgparsemail.c" +End: +*/ diff --git a/tools/gpgsplit.c b/tools/gpgsplit.c new file mode 100644 index 0000000..d65348c --- /dev/null +++ b/tools/gpgsplit.c @@ -0,0 +1,897 @@ +/* gpgsplit.c - An OpenPGP packet splitting tool + * Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <assert.h> +#include <sys/types.h> +#ifdef HAVE_DOSISH_SYSTEM +# include <fcntl.h> /* for setmode() */ +#endif +#ifdef HAVE_ZIP +# include <zlib.h> +#endif +#if defined(__riscos__) && defined(USE_ZLIBRISCOS) +# include "zlib-riscos.h" +#endif + +#define INCLUDED_BY_MAIN_MODULE 1 +#include "../common/util.h" +#include "../common/openpgpdefs.h" + +#ifdef HAVE_BZIP2 +# include <bzlib.h> +#endif /* HAVE_BZIP2 */ + +static int opt_verbose; +static const char *opt_prefix = ""; +static int opt_uncompress; +static int opt_secret_to_public; +static int opt_no_split; + +static void g10_exit( int rc ); +static void split_packets (const char *fname); + + +enum cmd_and_opt_values { + aNull = 0, + oVerbose = 'v', + oPrefix = 'p', + oUncompress = 500, + oSecretToPublic, + oNoSplit, + + aTest +}; + + +static ARGPARSE_OPTS opts[] = { + + { 301, NULL, 0, "@Options:\n " }, + + { oVerbose, "verbose", 0, "verbose" }, + { oPrefix, "prefix", 2, "|STRING|Prepend filenames with STRING" }, + { oUncompress, "uncompress", 0, "uncompress a packet"}, + { oSecretToPublic, "secret-to-public", 0, "convert secret keys to public keys"}, + { oNoSplit, "no-split", 0, "write to stdout and don't actually split"}, + + ARGPARSE_end () +}; + + +static const char * +my_strusage (int level) +{ + const char *p; + switch (level) + { + case 9: p = "GPL-3.0-or-later"; break; + case 11: p = "gpgsplit (@GNUPG@)"; + break; + case 13: p = VERSION; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = "Please report bugs to <@EMAIL@>.\n"; break; + + case 1: + case 40: p = + "Usage: gpgsplit [options] [files] (-h for help)"; + break; + case 41: p = + "Syntax: gpgsplit [options] [files]\n" + "Split an OpenPGP message into packets\n"; + break; + + default: p = NULL; + } + return p; +} + + + +int +main (int argc, char **argv) +{ + ARGPARSE_ARGS pargs; + +#ifdef HAVE_DOSISH_SYSTEM + setmode( fileno(stdin), O_BINARY ); + setmode( fileno(stdout), O_BINARY ); +#endif + log_set_prefix ("gpgsplit", GPGRT_LOG_WITH_PREFIX); + set_strusage (my_strusage); + + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= ARGPARSE_FLAG_KEEP; + while (gnupg_argparse (NULL, &pargs, opts)) + { + switch (pargs.r_opt) + { + case oVerbose: opt_verbose = 1; break; + case oPrefix: opt_prefix = pargs.r.ret_str; break; + case oUncompress: opt_uncompress = 1; break; + case oSecretToPublic: opt_secret_to_public = 1; break; + case oNoSplit: opt_no_split = 1; break; + default : pargs.err = ARGPARSE_PRINT_ERROR; break; + } + } + gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ + + if (log_get_errorcount(0)) + g10_exit (2); + + if (!argc) + split_packets (NULL); + else + { + for ( ;argc; argc--, argv++) + split_packets (*argv); + } + + g10_exit (0); + return 0; +} + + +static void +g10_exit (int rc) +{ + rc = rc? rc : log_get_errorcount(0)? 2 : 0; + exit(rc ); +} + +static const char * +pkttype_to_string (int pkttype) +{ + const char *s; + + switch (pkttype) + { + case PKT_PUBKEY_ENC : s = "pk_enc"; break; + case PKT_SIGNATURE : s = "sig"; break; + case PKT_SYMKEY_ENC : s = "sym_enc"; break; + case PKT_ONEPASS_SIG : s = "onepass_sig"; break; + case PKT_SECRET_KEY : s = "secret_key"; break; + case PKT_PUBLIC_KEY : s = "public_key"; break; + case PKT_SECRET_SUBKEY : s = "secret_subkey"; break; + case PKT_COMPRESSED : + s = opt_uncompress? "uncompressed":"compressed"; + break; + case PKT_ENCRYPTED : s = "encrypted"; break; + case PKT_MARKER : s = "marker"; break; + case PKT_PLAINTEXT : s = "plaintext"; break; + case PKT_RING_TRUST : s = "ring_trust"; break; + case PKT_USER_ID : s = "user_id"; break; + case PKT_PUBLIC_SUBKEY : s = "public_subkey"; break; + case PKT_OLD_COMMENT : s = "old_comment"; break; + case PKT_ATTRIBUTE : s = "attribute"; break; + case PKT_ENCRYPTED_MDC : s = "encrypted_mdc"; break; + case PKT_MDC : s = "mdc"; break; + case PKT_COMMENT : s = "comment"; break; + case PKT_GPG_CONTROL : s = "gpg_control"; break; + default: s = "unknown"; break; + } + return s; +} + + +/* + * Create a new filename and a return a pointer to a statically + * allocated buffer + */ +static char * +create_filename (int pkttype) +{ + static unsigned int partno = 0; + static char *name; + + if (!name) + name = xmalloc (strlen (opt_prefix) + 100 ); + + assert (pkttype < 1000 && pkttype >= 0 ); + partno++; + sprintf (name, "%s%06u-%03d" EXTSEP_S "%.40s", + opt_prefix, partno, pkttype, pkttype_to_string (pkttype)); + return name; +} + +static int +read_u16 (FILE *fp, size_t *rn) +{ + int c; + + if ( (c = getc (fp)) == EOF ) + return -1; + *rn = c << 8; + if ( (c = getc (fp)) == EOF ) + return -1; + *rn |= c; + return 0; +} + +static int +read_u32 (FILE *fp, unsigned long *rn) +{ + size_t tmp; + + if (read_u16 (fp, &tmp)) + return -1; + *rn = tmp << 16; + if (read_u16 (fp, &tmp)) + return -1; + *rn |= tmp; + return 0; +} + +static int +write_old_header (FILE *fp, int pkttype, unsigned int len) +{ + int ctb = (0x80 | ((pkttype & 15)<<2)); + + if (len < 256) + ; + else if (len < 65536) + ctb |= 1; + else + ctb |= 2; + + if ( putc ( ctb, fp) == EOF ) + return -1; + + if ( (ctb & 2) ) + { + if (putc ((len>>24), fp) == EOF) + return -1; + if (putc ((len>>16), fp) == EOF) + return -1; + } + if ( (ctb & 3) ) + { + if (putc ((len>>8), fp) == EOF) + return -1; + } + if (putc ((len&0xff), fp) == EOF) + return -1; + return 0; +} + +static int +write_new_header (FILE *fp, int pkttype, unsigned int len) +{ + if ( putc ((0xc0 | (pkttype & 0x3f)), fp) == EOF ) + return -1; + + if (len < 192) + { + if (putc (len, fp) == EOF) + return -1; + } + else if (len < 8384) + { + len -= 192; + if (putc ((len/256)+192, fp) == EOF) + return -1; + if (putc ((len%256), fp) == EOF) + return -1; + } + else + { + if (putc ( 0xff, fp) == EOF) + return -1; + if (putc ( (len >> 24), fp) == EOF) + return -1; + if (putc ( (len >> 16), fp) == EOF) + return -1; + if (putc ( (len >> 8), fp) == EOF) + return -1; + if (putc ( (len & 0xff), fp) == EOF) + return -1; + } + return 0; +} + +/* Return the length of the public key given BUF of BUFLEN with a + secret key. */ +static int +public_key_length (const unsigned char *buf, size_t buflen) +{ + const unsigned char *s; + int nmpis; + + /* byte version number (3 or 4) + u32 creation time + [u16 valid days (version 3 only)] + byte algorithm + n MPIs (n and e) */ + if (!buflen) + return 0; + if (buf[0] < 2 || buf[0] > 4) + return 0; /* wrong version number */ + if (buflen < (buf[0] == 4? 6:8)) + return 0; + s = buf + (buf[0] == 4? 6:8); + buflen -= (buf[0] == 4? 6:8); + switch (s[-1]) + { + case 1: + case 2: + case 3: + nmpis = 2; + break; + case 16: + case 20: + nmpis = 3; + break; + case 17: + nmpis = 4; + break; + default: + return 0; + } + + for (; nmpis; nmpis--) + { + unsigned int nbits, nbytes; + + if (buflen < 2) + return 0; + nbits = (s[0] << 8) | s[1]; + s += 2; buflen -= 2; + nbytes = (nbits+7) / 8; + if (buflen < nbytes) + return 0; + s += nbytes; buflen -= nbytes; + } + + return s - buf; +} + +#ifdef HAVE_ZIP +static int +handle_zlib(int algo,FILE *fpin,FILE *fpout) +{ + z_stream zs; + byte *inbuf, *outbuf; + unsigned int inbufsize, outbufsize; + int c,zinit_done, zrc, nread, count; + size_t n; + + memset (&zs, 0, sizeof zs); + inbufsize = 2048; + inbuf = xmalloc (inbufsize); + outbufsize = 8192; + outbuf = xmalloc (outbufsize); + zs.avail_in = 0; + zinit_done = 0; + + do + { + if (zs.avail_in < inbufsize) + { + n = zs.avail_in; + if (!n) + zs.next_in = (Bytef *) inbuf; + count = inbufsize - n; + for (nread=0; + nread < count && (c=getc (fpin)) != EOF; + nread++) + inbuf[n+nread] = c; + + n += nread; + if (nread < count && algo == 1) + { + inbuf[n] = 0xFF; /* chew dummy byte */ + n++; + } + zs.avail_in = n; + } + zs.next_out = (Bytef *) outbuf; + zs.avail_out = outbufsize; + + if (!zinit_done) + { + zrc = (algo == 1? inflateInit2 ( &zs, -13) + : inflateInit ( &zs )); + if (zrc != Z_OK) + { + log_fatal ("zlib problem: %s\n", zs.msg? zs.msg : + zrc == Z_MEM_ERROR ? "out of core" : + zrc == Z_VERSION_ERROR ? + "invalid lib version" : + "unknown error" ); + } + zinit_done = 1; + } + else + { +#ifdef Z_SYNC_FLUSH + zrc = inflate (&zs, Z_SYNC_FLUSH); +#else + zrc = inflate (&zs, Z_PARTIAL_FLUSH); +#endif + if (zrc == Z_STREAM_END) + ; /* eof */ + else if (zrc != Z_OK && zrc != Z_BUF_ERROR) + { + if (zs.msg) + log_fatal ("zlib inflate problem: %s\n", zs.msg ); + else + log_fatal ("zlib inflate problem: rc=%d\n", zrc ); + } + for (n=0; n < outbufsize - zs.avail_out; n++) + { + if (putc (outbuf[n], fpout) == EOF ) + return 1; + } + } + } + while (zrc != Z_STREAM_END && zrc != Z_BUF_ERROR); + { + int i; + + fputs ("Left over bytes:", stderr); + for (i=0; i < zs.avail_in; i++) + fprintf (stderr, " %02X", zs.next_in[i]); + putc ('\n', stderr); + + } + inflateEnd (&zs); + + return 0; +} +#endif /*HAVE_ZIP*/ + +#ifdef HAVE_BZIP2 +static int +handle_bzip2(int algo,FILE *fpin,FILE *fpout) +{ + bz_stream bzs; + byte *inbuf, *outbuf; + unsigned int inbufsize, outbufsize; + int c,zinit_done, zrc, nread, count; + size_t n; + + memset (&bzs, 0, sizeof bzs); + inbufsize = 2048; + inbuf = xmalloc (inbufsize); + outbufsize = 8192; + outbuf = xmalloc (outbufsize); + bzs.avail_in = 0; + zinit_done = 0; + + do + { + if (bzs.avail_in < inbufsize) + { + n = bzs.avail_in; + if (!n) + bzs.next_in = inbuf; + count = inbufsize - n; + for (nread=0; + nread < count && (c=getc (fpin)) != EOF; + nread++) + inbuf[n+nread] = c; + + n += nread; + if (nread < count && algo == 1) + { + inbuf[n] = 0xFF; /* chew dummy byte */ + n++; + } + bzs.avail_in = n; + } + bzs.next_out = outbuf; + bzs.avail_out = outbufsize; + + if (!zinit_done) + { + zrc = BZ2_bzDecompressInit(&bzs,0,0); + if (zrc != BZ_OK) + log_fatal ("bz2lib problem: %d\n",zrc); + zinit_done = 1; + } + else + { + zrc = BZ2_bzDecompress(&bzs); + if (zrc == BZ_STREAM_END) + ; /* eof */ + else if (zrc != BZ_OK && zrc != BZ_PARAM_ERROR) + log_fatal ("bz2lib inflate problem: %d\n", zrc ); + for (n=0; n < outbufsize - bzs.avail_out; n++) + { + if (putc (outbuf[n], fpout) == EOF ) + return 1; + } + } + } + while (zrc != BZ_STREAM_END && zrc != BZ_PARAM_ERROR); + BZ2_bzDecompressEnd(&bzs); + + return 0; +} +#endif /* HAVE_BZIP2 */ + +/* hdr must point to a buffer large enough to hold all header bytes */ +static int +write_part (FILE *fpin, unsigned long pktlen, + int pkttype, int partial, unsigned char *hdr, size_t hdrlen) +{ + FILE *fpout; + int c, first; + unsigned char *p; + const char *outname = create_filename (pkttype); + +#if defined(__riscos__) && defined(USE_ZLIBRISCOS) + static int initialized = 0; + + if (!initialized) + initialized = riscos_load_module("ZLib", zlib_path, 1); +#endif + if (opt_no_split) + fpout = stdout; + else + { + if (opt_verbose) + log_info ("writing '%s'\n", outname); + fpout = gnupg_fopen (outname, "wb"); + if (!fpout) + { + log_error ("error creating '%s': %s\n", outname, strerror(errno)); + /* stop right now, otherwise we would mess up the sequence + of the part numbers */ + g10_exit (1); + } + } + + if (opt_secret_to_public + && (pkttype == PKT_SECRET_KEY || pkttype == PKT_SECRET_SUBKEY)) + { + unsigned char *blob = xmalloc (pktlen); + int i, len; + + pkttype = pkttype == PKT_SECRET_KEY? PKT_PUBLIC_KEY:PKT_PUBLIC_SUBKEY; + + for (i=0; i < pktlen; i++) + { + c = getc (fpin); + if (c == EOF) + goto read_error; + blob[i] = c; + } + len = public_key_length (blob, pktlen); + if (!len) + { + log_error ("error calculating public key length\n"); + g10_exit (1); + } + if ( (hdr[0] & 0x40) ) + { + if (write_new_header (fpout, pkttype, len)) + goto write_error; + } + else + { + if (write_old_header (fpout, pkttype, len)) + goto write_error; + } + + for (i=0; i < len; i++) + { + if ( putc (blob[i], fpout) == EOF ) + goto write_error; + } + + goto ready; + } + + + if (!opt_uncompress) + { + for (p=hdr; hdrlen; p++, hdrlen--) + { + if ( putc (*p, fpout) == EOF ) + goto write_error; + } + } + + first = 1; + while (partial) + { + size_t partlen; + + if (partial == 1) + { /* openpgp */ + if (first ) + { + c = pktlen; + assert( c >= 224 && c < 255 ); + first = 0; + } + else if ((c = getc (fpin)) == EOF ) + goto read_error; + else + hdr[hdrlen++] = c; + + if (c < 192) + { + pktlen = c; + partial = 0; /* (last segment may follow) */ + } + else if (c < 224 ) + { + pktlen = (c - 192) * 256; + if ((c = getc (fpin)) == EOF) + goto read_error; + hdr[hdrlen++] = c; + pktlen += c + 192; + partial = 0; + } + else if (c == 255) + { + if (read_u32 (fpin, &pktlen)) + goto read_error; + hdr[hdrlen++] = pktlen >> 24; + hdr[hdrlen++] = pktlen >> 16; + hdr[hdrlen++] = pktlen >> 8; + hdr[hdrlen++] = pktlen; + partial = 0; + } + else + { /* next partial body length */ + for (p=hdr; hdrlen; p++, hdrlen--) + { + if ( putc (*p, fpout) == EOF ) + goto write_error; + } + partlen = 1 << (c & 0x1f); + for (; partlen; partlen--) + { + if ((c = getc (fpin)) == EOF) + goto read_error; + if ( putc (c, fpout) == EOF ) + goto write_error; + } + } + } + else if (partial == 2) + { /* old gnupg */ + assert (!pktlen); + if ( read_u16 (fpin, &partlen) ) + goto read_error; + hdr[hdrlen++] = partlen >> 8; + hdr[hdrlen++] = partlen; + for (p=hdr; hdrlen; p++, hdrlen--) + { + if ( putc (*p, fpout) == EOF ) + goto write_error; + } + if (!partlen) + partial = 0; /* end of packet */ + for (; partlen; partlen--) + { + c = getc (fpin); + if (c == EOF) + goto read_error; + if ( putc (c, fpout) == EOF ) + goto write_error; + } + } + else + { /* compressed: read to end */ + pktlen = 0; + partial = 0; + hdrlen = 0; + if (opt_uncompress) + { + if ((c = getc (fpin)) == EOF) + goto read_error; + + if (0) + ; +#ifdef HAVE_ZIP + else if(c==1 || c==2) + { + if(handle_zlib(c,fpin,fpout)) + goto write_error; + } +#endif /* HAVE_ZIP */ +#ifdef HAVE_BZIP2 + else if(c==3) + { + if(handle_bzip2(c,fpin,fpout)) + goto write_error; + } +#endif /* HAVE_BZIP2 */ + else + { + log_error("invalid compression algorithm (%d)\n",c); + goto read_error; + } + } + else + { + while ( (c=getc (fpin)) != EOF ) + { + if ( putc (c, fpout) == EOF ) + goto write_error; + } + } + if (!feof (fpin)) + goto read_error; + } + } + + for (p=hdr; hdrlen; p++, hdrlen--) + { + if ( putc (*p, fpout) == EOF ) + goto write_error; + } + + /* standard packet or last segment of partial length encoded packet */ + for (; pktlen; pktlen--) + { + c = getc (fpin); + if (c == EOF) + goto read_error; + if ( putc (c, fpout) == EOF ) + goto write_error; + } + + ready: + if ( !opt_no_split && fclose (fpout) ) + log_error ("error closing '%s': %s\n", outname, strerror (errno)); + return 0; + + write_error: + log_error ("error writing '%s': %s\n", outname, strerror (errno)); + if (!opt_no_split) + fclose (fpout); + return 2; + + read_error: + if (!opt_no_split) + { + int save = errno; + fclose (fpout); + errno = save; + } + return -1; +} + + + +static int +do_split (FILE *fp) +{ + int c, ctb, pkttype; + unsigned long pktlen = 0; + int partial = 0; + unsigned char header[20]; + int header_idx = 0; + + ctb = getc (fp); + if (ctb == EOF) + return 3; /* ready */ + header[header_idx++] = ctb; + + if (!(ctb & 0x80)) + { + log_error("invalid CTB %02x\n", ctb ); + return 1; + } + if ( (ctb & 0x40) ) + { /* new CTB */ + pkttype = (ctb & 0x3f); + if( (c = getc (fp)) == EOF ) + return -1; + header[header_idx++] = c; + + if ( c < 192 ) + pktlen = c; + else if ( c < 224 ) + { + pktlen = (c - 192) * 256; + if( (c = getc (fp)) == EOF ) + return -1; + header[header_idx++] = c; + pktlen += c + 192; + } + else if ( c == 255 ) + { + if (read_u32 (fp, &pktlen)) + return -1; + header[header_idx++] = pktlen >> 24; + header[header_idx++] = pktlen >> 16; + header[header_idx++] = pktlen >> 8; + header[header_idx++] = pktlen; + } + else + { /* partial body length */ + pktlen = c; + partial = 1; + } + } + else + { + int lenbytes; + + pkttype = (ctb>>2)&0xf; + lenbytes = ((ctb&3)==3)? 0 : (1<<(ctb & 3)); + if (!lenbytes ) + { + pktlen = 0; /* don't know the value */ + if( pkttype == PKT_COMPRESSED ) + partial = 3; + else + partial = 2; /* the old GnuPG partial length encoding */ + } + else + { + for ( ; lenbytes; lenbytes-- ) + { + pktlen <<= 8; + if( (c = getc (fp)) == EOF ) + return -1; + header[header_idx++] = c; + + pktlen |= c; + } + } + } + + return write_part (fp, pktlen, pkttype, partial, header, header_idx); +} + + +static void +split_packets (const char *fname) +{ + FILE *fp; + int rc; + + if (!fname || !strcmp (fname, "-")) + { + fp = stdin; + fname = "-"; + } + else if ( !(fp = gnupg_fopen (fname,"rb")) ) + { + log_error ("can't open '%s': %s\n", fname, strerror (errno)); + return; + } + + while ( !(rc = do_split (fp)) ) + ; + if ( rc > 0 ) + ; /* error already handled */ + else if ( ferror (fp) ) + log_error ("error reading '%s': %s\n", fname, strerror (errno)); + else + log_error ("premature EOF while reading '%s'\n", fname ); + + if ( fp != stdin ) + fclose (fp); +} diff --git a/tools/gpgtar-create.c b/tools/gpgtar-create.c new file mode 100644 index 0000000..e642da0 --- /dev/null +++ b/tools/gpgtar-create.c @@ -0,0 +1,1297 @@ +/* gpgtar-create.c - Create a TAR archive + * Copyright (C) 2016-2017, 2019-2022 g10 Code GmbH + * Copyright (C) 2010, 2012, 2013 Werner Koch + * Copyright (C) 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <unistd.h> +#ifdef HAVE_W32_SYSTEM +# define WIN32_LEAN_AND_MEAN +# include <windows.h> +#else /*!HAVE_W32_SYSTEM*/ +# include <pwd.h> +# include <grp.h> +#endif /*!HAVE_W32_SYSTEM*/ + +#include "../common/i18n.h" +#include <gpg-error.h> +#include "../common/exechelp.h" +#include "../common/sysutils.h" +#include "../common/ccparray.h" +#include "../common/membuf.h" +#include "gpgtar.h" + +#ifndef HAVE_LSTAT +#define lstat(a,b) gnupg_stat ((a), (b)) +#endif + + +/* Count the number of written headers. Extended headers are not + * counted. */ +static unsigned long global_header_count; + + +/* Object to control the file scanning. */ +struct scanctrl_s; +typedef struct scanctrl_s *scanctrl_t; +struct scanctrl_s +{ + tar_header_t flist; + tar_header_t *flist_tail; + int nestlevel; +}; + + + +/* On Windows convert name to UTF8 and return it; caller must release + * the result. On Unix or if ALREADY_UTF8 is set, this function is a + * mere xtrystrcopy. On failure NULL is returned and ERRNO set. */ +static char * +name_to_utf8 (const char *name, int already_utf8) +{ +#ifdef HAVE_W32_SYSTEM + wchar_t *wstring; + char *result; + + if (already_utf8) + result = xtrystrdup (name); + else + { + wstring = native_to_wchar (name); + if (!wstring) + return NULL; + result = wchar_to_utf8 (wstring); + xfree (wstring); + } + return result; + +#else /*!HAVE_W32_SYSTEM */ + + (void)already_utf8; + return xtrystrdup (name); + +#endif /*!HAVE_W32_SYSTEM */ +} + + + + +/* Given a fresh header object HDR with only the name field set, try + to gather all available info. This is the W32 version. */ +#ifdef HAVE_W32_SYSTEM +static gpg_error_t +fillup_entry_w32 (tar_header_t hdr) +{ + char *p; + wchar_t *wfname; + WIN32_FILE_ATTRIBUTE_DATA fad; + DWORD attr; + + for (p=hdr->name; *p; p++) + if (*p == '/') + *p = '\\'; + wfname = gpgrt_fname_to_wchar (hdr->name); + for (p=hdr->name; *p; p++) + if (*p == '\\') + *p = '/'; + if (!wfname) + { + log_error ("error converting '%s': %s\n", hdr->name, w32_strerror (-1)); + return gpg_error_from_syserror (); + } + if (!GetFileAttributesExW (wfname, GetFileExInfoStandard, &fad)) + { + log_error ("error stat-ing '%s': %s\n", hdr->name, w32_strerror (-1)); + xfree (wfname); + return gpg_error_from_syserror (); + } + xfree (wfname); + + attr = fad.dwFileAttributes; + + if ((attr & FILE_ATTRIBUTE_NORMAL)) + hdr->typeflag = TF_REGULAR; + else if ((attr & FILE_ATTRIBUTE_DIRECTORY)) + hdr->typeflag = TF_DIRECTORY; + else if ((attr & FILE_ATTRIBUTE_DEVICE)) + hdr->typeflag = TF_NOTSUP; + else if ((attr & (FILE_ATTRIBUTE_OFFLINE | FILE_ATTRIBUTE_TEMPORARY))) + hdr->typeflag = TF_NOTSUP; + else + hdr->typeflag = TF_REGULAR; + + /* Map some attributes to USTAR defined mode bits. */ + hdr->mode = 0640; /* User may read and write, group only read. */ + if ((attr & FILE_ATTRIBUTE_DIRECTORY)) + hdr->mode |= 0110; /* Dirs are user and group executable. */ + if ((attr & FILE_ATTRIBUTE_READONLY)) + hdr->mode &= ~0200; /* Clear the user write bit. */ + if ((attr & FILE_ATTRIBUTE_HIDDEN)) + hdr->mode &= ~0707; /* Clear all user and other bits. */ + if ((attr & FILE_ATTRIBUTE_SYSTEM)) + hdr->mode |= 0004; /* Make it readable by other. */ + + /* Only set the size for a regular file. */ + if (hdr->typeflag == TF_REGULAR) + hdr->size = (fad.nFileSizeHigh * ((unsigned long long)MAXDWORD+1) + + fad.nFileSizeLow); + + hdr->mtime = (((unsigned long long)fad.ftLastWriteTime.dwHighDateTime << 32) + | fad.ftLastWriteTime.dwLowDateTime); + if (!hdr->mtime) + hdr->mtime = (((unsigned long long)fad.ftCreationTime.dwHighDateTime << 32) + | fad.ftCreationTime.dwLowDateTime); + hdr->mtime -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */ + hdr->mtime /= 10000000; /* Convert from 0.1us to seconds. */ + + return 0; +} +#endif /*HAVE_W32_SYSTEM*/ + + +/* Given a fresh header object HDR with only the name field set, try + to gather all available info. This is the POSIX version. */ +#ifndef HAVE_W32_SYSTEM +static gpg_error_t +fillup_entry_posix (tar_header_t hdr) +{ + gpg_error_t err; + struct stat sbuf; + + if (lstat (hdr->name, &sbuf)) + { + err = gpg_error_from_syserror (); + log_error ("error stat-ing '%s': %s\n", hdr->name, gpg_strerror (err)); + return err; + } + + if (S_ISREG (sbuf.st_mode)) + hdr->typeflag = TF_REGULAR; + else if (S_ISDIR (sbuf.st_mode)) + hdr->typeflag = TF_DIRECTORY; + else if (S_ISCHR (sbuf.st_mode)) + hdr->typeflag = TF_CHARDEV; + else if (S_ISBLK (sbuf.st_mode)) + hdr->typeflag = TF_BLOCKDEV; + else if (S_ISFIFO (sbuf.st_mode)) + hdr->typeflag = TF_FIFO; + else if (S_ISLNK (sbuf.st_mode)) + hdr->typeflag = TF_SYMLINK; + else + hdr->typeflag = TF_NOTSUP; + + /* FIXME: Save DEV and INO? */ + + /* Set the USTAR defined mode bits using the system macros. */ + if (sbuf.st_mode & S_IRUSR) + hdr->mode |= 0400; + if (sbuf.st_mode & S_IWUSR) + hdr->mode |= 0200; + if (sbuf.st_mode & S_IXUSR) + hdr->mode |= 0100; + if (sbuf.st_mode & S_IRGRP) + hdr->mode |= 0040; + if (sbuf.st_mode & S_IWGRP) + hdr->mode |= 0020; + if (sbuf.st_mode & S_IXGRP) + hdr->mode |= 0010; + if (sbuf.st_mode & S_IROTH) + hdr->mode |= 0004; + if (sbuf.st_mode & S_IWOTH) + hdr->mode |= 0002; + if (sbuf.st_mode & S_IXOTH) + hdr->mode |= 0001; +#ifdef S_IXUID + if (sbuf.st_mode & S_IXUID) + hdr->mode |= 04000; +#endif +#ifdef S_IXGID + if (sbuf.st_mode & S_IXGID) + hdr->mode |= 02000; +#endif +#ifdef S_ISVTX + if (sbuf.st_mode & S_ISVTX) + hdr->mode |= 01000; +#endif + + hdr->nlink = sbuf.st_nlink; + + hdr->uid = sbuf.st_uid; + hdr->gid = sbuf.st_gid; + + /* Only set the size for a regular file. */ + if (hdr->typeflag == TF_REGULAR) + hdr->size = sbuf.st_size; + + hdr->mtime = sbuf.st_mtime; + + return 0; +} +#endif /*!HAVE_W32_SYSTEM*/ + + +/* Add a new entry. The name of a directory entry is ENTRYNAME; if + that is NULL, DNAME is the name of the directory itself. Under + Windows ENTRYNAME shall have backslashes replaced by standard + slashes. */ +static gpg_error_t +add_entry (const char *dname, const char *entryname, scanctrl_t scanctrl) +{ + gpg_error_t err; + tar_header_t hdr; + char *p; + size_t dnamelen = strlen (dname); + + log_assert (dnamelen); + + hdr = xtrycalloc (1, sizeof *hdr + dnamelen + 1 + + (entryname? strlen (entryname) : 0) + 1); + if (!hdr) + return gpg_error_from_syserror (); + + p = stpcpy (hdr->name, dname); + if (entryname) + { + if (dname[dnamelen-1] != '/') + *p++ = '/'; + strcpy (p, entryname); + } + else + { + if (hdr->name[dnamelen-1] == '/') + hdr->name[dnamelen-1] = 0; + } +#ifdef HAVE_DOSISH_SYSTEM + err = fillup_entry_w32 (hdr); +#else + err = fillup_entry_posix (hdr); +#endif + if (err) + xfree (hdr); + else + { + /* FIXME: We don't have the extended info yet available so we + * can't print them. */ + if (opt.verbose) + gpgtar_print_header (hdr, NULL, log_get_stream ()); + *scanctrl->flist_tail = hdr; + scanctrl->flist_tail = &hdr->next; + } + + return 0; +} + + +static gpg_error_t +scan_directory (const char *dname, scanctrl_t scanctrl) +{ + gpg_error_t err = 0; + +#ifdef HAVE_W32_SYSTEM + /* Note that we introduced gnupg_opendir only after we had deployed + * this code and thus we don't change it for now. */ + WIN32_FIND_DATAW fi; + HANDLE hd = INVALID_HANDLE_VALUE; + char *p; + + if (!*dname) + return 0; /* An empty directory name has no entries. */ + + { + char *fname; + wchar_t *wfname; + + fname = xtrymalloc (strlen (dname) + 2 + 2 + 1); + if (!fname) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (!strcmp (dname, "/")) + strcpy (fname, "/*"); /* Trailing slash is not allowed. */ + else if (!strcmp (dname, ".")) + strcpy (fname, "*"); + else if (*dname && dname[strlen (dname)-1] == '/') + strcpy (stpcpy (fname, dname), "*"); + else if (*dname && dname[strlen (dname)-1] != '*') + strcpy (stpcpy (fname, dname), "/*"); + else + strcpy (fname, dname); + + for (p=fname; *p; p++) + if (*p == '/') + *p = '\\'; + wfname = gpgrt_fname_to_wchar (fname); + xfree (fname); + if (!wfname) + { + err = gpg_error_from_syserror (); + log_error (_("error reading directory '%s': %s\n"), + dname, gpg_strerror (err)); + goto leave; + } + hd = FindFirstFileW (wfname, &fi); + if (hd == INVALID_HANDLE_VALUE) + { + err = gpg_error_from_syserror (); + log_error (_("error reading directory '%s': %s\n"), + dname, w32_strerror (-1)); + xfree (wfname); + goto leave; + } + xfree (wfname); + } + + do + { + char *fname = wchar_to_utf8 (fi.cFileName); + if (!fname) + { + err = gpg_error_from_syserror (); + log_error ("error converting filename: %s\n", w32_strerror (-1)); + break; + } + for (p=fname; *p; p++) + if (*p == '\\') + *p = '/'; + if (!strcmp (fname, "." ) || !strcmp (fname, "..")) + err = 0; /* Skip self and parent dir entry. */ + else if (!strncmp (dname, "./", 2) && dname[2]) + err = add_entry (dname+2, fname, scanctrl); + else + err = add_entry (dname, fname, scanctrl); + xfree (fname); + } + while (!err && FindNextFileW (hd, &fi)); + if (err) + ; + else if (GetLastError () == ERROR_NO_MORE_FILES) + err = 0; + else + { + err = gpg_error_from_syserror (); + log_error (_("error reading directory '%s': %s\n"), + dname, w32_strerror (-1)); + } + + leave: + if (hd != INVALID_HANDLE_VALUE) + FindClose (hd); + +#else /*!HAVE_W32_SYSTEM*/ + DIR *dir; + struct dirent *de; + + if (!*dname) + return 0; /* An empty directory name has no entries. */ + + dir = opendir (dname); + if (!dir) + { + err = gpg_error_from_syserror (); + log_error (_("error reading directory '%s': %s\n"), + dname, gpg_strerror (err)); + return err; + } + + while ((de = readdir (dir))) + { + if (!strcmp (de->d_name, "." ) || !strcmp (de->d_name, "..")) + continue; /* Skip self and parent dir entry. */ + + err = add_entry (dname, de->d_name, scanctrl); + if (err) + goto leave; + } + + leave: + closedir (dir); +#endif /*!HAVE_W32_SYSTEM*/ + return err; +} + + +static gpg_error_t +scan_recursive (const char *dname, scanctrl_t scanctrl) +{ + gpg_error_t err = 0; + tar_header_t hdr, *start_tail, *stop_tail; + + if (scanctrl->nestlevel > 200) + { + log_error ("directories too deeply nested\n"); + return gpg_error (GPG_ERR_RESOURCE_LIMIT); + } + scanctrl->nestlevel++; + + log_assert (scanctrl->flist_tail); + start_tail = scanctrl->flist_tail; + scan_directory (dname, scanctrl); + stop_tail = scanctrl->flist_tail; + hdr = *start_tail; + for (; hdr && hdr != *stop_tail; hdr = hdr->next) + if (hdr->typeflag == TF_DIRECTORY) + { + if (opt.verbose > 1) + log_info ("scanning directory '%s'\n", hdr->name); + scan_recursive (hdr->name, scanctrl); + } + + scanctrl->nestlevel--; + return err; +} + + +/* Returns true if PATTERN is acceptable. */ +static int +pattern_valid_p (const char *pattern) +{ + if (!*pattern) + return 0; + if (*pattern == '.' && pattern[1] == '.') + return 0; + if (*pattern == '/' +#ifdef HAVE_DOSISH_SYSTEM + || *pattern == '\\' +#endif + ) + return 0; /* Absolute filenames are not supported. */ +#ifdef HAVE_DRIVE_LETTERS + if (((*pattern >= 'a' && *pattern <= 'z') + || (*pattern >= 'A' && *pattern <= 'Z')) + && pattern[1] == ':') + return 0; /* Drive letter are not allowed either. */ +#endif /*HAVE_DRIVE_LETTERS*/ + + return 1; /* Okay. */ +} + + + +static void +store_xoctal (char *buffer, size_t length, unsigned long long value) +{ + char *p, *pend; + size_t n; + unsigned long long v; + + log_assert (length > 1); + + v = value; + n = length; + p = pend = buffer + length; + *--p = 0; /* Nul byte. */ + n--; + do + { + *--p = '0' + (v % 8); + v /= 8; + n--; + } + while (v && n); + if (!v) + { + /* Pad. */ + for ( ; n; n--) + *--p = '0'; + } + else /* Does not fit into the field. Store as binary number. */ + { + v = value; + n = length; + p = pend = buffer + length; + do + { + *--p = v; + v /= 256; + n--; + } + while (v && n); + if (!v) + { + /* Pad. */ + for ( ; n; n--) + *--p = 0; + if (*p & 0x80) + BUG (); + *p |= 0x80; /* Set binary flag. */ + } + else + BUG (); + } +} + + +static void +store_uname (char *buffer, size_t length, unsigned long uid) +{ + static int initialized; + static unsigned long lastuid; + static char lastuname[32]; + + if (!initialized || uid != lastuid) + { +#ifdef HAVE_W32_SYSTEM + mem2str (lastuname, uid? "user":"root", sizeof lastuname); +#else + struct passwd *pw = getpwuid (uid); + + lastuid = uid; + initialized = 1; + if (pw) + mem2str (lastuname, pw->pw_name, sizeof lastuname); + else + { + log_info ("failed to get name for uid %lu\n", uid); + *lastuname = 0; + } +#endif + } + mem2str (buffer, lastuname, length); +} + + +static void +store_gname (char *buffer, size_t length, unsigned long gid) +{ + static int initialized; + static unsigned long lastgid; + static char lastgname[32]; + + if (!initialized || gid != lastgid) + { +#ifdef HAVE_W32_SYSTEM + mem2str (lastgname, gid? "users":"root", sizeof lastgname); +#else + struct group *gr = getgrgid (gid); + + lastgid = gid; + initialized = 1; + if (gr) + mem2str (lastgname, gr->gr_name, sizeof lastgname); + else + { + log_info ("failed to get name for gid %lu\n", gid); + *lastgname = 0; + } +#endif + } + mem2str (buffer, lastgname, length); +} + + +static void +compute_checksum (void *record) +{ + struct ustar_raw_header *raw = record; + unsigned long chksum = 0; + unsigned char *p; + size_t n; + + memset (raw->checksum, ' ', sizeof raw->checksum); + p = record; + for (n=0; n < RECORDSIZE; n++) + chksum += *p++; + store_xoctal (raw->checksum, sizeof raw->checksum - 1, chksum); + raw->checksum[7] = ' '; +} + + + +/* Read a symlink without truncating it. Caller must release the + * returned buffer. Returns NULL on error. */ +#ifndef HAVE_W32_SYSTEM +static char * +myreadlink (const char *name) +{ + char *buffer; + size_t size; + int nread; + + for (size = 1024; size <= 65536; size *= 2) + { + buffer = xtrymalloc (size); + if (!buffer) + return NULL; + + nread = readlink (name, buffer, size - 1); + if (nread < 0) + { + xfree (buffer); + return NULL; + } + if (nread < size - 1) + { + buffer[nread] = 0; + return buffer; /* Got it. */ + } + + xfree (buffer); + } + gpg_err_set_errno (ERANGE); + return NULL; +} +#endif /*Unix*/ + + + +/* Build a header. If the filename or the link name ist too long + * allocate an exthdr and use a replacement file name in RECORD. + * Caller should always release R_EXTHDR; this function initializes it + * to point to NULL. */ +static gpg_error_t +build_header (void *record, tar_header_t hdr, strlist_t *r_exthdr) +{ + gpg_error_t err; + struct ustar_raw_header *raw = record; + size_t namelen, n; + strlist_t sl; + + memset (record, 0, RECORDSIZE); + *r_exthdr = NULL; + + /* Store name and prefix. */ + namelen = strlen (hdr->name); + if (namelen < sizeof raw->name) + memcpy (raw->name, hdr->name, namelen); + else + { + n = (namelen < sizeof raw->prefix)? namelen : sizeof raw->prefix; + for (n--; n ; n--) + if (hdr->name[n] == '/') + break; + if (namelen - n < sizeof raw->name) + { + /* Note that the N is < sizeof prefix and that the + delimiting slash is not stored. */ + memcpy (raw->prefix, hdr->name, n); + memcpy (raw->name, hdr->name+n+1, namelen - n); + } + else + { + /* Too long - prepare extended header. */ + sl = add_to_strlist_try (r_exthdr, hdr->name); + if (!sl) + { + err = gpg_error_from_syserror (); + log_error ("error storing file '%s': %s\n", + hdr->name, gpg_strerror (err)); + return err; + } + sl->flags = 1; /* Mark as path */ + /* The name we use is not POSIX compliant but because we + * expect that (for security issues) a tarball will anyway + * be extracted to a unique new directory, a simple counter + * will do. To ease testing we also put in the PID. The + * count is bumped after the header has been written. */ + snprintf (raw->name, sizeof raw->name-1, "_@paxheader.%u.%lu", + (unsigned int)getpid(), global_header_count + 1); + } + } + + store_xoctal (raw->mode, sizeof raw->mode, hdr->mode); + store_xoctal (raw->uid, sizeof raw->uid, hdr->uid); + store_xoctal (raw->gid, sizeof raw->gid, hdr->gid); + store_xoctal (raw->size, sizeof raw->size, hdr->size); + store_xoctal (raw->mtime, sizeof raw->mtime, hdr->mtime); + + switch (hdr->typeflag) + { + case TF_REGULAR: raw->typeflag[0] = '0'; break; + case TF_HARDLINK: raw->typeflag[0] = '1'; break; + case TF_SYMLINK: raw->typeflag[0] = '2'; break; + case TF_CHARDEV: raw->typeflag[0] = '3'; break; + case TF_BLOCKDEV: raw->typeflag[0] = '4'; break; + case TF_DIRECTORY: raw->typeflag[0] = '5'; break; + case TF_FIFO: raw->typeflag[0] = '6'; break; + default: return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + memcpy (raw->magic, "ustar", 6); + raw->version[0] = '0'; + raw->version[1] = '0'; + + store_uname (raw->uname, sizeof raw->uname, hdr->uid); + store_gname (raw->gname, sizeof raw->gname, hdr->gid); + +#ifndef HAVE_W32_SYSTEM + if (hdr->typeflag == TF_SYMLINK) + { + int nread; + char *p; + + nread = readlink (hdr->name, raw->linkname, sizeof raw->linkname -1); + if (nread < 0) + { + err = gpg_error_from_syserror (); + log_error ("error reading symlink '%s': %s\n", + hdr->name, gpg_strerror (err)); + return err; + } + raw->linkname[nread] = 0; + if (nread == sizeof raw->linkname -1) + { + /* Truncated - read again and store as extended header. */ + p = myreadlink (hdr->name); + if (!p) + { + err = gpg_error_from_syserror (); + log_error ("error reading symlink '%s': %s\n", + hdr->name, gpg_strerror (err)); + return err; + } + + sl = add_to_strlist_try (r_exthdr, p); + xfree (p); + if (!sl) + { + err = gpg_error_from_syserror (); + log_error ("error storing syslink '%s': %s\n", + hdr->name, gpg_strerror (err)); + return err; + } + sl->flags = 2; /* Mark as linkpath */ + } + } +#endif /*!HAVE_W32_SYSTEM*/ + + compute_checksum (record); + + return 0; +} + + +/* Add an extended header record (NAME,VALUE) to the buffer MB. */ +static void +add_extended_header_record (membuf_t *mb, const char *name, const char *value) +{ + size_t n, n0, n1; + char numbuf[35]; + size_t valuelen; + + /* To avoid looping in most cases, we guess the initial value. */ + valuelen = strlen (value); + n1 = valuelen > 95? 3 : 2; + do + { + n0 = n1; + /* (3 for the space before name, the '=', and the LF.) */ + n = n0 + strlen (name) + valuelen + 3; + snprintf (numbuf, sizeof numbuf, "%zu", n); + n1 = strlen (numbuf); + } + while (n0 != n1); + put_membuf_str (mb, numbuf); + put_membuf (mb, " ", 1); + put_membuf_str (mb, name); + put_membuf (mb, "=", 1); + put_membuf (mb, value, valuelen); + put_membuf (mb, "\n", 1); +} + + + +/* Write the extended header specified by EXTHDR to STREAM. */ +static gpg_error_t +write_extended_header (estream_t stream, const void *record, strlist_t exthdr) +{ + gpg_error_t err = 0; + struct ustar_raw_header raw; + strlist_t sl; + membuf_t mb; + char *buffer, *p; + size_t buflen; + + init_membuf (&mb, 2*RECORDSIZE); + + for (sl=exthdr; sl; sl = sl->next) + { + if (sl->flags == 1) + add_extended_header_record (&mb, "path", sl->d); + else if (sl->flags == 2) + add_extended_header_record (&mb, "linkpath", sl->d); + } + + buffer = get_membuf (&mb, &buflen); + if (!buffer) + { + err = gpg_error_from_syserror (); + log_error ("error building extended header: %s\n", gpg_strerror (err)); + goto leave; + } + + /* We copy the header from the standard header record, so that an + * extracted extended header (using a non-pax aware software) is + * written with the same properties as the original file. The real + * entry will overwrite it anyway. Of course we adjust the size and + * the type. */ + memcpy (&raw, record, RECORDSIZE); + store_xoctal (raw.size, sizeof raw.size, buflen); + raw.typeflag[0] = 'x'; /* Mark as extended header. */ + compute_checksum (&raw); + + err = write_record (stream, &raw); + if (err) + goto leave; + + for (p = buffer; buflen >= RECORDSIZE; p += RECORDSIZE, buflen -= RECORDSIZE) + { + err = write_record (stream, p); + if (err) + goto leave; + } + if (buflen) + { + /* Reuse RAW for builidng the last record. */ + memcpy (&raw, p, buflen); + memset ((char*)&raw+buflen, 0, RECORDSIZE - buflen); + err = write_record (stream, &raw); + if (err) + goto leave; + } + + leave: + xfree (buffer); + return err; +} + + +static gpg_error_t +write_file (estream_t stream, tar_header_t hdr) +{ + gpg_error_t err; + char record[RECORDSIZE]; + estream_t infp; + size_t nread, nbytes; + strlist_t exthdr = NULL; + int any; + + err = build_header (record, hdr, &exthdr); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED) + { + log_info ("skipping unsupported file '%s'\n", hdr->name); + err = 0; + } + return err; + } + + if (hdr->typeflag == TF_REGULAR) + { + infp = es_fopen (hdr->name, "rb,sysopen"); + if (!infp) + { + err = gpg_error_from_syserror (); + log_error ("can't open '%s': %s - skipped\n", + hdr->name, gpg_strerror (err)); + return err; + } + } + else + infp = NULL; + + if (exthdr && (err = write_extended_header (stream, record, exthdr))) + goto leave; + err = write_record (stream, record); + if (err) + goto leave; + global_header_count++; + + if (hdr->typeflag == TF_REGULAR) + { + hdr->nrecords = (hdr->size + RECORDSIZE-1)/RECORDSIZE; + any = 0; + while (hdr->nrecords--) + { + nbytes = hdr->nrecords? RECORDSIZE : (hdr->size % RECORDSIZE); + if (!nbytes) + nbytes = RECORDSIZE; + nread = es_fread (record, 1, nbytes, infp); + if (nread != nbytes) + { + err = gpg_error_from_syserror (); + log_error ("error reading file '%s': %s%s\n", + hdr->name, gpg_strerror (err), + any? " (file shrunk?)":""); + goto leave; + } + else if (nbytes < RECORDSIZE) + memset (record + nbytes, 0, RECORDSIZE - nbytes); + any = 1; + err = write_record (stream, record); + if (err) + goto leave; + } + nread = es_fread (record, 1, 1, infp); + if (nread) + log_info ("note: file '%s' has grown\n", hdr->name); + } + + leave: + if (err) + es_fclose (infp); + else if ((err = es_fclose (infp))) + log_error ("error closing file '%s': %s\n", hdr->name, gpg_strerror (err)); + + free_strlist (exthdr); + return err; +} + + +static gpg_error_t +write_eof_mark (estream_t stream) +{ + gpg_error_t err; + char record[RECORDSIZE]; + + memset (record, 0, sizeof record); + err = write_record (stream, record); + if (!err) + err = write_record (stream, record); + return err; +} + + + +/* Create a new tarball using the names in the array INPATTERN. If + INPATTERN is NULL take the pattern as null terminated strings from + stdin or from the file specified by FILES_FROM. If NULL_NAMES is + set the filenames in such a file are delimited by a binary Nul and + not by a LF. */ +gpg_error_t +gpgtar_create (char **inpattern, const char *files_from, int null_names, + int encrypt, int sign) +{ + gpg_error_t err = 0; + struct scanctrl_s scanctrl_buffer; + scanctrl_t scanctrl = &scanctrl_buffer; + tar_header_t hdr, *start_tail; + estream_t files_from_stream = NULL; + estream_t outstream = NULL; + int eof_seen = 0; + pid_t pid = (pid_t)(-1); + + memset (scanctrl, 0, sizeof *scanctrl); + scanctrl->flist_tail = &scanctrl->flist; + + /* { unsigned int cpno, cpno2, cpno3; */ + + /* cpno = GetConsoleOutputCP (); */ + /* cpno2 = GetACP (); */ + /* cpno3 = GetOEMCP (); */ + /* log_debug ("Codepages: Console: %u ANSI: %u OEM: %u\n", */ + /* cpno, cpno2, cpno3); */ + /* } */ + + + if (!inpattern) + { + if (!files_from || !strcmp (files_from, "-")) + { + files_from = "-"; + files_from_stream = es_stdin; + if (null_names) + es_set_binary (es_stdin); + } + else if (!(files_from_stream=es_fopen (files_from, null_names? "rb":"r"))) + { + err = gpg_error_from_syserror (); + log_error ("error opening '%s': %s\n", + files_from, gpg_strerror (err)); + return err; + } + } + + + if (opt.directory && gnupg_chdir (opt.directory)) + { + err = gpg_error_from_syserror (); + log_error ("chdir to '%s' failed: %s\n", + opt.directory, gpg_strerror (err)); + return err; + } + + while (!eof_seen) + { + char *pat, *p; + int skip_this = 0; + + if (inpattern) + { + const char *pattern = *inpattern; + + if (!pattern) + break; /* End of array. */ + inpattern++; + + if (!*pattern) + continue; + + pat = name_to_utf8 (pattern, 0); + } + else /* Read Nul or LF delimited pattern from files_from_stream. */ + { + int c; + char namebuf[4096]; + size_t n = 0; + + for (;;) + { + if ((c = es_getc (files_from_stream)) == EOF) + { + if (es_ferror (files_from_stream)) + { + err = gpg_error_from_syserror (); + log_error ("error reading '%s': %s\n", + files_from, gpg_strerror (err)); + goto leave; + } + c = null_names ? 0 : '\n'; + eof_seen = 1; + } + if (n >= sizeof namebuf - 1) + { + if (!skip_this) + { + skip_this = 1; + log_error ("error reading '%s': %s\n", + files_from, "filename too long"); + } + } + else + namebuf[n++] = c; + + if (null_names) + { + if (!c) + { + namebuf[n] = 0; + break; + } + } + else /* Shall be LF delimited. */ + { + if (!c) + { + if (!skip_this) + { + skip_this = 1; + log_error ("error reading '%s': %s\n", + files_from, "filename with embedded Nul"); + } + } + else if ( c == '\n' ) + { + namebuf[n] = 0; + ascii_trim_spaces (namebuf); + n = strlen (namebuf); + break; + } + } + } + + if (skip_this || n < 2) + continue; + + pat = name_to_utf8 (namebuf, opt.utf8strings); + } + + if (!pat) + { + err = gpg_error_from_syserror (); + log_error ("memory allocation problem: %s\n", gpg_strerror (err)); + goto leave; + } + for (p=pat; *p; p++) + if (*p == '\\') + *p = '/'; + + if (opt.verbose > 1) + log_info ("scanning '%s'\n", pat); + + start_tail = scanctrl->flist_tail; + if (skip_this || !pattern_valid_p (pat)) + log_error ("skipping invalid name '%s'\n", pat); + else if (!add_entry (pat, NULL, scanctrl) + && *start_tail && ((*start_tail)->typeflag & TF_DIRECTORY)) + scan_recursive (pat, scanctrl); + + xfree (pat); + } + + if (files_from_stream && files_from_stream != es_stdin) + es_fclose (files_from_stream); + + if (encrypt || sign) + { + strlist_t arg; + ccparray_t ccp; + const char **argv; + + /* '--encrypt' may be combined with '--symmetric', but 'encrypt' + * is set either way. Clear it if no recipients are specified. + */ + if (opt.symmetric && opt.recipients == NULL) + encrypt = 0; + + ccparray_init (&ccp, 0); + if (opt.batch) + ccparray_put (&ccp, "--batch"); + if (opt.answer_yes) + ccparray_put (&ccp, "--yes"); + if (opt.answer_no) + ccparray_put (&ccp, "--no"); + if (opt.require_compliance) + ccparray_put (&ccp, "--require-compliance"); + if (opt.status_fd != -1) + { + static char tmpbuf[40]; + + snprintf (tmpbuf, sizeof tmpbuf, "--status-fd=%d", opt.status_fd); + ccparray_put (&ccp, tmpbuf); + } + + ccparray_put (&ccp, "--output"); + ccparray_put (&ccp, opt.outfile? opt.outfile : "-"); + if (encrypt) + ccparray_put (&ccp, "--encrypt"); + if (sign) + ccparray_put (&ccp, "--sign"); + if (opt.user) + { + ccparray_put (&ccp, "--local-user"); + ccparray_put (&ccp, opt.user); + } + if (opt.symmetric) + ccparray_put (&ccp, "--symmetric"); + for (arg = opt.recipients; arg; arg = arg->next) + { + ccparray_put (&ccp, "--recipient"); + ccparray_put (&ccp, arg->d); + } + for (arg = opt.gpg_arguments; arg; arg = arg->next) + ccparray_put (&ccp, arg->d); + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + + err = gnupg_spawn_process (opt.gpg_program, argv, NULL, NULL, + (GNUPG_SPAWN_KEEP_STDOUT + | GNUPG_SPAWN_KEEP_STDERR), + &outstream, NULL, NULL, &pid); + xfree (argv); + if (err) + goto leave; + es_set_binary (outstream); + } + else if (opt.outfile) /* No crypto */ + { + if (!strcmp (opt.outfile, "-")) + outstream = es_stdout; + else + outstream = es_fopen (opt.outfile, "wb,sysopen"); + if (!outstream) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (outstream == es_stdout) + es_set_binary (es_stdout); + + } + else /* Also no crypto. */ + { + outstream = es_stdout; + es_set_binary (outstream); + } + + + for (hdr = scanctrl->flist; hdr; hdr = hdr->next) + { + err = write_file (outstream, hdr); + if (err) + goto leave; + } + err = write_eof_mark (outstream); + if (err) + goto leave; + + + if (pid != (pid_t)(-1)) + { + int exitcode; + + err = es_fclose (outstream); + outstream = NULL; + if (err) + log_error ("error closing pipe: %s\n", gpg_strerror (err)); + else + { + err = gnupg_wait_process (opt.gpg_program, pid, 1, &exitcode); + if (err) + log_error ("running %s failed (exitcode=%d): %s", + opt.gpg_program, exitcode, gpg_strerror (err)); + gnupg_release_process (pid); + pid = (pid_t)(-1); + } + } + + leave: + if (!err) + { + gpg_error_t first_err; + if (outstream != es_stdout || pid != (pid_t)(-1)) + first_err = es_fclose (outstream); + else + first_err = es_fflush (outstream); + outstream = NULL; + if (! err) + err = first_err; + } + if (err) + { + log_error ("creating tarball '%s' failed: %s\n", + opt.outfile ? opt.outfile : "-", gpg_strerror (err)); + if (outstream && outstream != es_stdout) + es_fclose (outstream); + if (opt.outfile) + gnupg_remove (opt.outfile); + } + scanctrl->flist_tail = NULL; + while ( (hdr = scanctrl->flist) ) + { + scanctrl->flist = hdr->next; + xfree (hdr); + } + return err; +} diff --git a/tools/gpgtar-extract.c b/tools/gpgtar-extract.c new file mode 100644 index 0000000..832039b --- /dev/null +++ b/tools/gpgtar-extract.c @@ -0,0 +1,485 @@ +/* gpgtar-extract.c - Extract from a TAR archive + * Copyright (C) 2016-2017, 2019-2022 g10 Code GmbH + * Copyright (C) 2010, 2012, 2013 Werner Koch + * Copyright (C) 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "../common/i18n.h" +#include <gpg-error.h> +#include "../common/exechelp.h" +#include "../common/sysutils.h" +#include "../common/ccparray.h" +#include "gpgtar.h" + +static gpg_error_t +check_suspicious_name (const char *name) +{ + size_t n; + + n = strlen (name); +#ifdef HAVE_DOSISH_SYSTEM + if (strchr (name, '\\')) + { + log_error ("filename '%s' contains a backslash - " + "can't extract on this system\n", name); + return gpg_error (GPG_ERR_INV_NAME); + } +#endif /*HAVE_DOSISH_SYSTEM*/ + + if (!n + || strstr (name, "//") + || strstr (name, "/../") + || !strncmp (name, "../", 3) + || (n >= 3 && !strcmp (name+n-3, "/.." ))) + { + log_error ("filename '%s' has suspicious parts - not extracting\n", + name); + return gpg_error (GPG_ERR_INV_NAME); + } + + return 0; +} + + +static gpg_error_t +extract_regular (estream_t stream, const char *dirname, + tarinfo_t info, tar_header_t hdr, strlist_t exthdr) +{ + gpg_error_t err; + char record[RECORDSIZE]; + size_t n, nbytes, nwritten; + char *fname_buffer = NULL; + const char *fname; + estream_t outfp = NULL; + strlist_t sl; + + fname = hdr->name; + for (sl = exthdr; sl; sl = sl->next) + if (sl->flags == 1) + fname = sl->d; + + err = check_suspicious_name (fname); + if (err) + goto leave; + + fname_buffer = strconcat (dirname, "/", fname, NULL); + if (!fname_buffer) + { + err = gpg_error_from_syserror (); + log_error ("error creating filename: %s\n", gpg_strerror (err)); + goto leave; + } + fname = fname_buffer; + + + if (opt.dry_run) + outfp = es_fopen ("/dev/null", "wb"); + else + outfp = es_fopen (fname, "wb,sysopen"); + if (!outfp) + { + err = gpg_error_from_syserror (); + log_error ("error creating '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + + for (n=0; n < hdr->nrecords;) + { + err = read_record (stream, record); + if (err) + goto leave; + info->nblocks++; + n++; + if (n < hdr->nrecords || (hdr->size && !(hdr->size % RECORDSIZE))) + nbytes = RECORDSIZE; + else + nbytes = (hdr->size % RECORDSIZE); + + nwritten = es_fwrite (record, 1, nbytes, outfp); + if (nwritten != nbytes) + { + err = gpg_error_from_syserror (); + log_error ("error writing '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + } + /* Fixme: Set permissions etc. */ + + leave: + if (!err && opt.verbose) + log_info ("extracted '%s'\n", fname); + es_fclose (outfp); + if (err && fname && outfp) + { + if (gnupg_remove (fname)) + log_error ("error removing incomplete file '%s': %s\n", + fname, gpg_strerror (gpg_error_from_syserror ())); + } + xfree (fname_buffer); + return err; +} + + +static gpg_error_t +extract_directory (const char *dirname, tar_header_t hdr, strlist_t exthdr) +{ + gpg_error_t err; + const char *name; + char *fname = NULL; + strlist_t sl; + + name = hdr->name; + for (sl = exthdr; sl; sl = sl->next) + if (sl->flags == 1) + name = sl->d; + + err = check_suspicious_name (name); + if (err) + goto leave; + + fname = strconcat (dirname, "/", name, NULL); + if (!fname) + { + err = gpg_error_from_syserror (); + log_error ("error creating filename: %s\n", gpg_strerror (err)); + goto leave; + } + /* Remove a possible trailing slash. */ + if (fname[strlen (fname)-1] == '/') + fname[strlen (fname)-1] = 0; + + if (! opt.dry_run && gnupg_mkdir (fname, "-rwx------")) + { + err = gpg_error_from_syserror (); + if (gpg_err_code (err) == GPG_ERR_EEXIST) + { + /* Ignore existing directories while extracting. */ + err = 0; + } + + if (gpg_err_code (err) == GPG_ERR_ENOENT) + { + /* Try to create the directory with parents but keep the + original error code in case of a failure. */ + int rc = 0; + char *p; + size_t prefixlen; + + /* (PREFIXLEN is the length of the new directory we use to + * extract the tarball.) */ + prefixlen = strlen (dirname) + 1; + + for (p = fname+prefixlen; (p = strchr (p, '/')); p++) + { + *p = 0; + rc = gnupg_mkdir (fname, "-rwx------"); + *p = '/'; + if (rc) + break; + } + if (!rc && !gnupg_mkdir (fname, "-rwx------")) + err = 0; + } + if (err) + log_error ("error creating directory '%s': %s\n", + fname, gpg_strerror (err)); + } + + leave: + if (!err && opt.verbose) + log_info ("created '%s/'\n", fname); + xfree (fname); + return err; +} + + +static gpg_error_t +extract (estream_t stream, const char *dirname, tarinfo_t info, + tar_header_t hdr, strlist_t exthdr) +{ + gpg_error_t err; + size_t n; + + if (hdr->typeflag == TF_REGULAR || hdr->typeflag == TF_UNKNOWN) + err = extract_regular (stream, dirname, info, hdr, exthdr); + else if (hdr->typeflag == TF_DIRECTORY) + err = extract_directory (dirname, hdr, exthdr); + else + { + char record[RECORDSIZE]; + + log_info ("unsupported file type %d for '%s' - skipped\n", + (int)hdr->typeflag, hdr->name); + for (err = 0, n=0; !err && n < hdr->nrecords; n++) + { + err = read_record (stream, record); + if (!err) + info->nblocks++; + } + } + return err; +} + + +/* Create a new directory to be used for extracting the tarball. + Returns the name of the directory which must be freed by the + caller. In case of an error a diagnostic is printed and NULL + returned. */ +static char * +create_directory (const char *dirprefix) +{ + gpg_error_t err = 0; + char *prefix_buffer = NULL; + char *dirname = NULL; + size_t n; + int idx; + + /* Remove common suffixes. */ + n = strlen (dirprefix); + if (n > 4 && (!compare_filenames (dirprefix + n - 4, EXTSEP_S GPGEXT_GPG) + || !compare_filenames (dirprefix + n - 4, EXTSEP_S "pgp") + || !compare_filenames (dirprefix + n - 4, EXTSEP_S "asc") + || !compare_filenames (dirprefix + n - 4, EXTSEP_S "pem") + || !compare_filenames (dirprefix + n - 4, EXTSEP_S "p7m") + || !compare_filenames (dirprefix + n - 4, EXTSEP_S "p7e"))) + { + prefix_buffer = xtrystrdup (dirprefix); + if (!prefix_buffer) + { + err = gpg_error_from_syserror (); + goto leave; + } + prefix_buffer[n-4] = 0; + dirprefix = prefix_buffer; + } + + + + for (idx=1; idx < 5000; idx++) + { + xfree (dirname); + dirname = xtryasprintf ("%s_%d_", dirprefix, idx); + if (!dirname) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (!gnupg_mkdir (dirname, "-rwx------")) + goto leave; /* Ready. */ + if (errno != EEXIST && errno != ENOTDIR) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + err = gpg_error (GPG_ERR_LIMIT_REACHED); + + leave: + if (err) + { + log_error ("error creating an extract directory: %s\n", + gpg_strerror (err)); + xfree (dirname); + dirname = NULL; + } + xfree (prefix_buffer); + return dirname; +} + + + +gpg_error_t +gpgtar_extract (const char *filename, int decrypt) +{ + gpg_error_t err; + estream_t stream = NULL; + tar_header_t header = NULL; + strlist_t extheader = NULL; + const char *dirprefix = NULL; + char *dirname = NULL; + struct tarinfo_s tarinfo_buffer; + tarinfo_t tarinfo = &tarinfo_buffer; + pid_t pid = (pid_t)(-1); + char *logfilename = NULL; + + + memset (&tarinfo_buffer, 0, sizeof tarinfo_buffer); + + if (opt.directory) + dirname = xtrystrdup (opt.directory); + else + { + if (opt.filename) + { + dirprefix = strrchr (opt.filename, '/'); + if (dirprefix) + dirprefix++; + else + dirprefix = opt.filename; + } + else if (filename) + { + dirprefix = strrchr (filename, '/'); + if (dirprefix) + dirprefix++; + else + dirprefix = filename; + } + + if (!dirprefix || !*dirprefix) + dirprefix = "GPGARCH"; + + dirname = create_directory (dirprefix); + if (!dirname) + { + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + } + + if (opt.verbose) + log_info ("extracting to '%s/'\n", dirname); + + if (decrypt) + { + strlist_t arg; + ccparray_t ccp; + const char **argv; + + ccparray_init (&ccp, 0); + if (opt.batch) + ccparray_put (&ccp, "--batch"); + if (opt.require_compliance) + ccparray_put (&ccp, "--require-compliance"); + if (opt.status_fd != -1) + { + static char tmpbuf[40]; + + snprintf (tmpbuf, sizeof tmpbuf, "--status-fd=%d", opt.status_fd); + ccparray_put (&ccp, tmpbuf); + } + if (opt.with_log) + { + ccparray_put (&ccp, "--log-file"); + logfilename = xstrconcat (dirname, ".log", NULL); + ccparray_put (&ccp, logfilename); + } + ccparray_put (&ccp, "--output"); + ccparray_put (&ccp, "-"); + ccparray_put (&ccp, "--decrypt"); + for (arg = opt.gpg_arguments; arg; arg = arg->next) + ccparray_put (&ccp, arg->d); + if (filename) + { + ccparray_put (&ccp, "--"); + ccparray_put (&ccp, filename); + } + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + + err = gnupg_spawn_process (opt.gpg_program, argv, NULL, NULL, + ((filename? 0 : GNUPG_SPAWN_KEEP_STDIN) + | GNUPG_SPAWN_KEEP_STDERR), + NULL, &stream, NULL, &pid); + xfree (argv); + if (err) + goto leave; + es_set_binary (stream); + } + else if (filename) + { + if (!strcmp (filename, "-")) + stream = es_stdin; + else + stream = es_fopen (filename, "rb,sysopen"); + if (!stream) + { + err = gpg_error_from_syserror (); + log_error ("error opening '%s': %s\n", filename, gpg_strerror (err)); + return err; + } + if (stream == es_stdin) + es_set_binary (es_stdin); + } + else + { + stream = es_stdin; + es_set_binary (es_stdin); + } + + + for (;;) + { + err = gpgtar_read_header (stream, tarinfo, &header, &extheader); + if (err || header == NULL) + goto leave; + + err = extract (stream, dirname, tarinfo, header, extheader); + if (err) + goto leave; + free_strlist (extheader); + extheader = NULL; + xfree (header); + header = NULL; + } + + if (pid != (pid_t)(-1)) + { + int exitcode; + + err = es_fclose (stream); + stream = NULL; + if (err) + log_error ("error closing pipe: %s\n", gpg_strerror (err)); + else + { + err = gnupg_wait_process (opt.gpg_program, pid, 1, &exitcode); + if (err) + log_error ("running %s failed (exitcode=%d): %s", + opt.gpg_program, exitcode, gpg_strerror (err)); + gnupg_release_process (pid); + pid = (pid_t)(-1); + } + } + + + leave: + free_strlist (extheader); + xfree (header); + xfree (dirname); + xfree (logfilename); + if (stream != es_stdin) + es_fclose (stream); + return err; +} diff --git a/tools/gpgtar-list.c b/tools/gpgtar-list.c new file mode 100644 index 0000000..08ab967 --- /dev/null +++ b/tools/gpgtar-list.c @@ -0,0 +1,590 @@ +/* gpgtar-list.c - List a TAR archive + * Copyright (C) 2016-2017, 2019-2022 g10 Code GmbH + * Copyright (C) 2010, 2012, 2013 Werner Koch + * Copyright (C) 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/i18n.h" +#include <gpg-error.h> +#include "gpgtar.h" +#include "../common/exechelp.h" +#include "../common/sysutils.h" +#include "../common/ccparray.h" + + + +static unsigned long long +parse_xoctal (const void *data, size_t length, const char *filename) +{ + const unsigned char *p = data; + unsigned long long value; + + if (!length) + value = 0; + else if ( (*p & 0x80)) + { + /* Binary format. */ + value = (*p++ & 0x7f); + while (--length) + { + value <<= 8; + value |= *p++; + } + } + else + { + /* Octal format */ + value = 0; + /* Skip leading spaces and zeroes. */ + for (; length && (*p == ' ' || *p == '0'); length--, p++) + ; + for (; length && *p; length--, p++) + { + if (*p >= '0' && *p <= '7') + { + value <<= 3; + value += (*p - '0'); + } + else + { + log_error ("%s: invalid octal number encountered - assuming 0\n", + filename); + value = 0; + break; + } + } + } + return value; +} + + +static tar_header_t +parse_header (const void *record, const char *filename, tarinfo_t info) +{ + const struct ustar_raw_header *raw = record; + size_t n, namelen, prefixlen; + tar_header_t header; + int use_prefix; + int anyerror = 0; + + info->headerblock = info->nblocks - 1; + + use_prefix = (!memcmp (raw->magic, "ustar", 5) + && (raw->magic[5] == ' ' || !raw->magic[5])); + + + for (namelen=0; namelen < sizeof raw->name && raw->name[namelen]; namelen++) + ; + if (namelen == sizeof raw->name) + { + log_info ("%s: warning: name not terminated by a nul\n", filename); + anyerror = 1; + } + for (n=namelen+1; n < sizeof raw->name; n++) + if (raw->name[n]) + { + log_info ("%s: warning: garbage after name\n", filename); + anyerror = 1; + break; + } + + if (use_prefix && raw->prefix[0]) + { + for (prefixlen=0; (prefixlen < sizeof raw->prefix + && raw->prefix[prefixlen]); prefixlen++) + ; + if (prefixlen == sizeof raw->prefix) + log_info ("%s: warning: prefix not terminated by a nul (block %llu)\n", + filename, info->headerblock); + for (n=prefixlen+1; n < sizeof raw->prefix; n++) + if (raw->prefix[n]) + { + log_info ("%s: warning: garbage after prefix\n", filename); + anyerror = 1; + break; + } + } + else + prefixlen = 0; + + header = xtrycalloc (1, sizeof *header + prefixlen + 1 + namelen); + if (!header) + { + log_error ("%s: error allocating header: %s\n", + filename, gpg_strerror (gpg_error_from_syserror ())); + return NULL; + } + if (prefixlen) + { + n = prefixlen; + memcpy (header->name, raw->prefix, n); + if (raw->prefix[n-1] != '/') + header->name[n++] = '/'; + } + else + n = 0; + memcpy (header->name+n, raw->name, namelen); + header->name[n+namelen] = 0; + + header->mode = parse_xoctal (raw->mode, sizeof raw->mode, filename); + header->uid = parse_xoctal (raw->uid, sizeof raw->uid, filename); + header->gid = parse_xoctal (raw->gid, sizeof raw->gid, filename); + header->size = parse_xoctal (raw->size, sizeof raw->size, filename); + header->mtime = parse_xoctal (raw->mtime, sizeof raw->mtime, filename); + /* checksum = */ + switch (raw->typeflag[0]) + { + case '0': header->typeflag = TF_REGULAR; break; + case '1': header->typeflag = TF_HARDLINK; break; + case '2': header->typeflag = TF_SYMLINK; break; + case '3': header->typeflag = TF_CHARDEV; break; + case '4': header->typeflag = TF_BLOCKDEV; break; + case '5': header->typeflag = TF_DIRECTORY; break; + case '6': header->typeflag = TF_FIFO; break; + case '7': header->typeflag = TF_RESERVED; break; + case 'g': header->typeflag = TF_GEXTHDR; break; + case 'x': header->typeflag = TF_EXTHDR; break; + default: header->typeflag = TF_UNKNOWN; break; + } + + /* Compute the number of data records following this header. */ + if (header->typeflag == TF_REGULAR + || header->typeflag == TF_EXTHDR + || header->typeflag == TF_UNKNOWN) + header->nrecords = (header->size + RECORDSIZE-1)/RECORDSIZE; + else + header->nrecords = 0; + + if (anyerror) + { + log_info ("%s: header block %llu is corrupt" + " (size=%llu type=%d nrec=%llu)\n", + filename, info->headerblock, + header->size, header->typeflag, header->nrecords); + /* log_printhex (record, RECORDSIZE, " "); */ + } + + return header; +} + +/* Parse the extended header. This funcion may modify BUFFER. */ +static gpg_error_t +parse_extended_header (const char *fname, + char *buffer, size_t buflen, strlist_t *r_exthdr) +{ + unsigned int reclen; + unsigned char *p, *record; + strlist_t sl; + + while (buflen) + { + record = buffer; /* Remember begin of record. */ + reclen = 0; + for (p = buffer; buflen && digitp (p); buflen--, p++) + { + reclen *= 10; + reclen += (*p - '0'); + } + if (!buflen || *p != ' ') + { + log_error ("%s: malformed record length in extended header\n", fname); + return gpg_error (GPG_ERR_INV_RECORD); + } + p++; /* Skip space. */ + buflen--; + if (buflen + (p-record) < reclen) + { + log_error ("%s: extended header record larger" + " than total extended header data\n", fname); + return gpg_error (GPG_ERR_INV_RECORD); + } + if (reclen < (p-record)+2 || record[reclen-1] != '\n') + { + log_error ("%s: malformed extended header record\n", fname); + return gpg_error (GPG_ERR_INV_RECORD); + } + record[reclen-1] = 0; /* For convenience change LF to a Nul. */ + reclen -= (p-record); + /* P points to the begin of the keyword and RECLEN is the + * remaining length of the record excluding the LF. */ + if (memchr (p, 0, reclen-1) + && (!strncmp (p, "path=", 5) || !strncmp (p, "linkpath=", 9))) + { + log_error ("%s: extended header record has an embedded nul" + " - ignoring\n", fname); + } + else if (!strncmp (p, "path=", 5)) + { + sl = add_to_strlist_try (r_exthdr, p+5); + if (!sl) + return gpg_error_from_syserror (); + sl->flags = 1; /* Mark as path */ + } + else if (!strncmp (p, "linkpath=", 9)) + { + sl = add_to_strlist_try (r_exthdr, p+9); + if (!sl) + return gpg_error_from_syserror (); + sl->flags = 2; /* Mark as linkpath */ + } + + buffer = p + reclen; + buflen -= reclen; + } + + return 0; +} + + +/* Read the next block, assuming it is a tar header. Returns a header + * object on success in R_HEADER, or an error. If the stream is + * consumed (i.e. end-of-archive), R_HEADER is set to NULL. In case + * of an error an error message is printed. If the header is an + * extended header, a string list is allocated and stored at + * R_EXTHEADER; the caller should provide a pointer to NULL. Such an + * extended header is fully processed here and the returned R_HEADER + * has then the next regular header. */ +static gpg_error_t +read_header (estream_t stream, tarinfo_t info, + tar_header_t *r_header, strlist_t *r_extheader) +{ + gpg_error_t err; + char record[RECORDSIZE]; + int i; + tar_header_t hdr; + char *buffer; + size_t buflen, nrec; + + err = read_record (stream, record); + if (err) + return err; + info->nblocks++; + + for (i=0; i < RECORDSIZE && !record[i]; i++) + ; + if (i == RECORDSIZE) + { + /* All zero header - check whether it is the first part of an + end of archive mark. */ + err = read_record (stream, record); + if (err) + return err; + info->nblocks++; + + for (i=0; i < RECORDSIZE && !record[i]; i++) + ; + if (i != RECORDSIZE) + log_info ("%s: warning: skipping empty header\n", + es_fname_get (stream)); + else + { + /* End of archive - FIXME: we might want to check for garbage. */ + *r_header = NULL; + return 0; + } + } + + *r_header = parse_header (record, es_fname_get (stream), info); + if (!*r_header) + return gpg_error_from_syserror (); + hdr = *r_header; + + if (hdr->typeflag != TF_EXTHDR || !r_extheader) + return 0; + + /* Read the extended header. */ + if (!hdr->nrecords) + { + /* More than 64k for an extedned header is surely too large. */ + log_info ("%s: warning: empty extended header\n", + es_fname_get (stream)); + return 0; + } + if (hdr->nrecords > 65536 / RECORDSIZE) + { + /* More than 64k for an extedned header is surely too large. */ + log_error ("%s: extended header too large - skipping\n", + es_fname_get (stream)); + return 0; + } + + buffer = xtrymalloc (hdr->nrecords * RECORDSIZE); + if (!buffer) + { + err = gpg_error_from_syserror (); + log_error ("%s: error allocating space for extended header: %s\n", + es_fname_get (stream), gpg_strerror (err)); + return err; + } + buflen = 0; + + for (nrec=0; nrec < hdr->nrecords;) + { + err = read_record (stream, buffer + buflen); + if (err) + { + xfree (buffer); + return err; + } + info->nblocks++; + nrec++; + if (nrec < hdr->nrecords || (hdr->size && !(hdr->size % RECORDSIZE))) + buflen += RECORDSIZE; + else + buflen += (hdr->size % RECORDSIZE); + } + + err = parse_extended_header (es_fname_get (stream), + buffer, buflen, r_extheader); + if (err) + { + free_strlist (*r_extheader); + *r_extheader = NULL; + } + + xfree (buffer); + /* Now tha the extedned header has been read, we read the next + * header without allowing an extended header. */ + return read_header (stream, info, r_header, NULL); +} + + +/* Skip the data records according to HEADER. Prints an error message + on error and return -1. */ +static int +skip_data (estream_t stream, tarinfo_t info, tar_header_t header) +{ + char record[RECORDSIZE]; + unsigned long long n; + + for (n=0; n < header->nrecords; n++) + { + if (read_record (stream, record)) + return -1; + info->nblocks++; + } + + return 0; +} + + + +static void +print_header (tar_header_t header, strlist_t extheader, estream_t out) +{ + unsigned long mask; + char modestr[10+1]; + int i; + strlist_t sl; + const char *name, *linkname; + + *modestr = '?'; + switch (header->typeflag) + { + case TF_REGULAR: *modestr = '-'; break; + case TF_HARDLINK: *modestr = 'h'; break; + case TF_SYMLINK: *modestr = 'l'; break; + case TF_CHARDEV: *modestr = 'c'; break; + case TF_BLOCKDEV: *modestr = 'b'; break; + case TF_DIRECTORY:*modestr = 'd'; break; + case TF_FIFO: *modestr = 'f'; break; + case TF_RESERVED: *modestr = '='; break; + case TF_EXTHDR: break; + case TF_GEXTHDR: break; + case TF_UNKNOWN: break; + case TF_NOTSUP: break; + } + for (mask = 0400, i = 0; i < 9; i++, mask >>= 1) + modestr[1+i] = (header->mode & mask)? "rwxrwxrwx"[i]:'-'; + if ((header->typeflag & 04000)) + modestr[3] = modestr[3] == 'x'? 's':'S'; + if ((header->typeflag & 02000)) + modestr[6] = modestr[6] == 'x'? 's':'S'; + if ((header->typeflag & 01000)) + modestr[9] = modestr[9] == 'x'? 't':'T'; + modestr[10] = 0; + + /* FIXME: We do not parse the linkname unless its part of an + * extended header. */ + name = header->name; + linkname = header->typeflag == TF_SYMLINK? "?" : NULL; + + for (sl = extheader; sl; sl = sl->next) + { + if (sl->flags == 1) + name = sl->d; + else if (sl->flags == 2) + linkname = sl->d; + } + + es_fprintf (out, "%s %lu %lu/%lu %12llu %s %s%s%s\n", + modestr, header->nlink, header->uid, header->gid, header->size, + isotimestamp (header->mtime), + name, + linkname? " -> " : "", + linkname? linkname : ""); +} + + + +/* List the tarball FILENAME or, if FILENAME is NULL, the tarball read + from stdin. */ +gpg_error_t +gpgtar_list (const char *filename, int decrypt) +{ + gpg_error_t err; + estream_t stream = NULL; + tar_header_t header = NULL; + strlist_t extheader = NULL; + struct tarinfo_s tarinfo_buffer; + tarinfo_t tarinfo = &tarinfo_buffer; + pid_t pid = (pid_t)(-1); + + memset (&tarinfo_buffer, 0, sizeof tarinfo_buffer); + + if (decrypt) + { + strlist_t arg; + ccparray_t ccp; + const char **argv; + + ccparray_init (&ccp, 0); + if (opt.batch) + ccparray_put (&ccp, "--batch"); + if (opt.require_compliance) + ccparray_put (&ccp, "--require-compliance"); + if (opt.status_fd != -1) + { + static char tmpbuf[40]; + + snprintf (tmpbuf, sizeof tmpbuf, "--status-fd=%d", opt.status_fd); + ccparray_put (&ccp, tmpbuf); + } + ccparray_put (&ccp, "--output"); + ccparray_put (&ccp, "-"); + ccparray_put (&ccp, "--decrypt"); + for (arg = opt.gpg_arguments; arg; arg = arg->next) + ccparray_put (&ccp, arg->d); + if (filename) + { + ccparray_put (&ccp, "--"); + ccparray_put (&ccp, filename); + } + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + + err = gnupg_spawn_process (opt.gpg_program, argv, NULL, NULL, + ((filename? 0 : GNUPG_SPAWN_KEEP_STDIN) + | GNUPG_SPAWN_KEEP_STDERR), + NULL, &stream, NULL, &pid); + xfree (argv); + if (err) + goto leave; + es_set_binary (stream); + } + else if (filename) /* No decryption requested. */ + { + if (!strcmp (filename, "-")) + stream = es_stdin; + else + stream = es_fopen (filename, "rb,sysopen"); + if (!stream) + { + err = gpg_error_from_syserror (); + log_error ("error opening '%s': %s\n", filename, gpg_strerror (err)); + goto leave; + } + if (stream == es_stdin) + es_set_binary (es_stdin); + } + else + { + stream = es_stdin; + es_set_binary (es_stdin); + } + + for (;;) + { + err = read_header (stream, tarinfo, &header, &extheader); + if (err || header == NULL) + goto leave; + + print_header (header, extheader, es_stdout); + + if (skip_data (stream, tarinfo, header)) + goto leave; + free_strlist (extheader); + extheader = NULL; + xfree (header); + header = NULL; + } + + if (pid != (pid_t)(-1)) + { + int exitcode; + + err = es_fclose (stream); + stream = NULL; + if (err) + log_error ("error closing pipe: %s\n", gpg_strerror (err)); + else + { + err = gnupg_wait_process (opt.gpg_program, pid, 1, &exitcode); + if (err) + log_error ("running %s failed (exitcode=%d): %s", + opt.gpg_program, exitcode, gpg_strerror (err)); + gnupg_release_process (pid); + pid = (pid_t)(-1); + } + } + + leave: + free_strlist (extheader); + xfree (header); + if (stream != es_stdin) + es_fclose (stream); + return err; +} + + +gpg_error_t +gpgtar_read_header (estream_t stream, tarinfo_t info, + tar_header_t *r_header, strlist_t *r_extheader) +{ + return read_header (stream, info, r_header, r_extheader); +} + +void +gpgtar_print_header (tar_header_t header, strlist_t extheader, estream_t out) +{ + if (header && out) + print_header (header, extheader, out); +} diff --git a/tools/gpgtar-w32info.rc b/tools/gpgtar-w32info.rc new file mode 100644 index 0000000..211528b --- /dev/null +++ b/tools/gpgtar-w32info.rc @@ -0,0 +1,52 @@ +/* gpgtar-w32info.rc -*- c -*- + * Copyright (C) 2020 g10 Code GmbH + * + * This file is free software; as a special exception the author gives + * unlimited permission to copy and/or distribute it, with or without + * modifications, as long as this notice is preserved. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "afxres.h" +#include "../common/w32info-rc.h" + +1 ICON "../common/gnupg.ico" + +1 VERSIONINFO + FILEVERSION W32INFO_VI_FILEVERSION + PRODUCTVERSION W32INFO_VI_PRODUCTVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ + FILETYPE 0x1L /* VFT_APP (0x1) */ + FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ + BEGIN + VALUE "FileDescription", L"GnuPG\x2019s archive tool\0" + VALUE "InternalName", "gpgtar\0" + VALUE "OriginalFilename", "gpgtar.exe\0" + VALUE "ProductName", W32INFO_PRODUCTNAME + VALUE "ProductVersion", W32INFO_PRODUCTVERSION + VALUE "CompanyName", W32INFO_COMPANYNAME + VALUE "FileVersion", W32INFO_FILEVERSION + VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT + VALUE "Comments", W32INFO_COMMENTS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END + END + +1 RT_MANIFEST "gpgtar.w32-manifest" diff --git a/tools/gpgtar.c b/tools/gpgtar.c new file mode 100644 index 0000000..e86ed32 --- /dev/null +++ b/tools/gpgtar.c @@ -0,0 +1,638 @@ +/* gpgtar.c - A simple TAR implementation mainly useful for Windows. + * Copyright (C) 2010 Free Software Foundation, Inc. + * Copyright (C) 2020 g10 Code GmbH + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/* GnuPG comes with a shell script gpg-zip which creates archive files + in the same format as PGP Zip, which is actually a USTAR format. + That is fine and works nicely on all Unices but for Windows we + don't have a compatible shell and the supply of tar programs is + limited. Given that we need just a few tar option and it is an + open question how many Unix concepts are to be mapped to Windows, + we might as well write our own little tar customized for use with + gpg. So here we go. */ + +#include <config.h> +#include <ctype.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define INCLUDED_BY_MAIN_MODULE 1 +#include "../common/util.h" +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "../common/openpgpdefs.h" +#include "../common/init.h" +#include "../common/strlist.h" + +#include "gpgtar.h" + + +/* Constants to identify the commands and options. */ +enum cmd_and_opt_values + { + aNull = 0, + aCreate = 600, + aExtract, + aEncrypt = 'e', + aDecrypt = 'd', + aSign = 's', + aList = 't', + + oSymmetric = 'c', + oRecipient = 'r', + oUser = 'u', + oOutput = 'o', + oDirectory = 'C', + oQuiet = 'q', + oVerbose = 'v', + oFilesFrom = 'T', + oNoVerbose = 500, + + aSignEncrypt, + oGpgProgram, + oSkipCrypto, + oOpenPGP, + oCMS, + oSetFilename, + oNull, + oUtf8Strings, + + oBatch, + oAnswerYes, + oAnswerNo, + oStatusFD, + oRequireCompliance, + oWithLog, + + /* Compatibility with gpg-zip. */ + oGpgArgs, + oTarArgs, + + /* Debugging. */ + oDryRun + }; + + +/* The list of commands and options. */ +static ARGPARSE_OPTS opts[] = { + ARGPARSE_group (300, N_("@Commands:\n ")), + + ARGPARSE_c (aCreate, "create", N_("create an archive")), + ARGPARSE_c (aExtract, "extract", N_("extract an archive")), + ARGPARSE_c (aEncrypt, "encrypt", N_("create an encrypted archive")), + ARGPARSE_c (aDecrypt, "decrypt", N_("extract an encrypted archive")), + ARGPARSE_c (aSign, "sign", N_("create a signed archive")), + ARGPARSE_c (aList, "list-archive", N_("list an archive")), + + ARGPARSE_group (301, N_("@\nOptions:\n ")), + + ARGPARSE_s_n (oSymmetric, "symmetric", N_("use symmetric encryption")), + ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")), + ARGPARSE_s_s (oUser, "local-user", + N_("|USER-ID|use USER-ID to sign or decrypt")), + ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")), + ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), + ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), + ARGPARSE_s_s (oGpgProgram, "gpg", "@"), + ARGPARSE_s_n (oSkipCrypto, "skip-crypto", N_("skip the crypto processing")), + ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")), + ARGPARSE_s_s (oSetFilename, "set-filename", "@"), + ARGPARSE_s_n (oOpenPGP, "openpgp", "@"), + ARGPARSE_s_n (oCMS, "cms", "@"), + + ARGPARSE_s_n (oBatch, "batch", "@"), + ARGPARSE_s_n (oAnswerYes, "yes", "@"), + ARGPARSE_s_n (oAnswerNo, "no", "@"), + ARGPARSE_s_i (oStatusFD, "status-fd", "@"), + ARGPARSE_s_n (oRequireCompliance, "require-compliance", "@"), + ARGPARSE_s_n (oWithLog, "with-log", "@"), + + ARGPARSE_group (302, N_("@\nTar options:\n ")), + + ARGPARSE_s_s (oDirectory, "directory", + N_("|DIRECTORY|change to DIRECTORY first")), + ARGPARSE_s_s (oFilesFrom, "files-from", + N_("|FILE|get names to create from FILE")), + ARGPARSE_s_n (oNull, "null", N_("-T reads null-terminated names")), +#ifdef HAVE_W32_SYSTEM + ARGPARSE_s_n (oUtf8Strings, "utf8-strings", + N_("-T reads UTF-8 encoded names")), +#else + ARGPARSE_s_n (oUtf8Strings, "utf8-strings", "@"), +#endif + + ARGPARSE_s_s (oGpgArgs, "gpg-args", "@"), + ARGPARSE_s_s (oTarArgs, "tar-args", "@"), + + ARGPARSE_end () +}; + + +/* The list of commands and options for tar that we understand. */ +static ARGPARSE_OPTS tar_opts[] = { + ARGPARSE_s_s (oDirectory, "directory", + N_("|DIRECTORY|extract files into DIRECTORY")), + ARGPARSE_s_s (oFilesFrom, "files-from", + N_("|FILE|get names to create from FILE")), + ARGPARSE_s_n (oNull, "null", N_("-T reads null-terminated names")), + + ARGPARSE_end () +}; + + +/* Global flags. */ +static enum cmd_and_opt_values cmd = 0; +static int skip_crypto = 0; +static const char *files_from = NULL; +static int null_names = 0; + + + + +/* Print usage information and provide strings for help. */ +static const char * +my_strusage( int level ) +{ + const char *p; + + switch (level) + { + case 9: p = "GPL-3.0-or-later"; break; + case 11: p = "@GPGTAR@ (@GNUPG@)"; + break; + case 13: p = VERSION; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; + + case 1: + case 40: + p = _("Usage: gpgtar [options] [files] [directories] (-h for help)"); + break; + case 41: + p = _("Syntax: gpgtar [options] [files] [directories]\n" + "Encrypt or sign files into an archive\n"); + break; + + default: p = NULL; break; + } + return p; +} + + +static void +set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd) +{ + enum cmd_and_opt_values c = *ret_cmd; + + if (!c || c == new_cmd) + c = new_cmd; + else if (c == aSign && new_cmd == aEncrypt) + c = aSignEncrypt; + else if (c == aEncrypt && new_cmd == aSign) + c = aSignEncrypt; + else + { + log_error (_("conflicting commands\n")); + exit (2); + } + + *ret_cmd = c; +} + + + +/* Shell-like argument splitting. + + For compatibility with gpg-zip we accept arguments for GnuPG and + tar given as a string argument to '--gpg-args' and '--tar-args'. + gpg-zip was implemented as a Bourne Shell script, and therefore, we + need to split the string the same way the shell would. */ +static int +shell_parse_stringlist (const char *str, strlist_t *r_list) +{ + strlist_t list = NULL; + const char *s = str; + char quoted = 0; + char arg[1024]; + char *p = arg; +#define addchar(c) \ + do { if (p - arg + 2 < sizeof arg) *p++ = (c); else return 1; } while (0) +#define addargument() \ + do { \ + if (p > arg) \ + { \ + *p = 0; \ + append_to_strlist (&list, arg); \ + p = arg; \ + } \ + } while (0) + +#define unquoted 0 +#define singlequote '\'' +#define doublequote '"' + + for (; *s; s++) + { + switch (quoted) + { + case unquoted: + if (isspace (*s)) + addargument (); + else if (*s == singlequote || *s == doublequote) + quoted = *s; + else + addchar (*s); + break; + + case singlequote: + if (*s == singlequote) + quoted = unquoted; + else + addchar (*s); + break; + + case doublequote: + log_assert (s > str || !"cannot be quoted at first char"); + if (*s == doublequote && *(s - 1) != '\\') + quoted = unquoted; + else + addchar (*s); + break; + + default: + log_assert (! "reached"); + } + } + + /* Append the last argument. */ + addargument (); + +#undef doublequote +#undef singlequote +#undef unquoted +#undef addargument +#undef addchar + *r_list = list; + return 0; +} + + +/* Like shell_parse_stringlist, but returns an argv vector + instead of a strlist. */ +static int +shell_parse_argv (const char *s, int *r_argc, char ***r_argv) +{ + int i; + strlist_t list; + + if (shell_parse_stringlist (s, &list)) + return 1; + + *r_argc = strlist_length (list); + *r_argv = xtrycalloc (*r_argc, sizeof **r_argv); + if (*r_argv == NULL) + return 1; + + for (i = 0; list; i++) + { + gpgrt_annotate_leaked_object (list); + (*r_argv)[i] = list->d; + list = list->next; + } + gpgrt_annotate_leaked_object (*r_argv); + return 0; +} + + + +/* Command line parsing. */ +static void +parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) +{ + int no_more_options = 0; + + while (!no_more_options && gnupg_argparse (NULL, pargs, popts)) + { + switch (pargs->r_opt) + { + case oOutput: opt.outfile = pargs->r.ret_str; break; + case oDirectory: opt.directory = pargs->r.ret_str; break; + case oSetFilename: opt.filename = pargs->r.ret_str; break; + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + case oNoVerbose: opt.verbose = 0; break; + case oFilesFrom: files_from = pargs->r.ret_str; break; + case oNull: null_names = 1; break; + case oUtf8Strings: opt.utf8strings = 1; break; + + case aList: + case aDecrypt: + case aEncrypt: + case aSign: + set_cmd (&cmd, pargs->r_opt); + break; + + case aCreate: + set_cmd (&cmd, aEncrypt); + skip_crypto = 1; + break; + + case aExtract: + set_cmd (&cmd, aDecrypt); + skip_crypto = 1; + break; + + case oRecipient: + add_to_strlist (&opt.recipients, pargs->r.ret_str); + break; + + case oUser: + opt.user = pargs->r.ret_str; + break; + + case oSymmetric: + set_cmd (&cmd, aEncrypt); + opt.symmetric = 1; + break; + + case oGpgProgram: + opt.gpg_program = pargs->r.ret_str; + break; + + case oSkipCrypto: + skip_crypto = 1; + break; + + case oOpenPGP: /* Dummy option for now. */ break; + case oCMS: /* Dummy option for now. */ break; + + case oBatch: opt.batch = 1; break; + case oAnswerYes: opt.answer_yes = 1; break; + case oAnswerNo: opt.answer_no = 1; break; + case oStatusFD: opt.status_fd = pargs->r.ret_int; break; + case oRequireCompliance: opt.require_compliance = 1; break; + case oWithLog: opt.with_log = 1; break; + + case oGpgArgs:; + { + strlist_t list; + if (shell_parse_stringlist (pargs->r.ret_str, &list)) + log_error ("failed to parse gpg arguments '%s'\n", + pargs->r.ret_str); + else + { + if (opt.gpg_arguments) + strlist_last (opt.gpg_arguments)->next = list; + else + opt.gpg_arguments = list; + } + } + break; + + case oTarArgs: + { + int tar_argc; + char **tar_argv; + + if (shell_parse_argv (pargs->r.ret_str, &tar_argc, &tar_argv)) + log_error ("failed to parse tar arguments '%s'\n", + pargs->r.ret_str); + else + { + ARGPARSE_ARGS tar_args; + tar_args.argc = &tar_argc; + tar_args.argv = &tar_argv; + tar_args.flags = ARGPARSE_FLAG_ARG0; + parse_arguments (&tar_args, tar_opts); + gnupg_argparse (NULL, &tar_args, NULL); + if (tar_args.err) + log_error ("unsupported tar arguments '%s'\n", + pargs->r.ret_str); + pargs->err = tar_args.err; + } + } + break; + + case oDryRun: + opt.dry_run = 1; + break; + + default: pargs->err = 2; break; + } + } +} + + +/* gpgtar main. */ +int +main (int argc, char **argv) +{ + gpg_error_t err; + const char *fname; + ARGPARSE_ARGS pargs; + + gnupg_reopen_std (GPGTAR_NAME); + set_strusage (my_strusage); + log_set_prefix (GPGTAR_NAME, GPGRT_LOG_WITH_PREFIX); + + /* Make sure that our subsystems are ready. */ + i18n_init(); + init_common_subsystems (&argc, &argv); + gnupg_init_signals (0, NULL); + + log_assert (sizeof (struct ustar_raw_header) == 512); + + /* Set default options */ + opt.status_fd = -1; + + /* Parse the command line. */ + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags = ARGPARSE_FLAG_KEEP; + parse_arguments (&pargs, opts); + gnupg_argparse (NULL, &pargs, NULL); + + if (log_get_errorcount (0)) + exit (2); + + /* Print a warning if an argument looks like an option. */ + if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) + { + int i; + + for (i=0; i < argc; i++) + if (argv[i][0] == '-' && argv[i][1] == '-') + log_info (_("NOTE: '%s' is not considered an option\n"), argv[i]); + } + + if (! opt.gpg_program) + opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); + + if (opt.verbose > 1) + opt.debug_level = 1024; + + switch (cmd) + { + case aList: + if (argc > 1) + usage (1); + fname = argc ? *argv : NULL; + if (opt.filename) + log_info ("note: ignoring option --set-filename\n"); + if (files_from) + log_info ("note: ignoring option --files-from\n"); + err = gpgtar_list (fname, !skip_crypto); + if (err && log_get_errorcount (0) == 0) + log_error ("listing archive failed: %s\n", gpg_strerror (err)); + break; + + case aEncrypt: + case aSign: + case aSignEncrypt: + if ((!argc && !files_from) + || (argc && files_from)) + usage (1); + if (opt.filename) + log_info ("note: ignoring option --set-filename\n"); + err = gpgtar_create (files_from? NULL : argv, + files_from, + null_names, + !skip_crypto + && (cmd == aEncrypt || cmd == aSignEncrypt), + cmd == aSign || cmd == aSignEncrypt); + if (err && log_get_errorcount (0) == 0) + log_error ("creating archive failed: %s\n", gpg_strerror (err)); + break; + + case aDecrypt: + if (argc != 1) + usage (1); + if (opt.outfile) + log_info ("note: ignoring option --output\n"); + if (files_from) + log_info ("note: ignoring option --files-from\n"); + fname = argc ? *argv : NULL; + err = gpgtar_extract (fname, !skip_crypto); + if (err && log_get_errorcount (0) == 0) + log_error ("extracting archive failed: %s\n", gpg_strerror (err)); + break; + + default: + log_error (_("invalid command (there is no implicit command)\n")); + break; + } + + return log_get_errorcount (0)? 1:0; +} + + +/* Read the next record from STREAM. RECORD is a buffer provided by + the caller and must be at least of size RECORDSIZE. The function + return 0 on success and error code on failure; a diagnostic + printed as well. Note that there is no need for an EOF indicator + because a tarball has an explicit EOF record. */ +gpg_error_t +read_record (estream_t stream, void *record) +{ + gpg_error_t err; + size_t nread; + + nread = es_fread (record, 1, RECORDSIZE, stream); + if (nread != RECORDSIZE) + { + err = gpg_error_from_syserror (); + if (es_ferror (stream)) + log_error ("error reading '%s': %s\n", + es_fname_get (stream), gpg_strerror (err)); + else + log_error ("error reading '%s': premature EOF " + "(size of last record: %zu)\n", + es_fname_get (stream), nread); + } + else + err = 0; + + return err; +} + + +/* Write the RECORD of size RECORDSIZE to STREAM. FILENAME is the + name of the file used for diagnostics. */ +gpg_error_t +write_record (estream_t stream, const void *record) +{ + gpg_error_t err; + size_t nwritten; + + nwritten = es_fwrite (record, 1, RECORDSIZE, stream); + if (nwritten != RECORDSIZE) + { + err = gpg_error_from_syserror (); + log_error ("error writing '%s': %s\n", + es_fname_get (stream), gpg_strerror (err)); + } + else + err = 0; + + return err; +} + + +/* Return true if FP is an unarmored OpenPGP message. Note that this + function reads a few bytes from FP but pushes them back. */ +#if 0 +static int +openpgp_message_p (estream_t fp) +{ + int ctb; + + ctb = es_getc (fp); + if (ctb != EOF) + { + if (es_ungetc (ctb, fp)) + log_fatal ("error ungetting first byte: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + + if ((ctb & 0x80)) + { + switch ((ctb & 0x40) ? (ctb & 0x3f) : ((ctb>>2)&0xf)) + { + case PKT_MARKER: + case PKT_SYMKEY_ENC: + case PKT_ONEPASS_SIG: + case PKT_PUBKEY_ENC: + case PKT_SIGNATURE: + case PKT_COMMENT: + case PKT_OLD_COMMENT: + case PKT_PLAINTEXT: + case PKT_COMPRESSED: + case PKT_ENCRYPTED: + return 1; /* Yes, this seems to be an OpenPGP message. */ + default: + break; + } + } + } + return 0; +} +#endif diff --git a/tools/gpgtar.h b/tools/gpgtar.h new file mode 100644 index 0000000..9f3c90f --- /dev/null +++ b/tools/gpgtar.h @@ -0,0 +1,156 @@ +/* gpgtar.h - Global definitions for gpgtar + * Copyright (C) 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef GPGTAR_H +#define GPGTAR_H + +#include "../common/util.h" +#include "../common/strlist.h" + + +/* We keep all global options in the structure OPT. */ +EXTERN_UNLESS_MAIN_MODULE +struct +{ + int verbose; + unsigned int debug_level; + int quiet; + int dry_run; + int utf8strings; + const char *gpg_program; + strlist_t gpg_arguments; + const char *outfile; + strlist_t recipients; + const char *user; + int symmetric; + const char *filename; + const char *directory; + int batch; + int answer_yes; + int answer_no; + int status_fd; + int require_compliance; + int with_log; +} opt; + + +/* An info structure to avoid global variables. */ +struct tarinfo_s +{ + unsigned long long nblocks; /* Count of processed blocks. */ + unsigned long long headerblock; /* Number of current header block. */ +}; +typedef struct tarinfo_s *tarinfo_t; + + +/* The size of a tar record. All IO is done in chunks of this size. + Note that we don't care about blocking because this version of tar + is not expected to be used directly on a tape drive in fact it is + used in a pipeline with GPG and thus any blocking would be + useless. */ +#define RECORDSIZE 512 + + +/* Description of the USTAR header format. */ +struct ustar_raw_header +{ + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char checksum[8]; + char typeflag[1]; + char linkname[100]; + char magic[6]; + char version[2]; + char uname[32]; + char gname[32]; + char devmajor[8]; + char devminor[8]; + char prefix[155]; + char pad[12]; +}; + + +/* Filetypes as defined by USTAR. */ +typedef enum + { + TF_REGULAR, + TF_HARDLINK, + TF_SYMLINK, + TF_CHARDEV, + TF_BLOCKDEV, + TF_DIRECTORY, + TF_FIFO, + TF_RESERVED, + TF_GEXTHDR, /* Global extended header. */ + TF_EXTHDR, /* Extended header. */ + TF_UNKNOWN, /* Needs to be treated as regular file. */ + TF_NOTSUP /* Not supported (used with --create). */ + } typeflag_t; + + +/* The internal represenation of a TAR header. */ +struct tar_header_s; +typedef struct tar_header_s *tar_header_t; +struct tar_header_s +{ + tar_header_t next; /* Used to build a linked list of entries. */ + + unsigned long mode; /* The file mode. */ + unsigned long nlink; /* Number of hard links. */ + unsigned long uid; /* The user id of the file. */ + unsigned long gid; /* The group id of the file. */ + unsigned long long size; /* The size of the file. */ + unsigned long long mtime; /* Modification time since Epoch. Note + that we don't use time_t here but a + type which is more likely to be larger + that 32 bit and thus allows tracking + times beyond 2106. */ + typeflag_t typeflag; /* The type of the file. */ + + + unsigned long long nrecords; /* Number of data records. */ + + char name[1]; /* Filename (UTF-8, dynamically extended). */ +}; + + +/*-- gpgtar.c --*/ +gpg_error_t read_record (estream_t stream, void *record); +gpg_error_t write_record (estream_t stream, const void *record); + +/*-- gpgtar-create.c --*/ +gpg_error_t gpgtar_create (char **inpattern, const char *files_from, + int null_names, int encrypt, int sign); + +/*-- gpgtar-extract.c --*/ +gpg_error_t gpgtar_extract (const char *filename, int decrypt); + +/*-- gpgtar-list.c --*/ +gpg_error_t gpgtar_list (const char *filename, int decrypt); +gpg_error_t gpgtar_read_header (estream_t stream, tarinfo_t info, + tar_header_t *r_header, strlist_t *r_extheader); +void gpgtar_print_header (tar_header_t header, strlist_t extheader, + estream_t out); + + +#endif /*GPGTAR_H*/ diff --git a/tools/gpgtar.w32-manifest.in b/tools/gpgtar.w32-manifest.in new file mode 100644 index 0000000..62d5937 --- /dev/null +++ b/tools/gpgtar.w32-manifest.in @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<description>GNU Privacy Guard (Archive tool)</description> +<assemblyIdentity + type="win32" + name="GnuPG.gpgtar" + version="@BUILD_VERSION@" + /> +<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/><!-- 10 --> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/><!-- 8.1 --> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/><!-- 8 --> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/><!-- 7 --> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/><!-- Vista --> + </application> +</compatibility> +</assembly> diff --git a/tools/lspgpot b/tools/lspgpot new file mode 100755 index 0000000..f406392 --- /dev/null +++ b/tools/lspgpot @@ -0,0 +1,27 @@ +#!/bin/sh +# lspgpot - script to extract the ownertrust values +# from PGP keyrings and list them in GnuPG ownertrust format. +# +# This file is free software; as a special exception the author gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +if ! gpg --version > /dev/null 2>&1 ; then + echo "GnuPG not available!" + exit 1 +fi + +gpg --dry-run --with-fingerprint --with-colons $* | awk ' +BEGIN { FS=":" + printf "# Ownertrust listing generated by lspgpot\n" + printf "# This can be imported using the command:\n" + printf "# gpg --import-ownertrust\n\n" } +$1 == "fpr" { fpr = $10 } +$1 == "rtv" && $2 == 1 && $3 == 2 { printf "%s:3:\n", fpr; next } +$1 == "rtv" && $2 == 1 && $3 == 5 { printf "%s:4:\n", fpr; next } +$1 == "rtv" && $2 == 1 && $3 == 6 { printf "%s:5:\n", fpr; next } +' diff --git a/tools/mail-signed-keys b/tools/mail-signed-keys new file mode 100755 index 0000000..263b8e5 --- /dev/null +++ b/tools/mail-signed-keys @@ -0,0 +1,114 @@ +#!/bin/sh +# Copyright (C) 2000, 2001 Free Software Foundation, Inc. +# +# This file is free software; as a special exception the author gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# FIXME: Use only valid email addresses, extract only given keys + +dryrun=0 +if [ "$1" = "--dry-run" ]; then + dryrun=1 + shift +fi + +if [ -z "$1" -o -z "$2" -o -z "$3" ]; then + echo "usage: mail-signed-keys keyring signedby signame" >&2 + exit 1 +fi + +signame="$3" + +if [ ! -f $1 ]; then + echo "mail-signed-keys: '$1': no such file" >&2 + exit 1 +fi + +[ -f '.#tdb.tmp' ] && rm '.#tdb.tmp' +ro="--homedir . --no-options --trustdb-name=./.#tdb.tmp --dry-run --lock-never --no-default-keyring --keyring $1" + +signedby=`gpg $ro --list-keys --with-colons $2 \ + 2>/dev/null | awk -F: '$1=="pub" {print $5; exit 0}'` + +if [ -z "$signedby" ]; then + echo "mail-signed-keys: '$2': no such signator" >&2 + exit 1 +fi + +if [ "$dryrun" = "0" ]; then + echo "About to send the keys signed by $signedby" >&2 + echo -n "to their owners. Do you really want to do this? (y/N)" >&2 + read + [ "$REPLY" != "y" -a "$REPLY" != "Y" ] && exit 0 +fi + +gpg $ro --check-sigs --with-colons 2>/dev/null \ + | awk -F: -v signedby="$signedby" -v gpgopt="$ro" \ + -v dryrun="$dryrun" -v signame="$signame" ' +BEGIN { sendmail="/usr/lib/sendmail -oi -t " } +$1 == "pub" { nextkid=$5; nextuid=$10 + if( uidcount > 0 ) { myflush() } + kid=nextkid; uid=nextuid; next + } +$1 == "uid" { uid=$10 ; next } +$1 == "sig" && $2 == "!" && $5 == signedby { uids[uidcount++] = uid; next } +END { if( uidcount > 0 ) { myflush() } } + +function myflush() +{ + if ( kid == signedby ) { uidcount=0; return } + print "sending key " substr(kid,9) " to" | "cat >&2" + for(i=0; i < 1; i++ ) { + print " " uids[i] | "cat >&2" + if( dryrun == 0 ) { + if( i == 0 ) { + printf "To: %s", uids[i] | sendmail + } + else { + printf ",\n %s", uids[i] | sendmail + } + } + } + if(dryrun == 0) { + printf "\n" | sendmail + print "Subject: I signed your key " substr(kid,9) | sendmail + print "" | sendmail + print "Hi," | sendmail + print "" | sendmail + print "Here you get back the signed key." | sendmail + print "I already sent them to the keyservers." | sendmail + print "" | sendmail + print "Peace," | sendmail + print " " signame | sendmail + print "" | sendmail + cmd = "gpg " gpgopt " --export -a " kid " 2>/dev/null" + while( (cmd | getline) > 0 ) { + print | sendmail + } + print "" | sendmail + close(cmd) + close( sendmail ) + } + uidcount=0 +} +' + + + + + + + + + + + + + + + diff --git a/tools/make-dns-cert.c b/tools/make-dns-cert.c new file mode 100644 index 0000000..9a7e20d --- /dev/null +++ b/tools/make-dns-cert.c @@ -0,0 +1,247 @@ +/* make-dns-cert.c - An OpenPGP-to-DNS CERT conversion tool + * Copyright (C) 2006, 2008 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <unistd.h> +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +/* We use TYPE37 instead of CERT since not all nameservers can handle + CERT yet... */ + +static int +cert_key(const char *name,const char *keyfile) +{ + int fd,ret=1,err,i; + struct stat statbuf; + + fd=open(keyfile,O_RDONLY); + if(fd==-1) + { + fprintf(stderr,"Cannot open key file %s: %s\n",keyfile,strerror(errno)); + return 1; + } + + err=fstat(fd,&statbuf); + if(err==-1) + { + fprintf(stderr,"Unable to stat key file %s: %s\n", + keyfile,strerror(errno)); + goto fail; + } + + if(statbuf.st_size>65536) + { + fprintf(stderr,"Key %s too large for CERT encoding\n",keyfile); + goto fail; + } + + if(statbuf.st_size>16384) + fprintf(stderr,"Warning: key file %s is larger than the default" + " GnuPG max-cert-size\n",keyfile); + + printf("%s\tTYPE37\t\\# %u 0003 0000 00 ", + name,(unsigned int)statbuf.st_size+5); + + err=1; + while(err!=0) + { + unsigned char buffer[1024]; + + do + err = read (fd,buffer,1024); + while (err == -1 && errno == EINTR); + if(err==-1) + { + fprintf(stderr,"Unable to read key file %s: %s\n", + keyfile,strerror(errno)); + goto fail; + } + + for(i=0;i<err;i++) + printf("%02X",buffer[i]); + } + + printf("\n"); + + ret=0; + + fail: + close(fd); + + return ret; +} + +static int +url_key(const char *name,const char *fpr,const char *url) +{ + int len=6,fprlen=0; + + if(fpr) + { + const char *tmp = fpr; + while (*tmp) + { + if ((*tmp >= 'A' && *tmp <= 'F') || + (*tmp >= 'a' && *tmp <= 'f') || + (*tmp >= '0' && *tmp <= '9')) + { + fprlen++; + } + else if (*tmp != ' ' && *tmp != '\t') + { + fprintf(stderr,"Fingerprint must consist of only hex digits" + " and whitespace\n"); + return 1; + } + + tmp++; + } + + if(fprlen%2) + { + fprintf(stderr,"Fingerprint must be an even number of characters\n"); + return 1; + } + + fprlen/=2; + len+=fprlen; + } + + if(url) + len+=strlen(url); + + if(!fpr && !url) + { + fprintf(stderr, + "Cannot generate a CERT without either a fingerprint or URL\n"); + return 1; + } + + printf("%s\tTYPE37\t\\# %d 0006 0000 00 %02X",name,len,fprlen); + + if(fpr) + printf(" %s",fpr); + + if(url) + { + const char *c; + printf(" "); + for(c=url;*c;c++) + printf("%02X",*c); + } + + printf("\n"); + + return 0; +} + +static void +usage(FILE *stream) +{ + fprintf(stream,"make-dns-cert\n"); + fprintf(stream,"\t-f\tfingerprint\n"); + fprintf(stream,"\t-u\tURL\n"); + fprintf(stream,"\t-k\tkey file\n"); + fprintf(stream,"\t-n\tDNS name\n"); +} + +int +main(int argc,char *argv[]) +{ + int arg,err=1; + char *fpr=NULL,*url=NULL,*keyfile=NULL,*name=NULL; + + if(argc==1) + { + usage(stderr); + return 1; + } + else if(argc>1 && strcmp(argv[1],"--version")==0) + { +#if defined(HAVE_CONFIG_H) && defined(VERSION) + printf ("make-dns-cert (GnuPG) " VERSION "\n"); +#else + printf ("make-dns-cert gnupg-svn%d\n", atoi (10+"$Revision$")); +#endif + return 0; + } + else if(argc>1 && strcmp(argv[1],"--help")==0) + { + usage(stdout); + return 0; + } + + while((arg=getopt(argc,argv,"hf:u:k:n:"))!=-1) + switch(arg) + { + default: + case 'h': + usage(stdout); + exit(0); + + case 'f': + fpr=optarg; + break; + + case 'u': + url=optarg; + break; + + case 'k': + keyfile=optarg; + break; + + case 'n': + name=optarg; + break; + } + + if(!name) + { + fprintf(stderr,"No name provided\n"); + return 1; + } + + if(keyfile && (fpr || url)) + { + fprintf(stderr,"Cannot generate a CERT record with both a keyfile and" + " a fingerprint or URL\n"); + return 1; + } + + if(keyfile) + err=cert_key(name,keyfile); + else + err=url_key(name,fpr,url); + + return err; +} diff --git a/tools/mime-maker.c b/tools/mime-maker.c new file mode 100644 index 0000000..91eab82 --- /dev/null +++ b/tools/mime-maker.c @@ -0,0 +1,777 @@ +/* mime-maker.c - Create MIME structures + * Copyright (C) 2016 g10 Code GmbH + * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/util.h" +#include "../common/zb32.h" +#include "rfc822parse.h" +#include "mime-maker.h" + + +/* An object to store an header. Also used for a list of headers. */ +struct header_s +{ + struct header_s *next; + char *value; /* Malloced value. */ + char name[1]; /* Name. */ +}; +typedef struct header_s *header_t; + + +/* An object to store a MIME part. A part is the header plus the + * content (body). */ +struct part_s +{ + struct part_s *next; /* Next part in the current container. */ + struct part_s *child; /* Child container. */ + char *boundary; /* Malloced boundary string. */ + header_t headers; /* List of headers. */ + header_t *headers_tail;/* Address of last header in chain. */ + size_t bodylen; /* Length of BODY. */ + char *body; /* Malloced buffer with the body. This is the + * non-encoded value. */ + unsigned int partid; /* The part ID. */ +}; +typedef struct part_s *part_t; + + + +/* Definition of the mime parser object. */ +struct mime_maker_context_s +{ + void *cookie; /* Cookie passed to all callbacks. */ + + unsigned int verbose:1; /* Enable verbose mode. */ + unsigned int debug:1; /* Enable debug mode. */ + + part_t mail; /* The MIME tree. */ + part_t current_part; + + unsigned int partid_counter; /* Counter assign part ids. */ + + int boundary_counter; /* Used to create easy to read boundaries. */ + char *boundary_suffix; /* Random string used in the boundaries. */ + + struct b64state *b64state; /* NULL or malloced Base64 decoder state. */ + + /* Helper to convey the output stream to recursive functions. */ + estream_t outfp; +}; + + +/* Create a new mime make object. COOKIE is a values woich will be + * used as first argument for all callbacks registered with this + * object. */ +gpg_error_t +mime_maker_new (mime_maker_t *r_maker, void *cookie) +{ + mime_maker_t ctx; + + *r_maker = NULL; + + ctx = xtrycalloc (1, sizeof *ctx); + if (!ctx) + return gpg_error_from_syserror (); + ctx->cookie = cookie; + + *r_maker = ctx; + return 0; +} + + +static void +release_parts (part_t part) +{ + while (part) + { + part_t partnext = part->next; + while (part->headers) + { + header_t hdrnext = part->headers->next; + xfree (part->headers); + part->headers = hdrnext; + } + release_parts (part->child); + xfree (part->boundary); + xfree (part->body); + xfree (part); + part = partnext; + } +} + + +/* Release a mime maker object. */ +void +mime_maker_release (mime_maker_t ctx) +{ + if (!ctx) + return; + + release_parts (ctx->mail); + xfree (ctx->boundary_suffix); + xfree (ctx); +} + + +/* Set verbose and debug mode. */ +void +mime_maker_set_verbose (mime_maker_t ctx, int level) +{ + if (!level) + { + ctx->verbose = 0; + ctx->debug = 0; + } + else + { + ctx->verbose = 1; + if (level > 10) + ctx->debug = 1; + } +} + + +static void +dump_parts (part_t part, int level) +{ + header_t hdr; + + for (; part; part = part->next) + { + log_debug ("%*s[part %u]\n", level*2, "", part->partid); + for (hdr = part->headers; hdr; hdr = hdr->next) + { + log_debug ("%*s%s: %s\n", level*2, "", hdr->name, hdr->value); + } + if (part->body) + log_debug ("%*s[body %zu bytes]\n", level*2, "", part->bodylen); + if (part->child) + { + log_debug ("%*s[container]\n", level*2, ""); + dump_parts (part->child, level+1); + } + } +} + + +/* Dump the mime tree for debugging. */ +void +mime_maker_dump_tree (mime_maker_t ctx) +{ + dump_parts (ctx->mail, 0); +} + + +/* Find the parent node for NEEDLE starting at ROOT. */ +static part_t +find_parent (part_t root, part_t needle) +{ + part_t node, n; + + for (node = root->child; node; node = node->next) + { + if (node == needle) + return root; + if ((n = find_parent (node, needle))) + return n; + } + return NULL; +} + +/* Find the part node from the PARTID. */ +static part_t +find_part (part_t root, unsigned int partid) +{ + part_t node, n; + + for (node = root->child; node; node = node->next) + { + if (node->partid == partid) + return root; + if ((n = find_part (node, partid))) + return n; + } + return NULL; +} + + +/* Create a boundary string. Outr codes is aware of the general + * structure of that string (gebins with "=-=") so that + * it can protect against accidentally-used boundaries within the + * content. */ +static char * +generate_boundary (mime_maker_t ctx) +{ + if (!ctx->boundary_suffix) + { + char buffer[12]; + + gcry_create_nonce (buffer, sizeof buffer); + ctx->boundary_suffix = zb32_encode (buffer, 8 * sizeof buffer); + if (!ctx->boundary_suffix) + return NULL; + } + + ctx->boundary_counter++; + return es_bsprintf ("=-=%02d-%s=-=", + ctx->boundary_counter, ctx->boundary_suffix); +} + + +/* Ensure that the context has a MAIL and CURRENT_PART object and + * return the parent object if available */ +static gpg_error_t +ensure_part (mime_maker_t ctx, part_t *r_parent) +{ + if (!ctx->mail) + { + ctx->mail = xtrycalloc (1, sizeof *ctx->mail); + if (!ctx->mail) + { + if (r_parent) + *r_parent = NULL; + return gpg_error_from_syserror (); + } + log_assert (!ctx->current_part); + ctx->current_part = ctx->mail; + ctx->current_part->headers_tail = &ctx->current_part->headers; + } + log_assert (ctx->current_part); + if (r_parent) + *r_parent = find_parent (ctx->mail, ctx->current_part); + + return 0; +} + + +/* Check whether a header with NAME has already been set into PART. + * NAME must be in canonical capitalized format. Return true or + * false. */ +static int +have_header (part_t part, const char *name) +{ + header_t hdr; + + for (hdr = part->headers; hdr; hdr = hdr->next) + if (!strcmp (hdr->name, name)) + return 1; + return 0; +} + + +/* Helper to add a header to a part. */ +static gpg_error_t +add_header (part_t part, const char *name, const char *value) +{ + gpg_error_t err; + header_t hdr; + size_t namelen; + const char *s; + char *p; + + if (!value) + { + s = strchr (name, '='); + if (!s) + return gpg_error (GPG_ERR_INV_ARG); + namelen = s - name; + value = s+1; + } + else + namelen = strlen (name); + + hdr = xtrymalloc (sizeof *hdr + namelen); + if (!hdr) + return gpg_error_from_syserror (); + hdr->next = NULL; + memcpy (hdr->name, name, namelen); + hdr->name[namelen] = 0; + + /* Check that the header name is valid. */ + if (!rfc822_valid_header_name_p (hdr->name)) + { + xfree (hdr); + return gpg_error (GPG_ERR_INV_NAME); + } + + rfc822_capitalize_header_name (hdr->name); + hdr->value = xtrystrdup (value); + if (!hdr->value) + { + err = gpg_error_from_syserror (); + xfree (hdr); + return err; + } + + for (p = hdr->value + strlen (hdr->value) - 1; + (p >= hdr->value + && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')); + p--) + *p = 0; + if (!(p >= hdr->value)) + { + xfree (hdr->value); + xfree (hdr); + return gpg_error (GPG_ERR_INV_VALUE); /* Only spaces. */ + } + + if (part) + { + *part->headers_tail = hdr; + part->headers_tail = &hdr->next; + } + else + xfree (hdr); + + return 0; +} + + +/* Add a header with NAME and VALUE to the current mail. A LF in the + * VALUE will be handled automagically. If NULL is used for VALUE it + * is expected that the NAME has the format "NAME=VALUE" and VALUE is + * taken from there. + * + * If no container has been added, the header will be used for the + * regular mail headers and not for a MIME part. If the current part + * is in a container and a body has been added, we append a new part + * to the current container. Thus for a non-MIME mail the caller + * needs to call this function followed by a call to add a body. When + * adding a Content-Type the boundary parameter must not be included. + */ +gpg_error_t +mime_maker_add_header (mime_maker_t ctx, const char *name, const char *value) +{ + gpg_error_t err; + part_t part, parent; + + /* Hack to use this function for a syntax check of NAME and VALUE. */ + if (!ctx) + return add_header (NULL, name, value); + + err = ensure_part (ctx, &parent); + if (err) + return err; + part = ctx->current_part; + + if ((part->body || part->child) && !parent) + { + /* We already have a body but no parent. Adding another part is + * thus not possible. */ + return gpg_error (GPG_ERR_CONFLICT); + } + if (part->body || part->child) + { + /* We already have a body and there is a parent. We now append + * a new part to the current container. */ + part = xtrycalloc (1, sizeof *part); + if (!part) + return gpg_error_from_syserror (); + part->partid = ++ctx->partid_counter; + part->headers_tail = &part->headers; + log_assert (!ctx->current_part->next); + ctx->current_part->next = part; + ctx->current_part = part; + } + + /* If no NAME and no VALUE has been given we do not add a header. + * This can be used to create a new part without any header. */ + if (!name && !value) + return 0; + + /* If we add Content-Type, make sure that we have a MIME-version + * header first; this simply looks better. */ + if (!ascii_strcasecmp (name, "Content-Type") + && !have_header (ctx->mail, "MIME-Version")) + { + err = add_header (ctx->mail, "MIME-Version", "1.0"); + if (err) + return err; + } + return add_header (part, name, value); +} + + +/* Helper for mime_maker_add_{body,stream}. */ +static gpg_error_t +add_body (mime_maker_t ctx, const void *data, size_t datalen) +{ + gpg_error_t err; + part_t part, parent; + + err = ensure_part (ctx, &parent); + if (err) + return err; + part = ctx->current_part; + if (part->body) + return gpg_error (GPG_ERR_CONFLICT); + + part->body = xtrymalloc (datalen? datalen : 1); + if (!part->body) + return gpg_error_from_syserror (); + part->bodylen = datalen; + if (data) + memcpy (part->body, data, datalen); + + return 0; +} + + +/* Add STRING as body to the mail or the current MIME container. A + * second call to this function or mime_make_add_body_data is not + * allowed. + * + * FIXME: We may want to have an append_body to add more data to a body. + */ +gpg_error_t +mime_maker_add_body (mime_maker_t ctx, const char *string) +{ + return add_body (ctx, string, strlen (string)); +} + + +/* Add (DATA,DATALEN) as body to the mail or the current MIME + * container. Note that a second call to this function or to + * mime_make_add_body is not allowed. */ +gpg_error_t +mime_maker_add_body_data (mime_maker_t ctx, const void *data, size_t datalen) +{ + return add_body (ctx, data, datalen); +} + + +/* This is the same as mime_maker_add_body but takes a stream as + * argument. As of now the stream is copied to the MIME object but + * eventually we may delay that and read the stream only at the time + * it is needed. Note that the address of the stream object must be + * passed and that the ownership of the stream is transferred to this + * MIME object. To indicate the latter the function will store NULL + * at the ADDR_STREAM so that a caller can't use that object anymore + * except for es_fclose which accepts a NULL pointer. */ +gpg_error_t +mime_maker_add_stream (mime_maker_t ctx, estream_t *stream_addr) +{ + void *data; + size_t datalen; + + es_rewind (*stream_addr); + if (es_fclose_snatch (*stream_addr, &data, &datalen)) + return gpg_error_from_syserror (); + *stream_addr = NULL; + return add_body (ctx, data, datalen); +} + + +/* Add a new MIME container. A container can be used instead of a + * body. */ +gpg_error_t +mime_maker_add_container (mime_maker_t ctx) +{ + gpg_error_t err; + part_t part; + + err = ensure_part (ctx, NULL); + if (err) + return err; + part = ctx->current_part; + + if (part->body) + return gpg_error (GPG_ERR_CONFLICT); /* There is already a body. */ + if (part->child || part->boundary) + return gpg_error (GPG_ERR_CONFLICT); /* There is already a container. */ + + /* Create a child node. */ + part->child = xtrycalloc (1, sizeof *part->child); + if (!part->child) + return gpg_error_from_syserror (); + part->child->headers_tail = &part->child->headers; + + part->boundary = generate_boundary (ctx); + if (!part->boundary) + { + err = gpg_error_from_syserror (); + xfree (part->child); + part->child = NULL; + return err; + } + + part = part->child; + part->partid = ++ctx->partid_counter; + ctx->current_part = part; + + return 0; +} + + +/* Finish the current container. */ +gpg_error_t +mime_maker_end_container (mime_maker_t ctx) +{ + gpg_error_t err; + part_t parent; + + err = ensure_part (ctx, &parent); + if (err) + return err; + if (!parent) + return gpg_error (GPG_ERR_CONFLICT); /* No container. */ + while (parent->next) + parent = parent->next; + ctx->current_part = parent; + return 0; +} + + +/* Return the part-ID of the current part. */ +unsigned int +mime_maker_get_partid (mime_maker_t ctx) +{ + if (ensure_part (ctx, NULL)) + return 0; /* Ooops. */ + return ctx->current_part->partid; +} + + +/* Write a header and handle emdedded LFs. If BOUNDARY is not NULL it + * is appended to the value. */ +/* Fixme: Add automatic line wrapping. */ +static gpg_error_t +write_header (mime_maker_t ctx, const char *name, const char *value, + const char *boundary) +{ + const char *s; + + es_fprintf (ctx->outfp, "%s: ", name); + + /* Note that add_header made sure that VALUE does not end with a LF. + * Thus we can assume that a LF is followed by non-whitespace. */ + for (s = value; *s; s++) + { + if (*s == '\n') + es_fputs ("\r\n\t", ctx->outfp); + else + es_fputc (*s, ctx->outfp); + } + if (boundary) + { + if (s > value && s[-1] != ';') + es_fputc (';', ctx->outfp); + es_fprintf (ctx->outfp, "\r\n\tboundary=\"%s\"", boundary); + } + + es_fputs ("\r\n", ctx->outfp); + + return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0; +} + + +static gpg_error_t +write_gap (mime_maker_t ctx) +{ + es_fputs ("\r\n", ctx->outfp); + return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0; +} + + +static gpg_error_t +write_boundary (mime_maker_t ctx, const char *boundary, int last) +{ + es_fprintf (ctx->outfp, "\r\n--%s%s\r\n", boundary, last?"--":""); + return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0; +} + + +/* Fixme: Apply required encoding. */ +static gpg_error_t +write_body (mime_maker_t ctx, const void *body, size_t bodylen) +{ + const char *s; + + for (s = body; bodylen; s++, bodylen--) + { + if (*s == '\n' && !(s > (const char *)body && s[-1] == '\r')) + es_fputc ('\r', ctx->outfp); + es_fputc (*s, ctx->outfp); + } + + return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0; +} + + +/* Recursive worker for mime_maker_make. */ +static gpg_error_t +write_tree (mime_maker_t ctx, part_t parent, part_t part) +{ + gpg_error_t err; + header_t hdr; + + for (; part; part = part->next) + { + for (hdr = part->headers; hdr; hdr = hdr->next) + { + if (part->child && !strcmp (hdr->name, "Content-Type")) + err = write_header (ctx, hdr->name, hdr->value, part->boundary); + else + err = write_header (ctx, hdr->name, hdr->value, NULL); + if (err) + return err; + } + err = write_gap (ctx); + if (err) + return err; + if (part->body) + { + err = write_body (ctx, part->body, part->bodylen); + if (err) + return err; + } + if (part->child) + { + log_assert (part->boundary); + err = write_boundary (ctx, part->boundary, 0); + if (!err) + err = write_tree (ctx, part, part->child); + if (!err) + err = write_boundary (ctx, part->boundary, 1); + if (err) + return err; + } + + if (part->next) + { + log_assert (parent && parent->boundary); + err = write_boundary (ctx, parent->boundary, 0); + if (err) + return err; + } + } + return 0; +} + + +/* Add headers we always require. */ +static gpg_error_t +add_missing_headers (mime_maker_t ctx) +{ + gpg_error_t err; + + if (!ctx->mail) + return gpg_error (GPG_ERR_NO_DATA); + if (!have_header (ctx->mail, "MIME-Version")) + { + /* Even if a Content-Type has never been set, we want to + * announce that we do MIME. */ + err = add_header (ctx->mail, "MIME-Version", "1.0"); + if (err) + goto leave; + } + + if (!have_header (ctx->mail, "Date")) + { + char *p = rfctimestamp (make_timestamp ()); + if (!p) + err = gpg_error_from_syserror (); + else + err = add_header (ctx->mail, "Date", p); + xfree (p); + if (err) + goto leave; + } + + err = 0; + + leave: + return err; +} + + +/* Create message from the tree MIME and write it to FP. Note that + * the output uses only a LF and a later called sendmail(1) is + * expected to convert them to network line endings. */ +gpg_error_t +mime_maker_make (mime_maker_t ctx, estream_t fp) +{ + gpg_error_t err; + + err = add_missing_headers (ctx); + if (err) + return err; + + ctx->outfp = fp; + err = write_tree (ctx, NULL, ctx->mail); + + ctx->outfp = NULL; + return err; +} + + +/* Create a stream object from the MIME part identified by PARTID and + * store it at R_STREAM. If PARTID identifies a container the entire + * tree is returned. Using that function may read stream objects + * which have been added as MIME bodies. The caller must close the + * stream object. */ +gpg_error_t +mime_maker_get_part (mime_maker_t ctx, unsigned int partid, estream_t *r_stream) +{ + gpg_error_t err; + part_t part; + estream_t fp; + + *r_stream = NULL; + + /* When the entire tree is requested, we make sure that all missing + * headers are applied. We don't do that if only a part is + * requested because the additional headers (like Date:) will only + * be added to part 0 headers anyway. */ + if (!partid) + { + err = add_missing_headers (ctx); + if (err) + return err; + part = ctx->mail; + } + else + part = find_part (ctx->mail, partid); + + /* For now we use a memory stream object; however it would also be + * possible to create an object created on the fly while the caller + * is reading the returned stream. */ + fp = es_fopenmem (0, "w+b"); + if (!fp) + return gpg_error_from_syserror (); + + ctx->outfp = fp; + err = write_tree (ctx, NULL, part); + ctx->outfp = NULL; + + if (!err) + { + es_rewind (fp); + *r_stream = fp; + } + else + es_fclose (fp); + + return err; +} diff --git a/tools/mime-maker.h b/tools/mime-maker.h new file mode 100644 index 0000000..c0ddaea --- /dev/null +++ b/tools/mime-maker.h @@ -0,0 +1,50 @@ +/* mime-maker.h - Create MIME structures + * Copyright (C) 2016 g10 Code GmbH + * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef GNUPG_MIME_MAKER_H +#define GNUPG_MIME_MAKER_H + +struct mime_maker_context_s; +typedef struct mime_maker_context_s *mime_maker_t; + +gpg_error_t mime_maker_new (mime_maker_t *r_ctx, void *cookie); +void mime_maker_release (mime_maker_t ctx); + +void mime_maker_set_verbose (mime_maker_t ctx, int level); + +void mime_maker_dump_tree (mime_maker_t ctx); + +gpg_error_t mime_maker_add_header (mime_maker_t ctx, + const char *name, const char *value); +gpg_error_t mime_maker_add_body (mime_maker_t ctx, const char *string); +gpg_error_t mime_maker_add_body_data (mime_maker_t ctx, + const void *data, size_t datalen); +gpg_error_t mime_maker_add_stream (mime_maker_t ctx, estream_t *stream_addr); +gpg_error_t mime_maker_add_container (mime_maker_t ctx); +gpg_error_t mime_maker_end_container (mime_maker_t ctx); +unsigned int mime_maker_get_partid (mime_maker_t ctx); + +gpg_error_t mime_maker_make (mime_maker_t ctx, estream_t fp); +gpg_error_t mime_maker_get_part (mime_maker_t ctx, unsigned int partid, + estream_t *r_stream); + + + +#endif /*GNUPG_MIME_MAKER_H*/ diff --git a/tools/mime-parser.c b/tools/mime-parser.c new file mode 100644 index 0000000..a151dc6 --- /dev/null +++ b/tools/mime-parser.c @@ -0,0 +1,833 @@ +/* mime-parser.c - Parse MIME structures (high level rfc822 parser). + * Copyright (C) 2016 g10 Code GmbH + * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/util.h" +#include "rfc822parse.h" +#include "mime-parser.h" + + +enum pgpmime_states + { + PGPMIME_NONE = 0, + PGPMIME_WAIT_ENCVERSION, + PGPMIME_IN_ENCVERSION, + PGPMIME_WAIT_ENCDATA, + PGPMIME_IN_ENCDATA, + PGPMIME_GOT_ENCDATA, + PGPMIME_WAIT_SIGNEDDATA, + PGPMIME_IN_SIGNEDDATA, + PGPMIME_WAIT_SIGNATURE, + PGPMIME_IN_SIGNATURE, + PGPMIME_GOT_SIGNATURE, + PGPMIME_INVALID + }; + + +/* Definition of the mime parser object. */ +struct mime_parser_context_s +{ + void *cookie; /* Cookie passed to all callbacks. */ + + /* The callback to announce the transation from header to body. */ + gpg_error_t (*t2body) (void *cookie, int level); + + /* The callback to announce a new part. */ + gpg_error_t (*new_part) (void *cookie, + const char *mediatype, + const char *mediasubtype); + /* The callback to return data of a part. */ + gpg_error_t (*part_data) (void *cookie, + const void *data, + size_t datalen); + /* The callback to collect encrypted data. */ + gpg_error_t (*collect_encrypted) (void *cookie, const char *data); + /* The callback to collect signed data. */ + gpg_error_t (*collect_signeddata) (void *cookie, const char *data); + /* The callback to collect a signature. */ + gpg_error_t (*collect_signature) (void *cookie, const char *data); + + /* The RFC822 parser context is stored here during callbacks. */ + rfc822parse_t msg; + + /* Helper to convey error codes from user callbacks. */ + gpg_error_t err; + + int nesting_level; /* The current nesting level. */ + int hashing_at_level; /* The nesting level at which we are hashing. */ + enum pgpmime_states pgpmime; /* Current PGP/MIME state. */ + unsigned int delay_hashing:1;/* Helper for PGPMIME_IN_SIGNEDDATA. */ + unsigned int want_part:1; /* Return the current part. */ + unsigned int decode_part:2; /* Decode the part. 1 = QP, 2 = Base64. */ + + unsigned int verbose:1; /* Enable verbose mode. */ + unsigned int debug:1; /* Enable debug mode. */ + + /* Flags to help with debug output. */ + struct { + unsigned int n_skip; /* Skip showing these number of lines. */ + unsigned int header:1; /* Show the header lines. */ + unsigned int data:1; /* Show the data lines. */ + unsigned int as_note:1; /* Show the next data line as a note. */ + unsigned int boundary : 1; + } show; + + struct b64state *b64state; /* NULL or malloced Base64 decoder state. */ + + /* A buffer for reading a mail line, */ + char line[5000]; +}; + + +/* Print the event received by the parser for debugging. */ +static void +show_message_parser_event (rfc822parse_event_t event) +{ + const char *s; + + switch (event) + { + case RFC822PARSE_OPEN: s= "Open"; break; + case RFC822PARSE_CLOSE: s= "Close"; break; + case RFC822PARSE_CANCEL: s= "Cancel"; break; + case RFC822PARSE_T2BODY: s= "T2Body"; break; + case RFC822PARSE_FINISH: s= "Finish"; break; + case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break; + case RFC822PARSE_LEVEL_DOWN: s= "Level_Down"; break; + case RFC822PARSE_LEVEL_UP: s= "Level_Up"; break; + case RFC822PARSE_BOUNDARY: s= "Boundary"; break; + case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break; + case RFC822PARSE_BEGIN_HEADER: s= "Begin_Header"; break; + case RFC822PARSE_PREAMBLE: s= "Preamble"; break; + case RFC822PARSE_EPILOGUE: s= "Epilogue"; break; + default: s= "[unknown event]"; break; + } + log_debug ("*** RFC822 event %s\n", s); +} + + +/* Do in-place decoding of quoted-printable data of LENGTH in BUFFER. + Returns the new length of the buffer and stores true at R_SLBRK if + the line ended with a soft line break; false is stored if not. + This function asssumes that a complete line is passed in + buffer. */ +static size_t +qp_decode (char *buffer, size_t length, int *r_slbrk) +{ + char *d, *s; + + if (r_slbrk) + *r_slbrk = 0; + + /* Fixme: We should remove trailing white space first. */ + for (s=d=buffer; length; length--) + { + if (*s == '=') + { + if (length > 2 && hexdigitp (s+1) && hexdigitp (s+2)) + { + s++; + *(unsigned char*)d++ = xtoi_2 (s); + s += 2; + length -= 2; + } + else if (length > 2 && s[1] == '\r' && s[2] == '\n') + { + /* Soft line break. */ + s += 3; + length -= 2; + if (r_slbrk && length == 1) + *r_slbrk = 1; + } + else if (length > 1 && s[1] == '\n') + { + /* Soft line break with only a Unix line terminator. */ + s += 2; + length -= 1; + if (r_slbrk && length == 1) + *r_slbrk = 1; + } + else if (length == 1) + { + /* Soft line break at the end of the line. */ + s += 1; + if (r_slbrk) + *r_slbrk = 1; + } + else + *d++ = *s++; + } + else + *d++ = *s++; + } + + return d - buffer; +} + + +/* This function is called by parse_mail to communicate events. This + * callback communicates with the caller using a structure passed in + * OPAQUE. Should return 0 on success or set ERRNO and return -1. */ +static int +parse_message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg) +{ + mime_parser_t ctx = opaque; + const char *s; + int rc = 0; + + /* Make the RFC822 parser context availabale for callbacks. */ + ctx->msg = msg; + + if (ctx->debug) + show_message_parser_event (event); + + if (event == RFC822PARSE_BEGIN_HEADER || event == RFC822PARSE_T2BODY) + { + /* We need to check here whether to start collecting signed data + * because attachments might come without header lines and thus + * we won't see the BEGIN_HEADER event. */ + if (ctx->pgpmime == PGPMIME_WAIT_SIGNEDDATA) + { + if (ctx->debug) + log_debug ("begin_hash\n"); + ctx->hashing_at_level = ctx->nesting_level; + ctx->pgpmime = PGPMIME_IN_SIGNEDDATA; + ctx->delay_hashing = 0; + } + } + + if (event == RFC822PARSE_OPEN) + { + /* Initialize for a new message. */ + ctx->show.header = 1; + } + else if (event == RFC822PARSE_T2BODY) + { + rfc822parse_field_t field; + + ctx->want_part = 0; + ctx->decode_part = 0; + + if (ctx->t2body) + { + rc = ctx->t2body (ctx->cookie, ctx->nesting_level); + if (rc) + goto t2body_leave; + } + + field = rfc822parse_parse_field (msg, "Content-Type", -1); + if (field) + { + const char *s1, *s2; + + s1 = rfc822parse_query_media_type (field, &s2); + if (s1) + { + if (ctx->verbose) + log_debug ("h media: %*s%s %s\n", + ctx->nesting_level*2, "", s1, s2); + if (ctx->pgpmime == PGPMIME_WAIT_ENCVERSION) + { + if (!strcmp (s1, "application") + && !strcmp (s2, "pgp-encrypted")) + { + if (ctx->debug) + log_debug ("c begin_encversion\n"); + ctx->pgpmime = PGPMIME_IN_ENCVERSION; + } + else + { + log_error ("invalid PGP/MIME structure;" + " expected '%s', got '%s/%s'\n", + "application/pgp-encrypted", s1, s2); + ctx->pgpmime = PGPMIME_INVALID; + } + } + else if (ctx->pgpmime == PGPMIME_WAIT_ENCDATA) + { + if (!strcmp (s1, "application") + && !strcmp (s2, "octet-stream")) + { + if (ctx->debug) + log_debug ("c begin_encdata\n"); + ctx->pgpmime = PGPMIME_IN_ENCDATA; + } + else + { + log_error ("invalid PGP/MIME structure;" + " expected '%s', got '%s/%s'\n", + "application/octet-stream", s1, s2); + ctx->pgpmime = PGPMIME_INVALID; + } + } + else if (ctx->pgpmime == PGPMIME_WAIT_SIGNATURE) + { + if (!strcmp (s1, "application") + && !strcmp (s2, "pgp-signature")) + { + if (ctx->debug) + log_debug ("c begin_signature\n"); + ctx->pgpmime = PGPMIME_IN_SIGNATURE; + } + else + { + log_error ("invalid PGP/MIME structure;" + " expected '%s', got '%s/%s'\n", + "application/pgp-signature", s1, s2); + ctx->pgpmime = PGPMIME_INVALID; + } + } + else if (!strcmp (s1, "multipart") + && !strcmp (s2, "encrypted")) + { + s = rfc822parse_query_parameter (field, "protocol", 0); + if (s) + { + if (ctx->debug) + log_debug ("h encrypted.protocol: %s\n", s); + if (!strcmp (s, "application/pgp-encrypted")) + { + if (ctx->pgpmime) + log_error ("note: " + "ignoring nested PGP/MIME signature\n"); + else + ctx->pgpmime = PGPMIME_WAIT_ENCVERSION; + } + else if (ctx->verbose) + log_debug ("# this protocol is not supported\n"); + } + } + else if (!strcmp (s1, "multipart") + && !strcmp (s2, "signed")) + { + s = rfc822parse_query_parameter (field, "protocol", 1); + if (s) + { + if (ctx->debug) + log_debug ("h signed.protocol: %s\n", s); + if (!strcmp (s, "application/pgp-signature")) + { + if (ctx->pgpmime) + log_error ("note: " + "ignoring nested PGP/MIME signature\n"); + else + ctx->pgpmime = PGPMIME_WAIT_SIGNEDDATA; + } + else if (ctx->verbose) + log_debug ("# this protocol is not supported\n"); + } + } + else if (ctx->new_part) + { + ctx->err = ctx->new_part (ctx->cookie, s1, s2); + if (!ctx->err) + ctx->want_part = 1; + else if (gpg_err_code (ctx->err) == GPG_ERR_FALSE) + ctx->err = 0; + else if (gpg_err_code (ctx->err) == GPG_ERR_TRUE) + { + ctx->want_part = ctx->decode_part = 1; + ctx->err = 0; + } + } + } + else + { + if (ctx->debug) + log_debug ("h media: %*s none\n", ctx->nesting_level*2, ""); + if (ctx->new_part) + { + ctx->err = ctx->new_part (ctx->cookie, "", ""); + if (!ctx->err) + ctx->want_part = 1; + else if (gpg_err_code (ctx->err) == GPG_ERR_FALSE) + ctx->err = 0; + else if (gpg_err_code (ctx->err) == GPG_ERR_TRUE) + { + ctx->want_part = ctx->decode_part = 1; + ctx->err = 0; + } + } + } + + rfc822parse_release_field (field); + } + else + { + if (ctx->verbose) + log_debug ("h media: %*stext plain [assumed]\n", + ctx->nesting_level*2, ""); + if (ctx->new_part) + { + ctx->err = ctx->new_part (ctx->cookie, "text", "plain"); + if (!ctx->err) + ctx->want_part = 1; + else if (gpg_err_code (ctx->err) == GPG_ERR_FALSE) + ctx->err = 0; + else if (gpg_err_code (ctx->err) == GPG_ERR_TRUE) + { + ctx->want_part = ctx->decode_part = 1; + ctx->err = 0; + } + } + } + + /* Figure out the encoding if needed. */ + if (ctx->decode_part) + { + char *value; + size_t valueoff; + + ctx->decode_part = 0; /* Fallback for unknown encoding. */ + value = rfc822parse_get_field (msg, "Content-Transfer-Encoding", -1, + &valueoff); + if (value) + { + if (!stricmp (value+valueoff, "quoted-printable")) + ctx->decode_part = 1; + else if (!stricmp (value+valueoff, "base64")) + { + ctx->decode_part = 2; + if (ctx->b64state) + b64dec_finish (ctx->b64state); /* Reuse state. */ + else + { + ctx->b64state = xtrymalloc (sizeof *ctx->b64state); + if (!ctx->b64state) + rc = gpg_error_from_syserror (); + } + if (!rc) + rc = b64dec_start (ctx->b64state, NULL); + } + free (value); /* Right, we need a plain free. */ + } + } + + t2body_leave: + ctx->show.header = 0; + ctx->show.data = 1; + ctx->show.n_skip = 1; + } + else if (event == RFC822PARSE_PREAMBLE) + ctx->show.as_note = 1; + else if (event == RFC822PARSE_LEVEL_DOWN) + { + if (ctx->debug) + log_debug ("b down\n"); + ctx->nesting_level++; + } + else if (event == RFC822PARSE_LEVEL_UP) + { + if (ctx->debug) + log_debug ("b up\n"); + if (ctx->nesting_level) + ctx->nesting_level--; + else + log_error ("invalid structure (bad nesting level)\n"); + } + else if (event == RFC822PARSE_BOUNDARY || event == RFC822PARSE_LAST_BOUNDARY) + { + ctx->show.data = 0; + ctx->show.boundary = 1; + if (event == RFC822PARSE_BOUNDARY) + { + ctx->show.header = 1; + ctx->show.n_skip = 1; + if (ctx->debug) + log_debug ("b part\n"); + } + else if (ctx->debug) + log_debug ("b last\n"); + + if (ctx->pgpmime == PGPMIME_IN_ENCDATA) + { + if (ctx->debug) + log_debug ("c end_encdata\n"); + ctx->pgpmime = PGPMIME_GOT_ENCDATA; + /* FIXME: We should assert (event == LAST_BOUNDARY). */ + } + else if (ctx->pgpmime == PGPMIME_IN_SIGNEDDATA + && ctx->nesting_level == ctx->hashing_at_level) + { + if (ctx->debug) + log_debug ("c end_hash\n"); + ctx->pgpmime = PGPMIME_WAIT_SIGNATURE; + if (ctx->collect_signeddata) + ctx->err = ctx->collect_signeddata (ctx->cookie, NULL); + } + else if (ctx->pgpmime == PGPMIME_IN_SIGNATURE) + { + if (ctx->debug) + log_debug ("c end_signature\n"); + ctx->pgpmime = PGPMIME_GOT_SIGNATURE; + /* FIXME: We should assert (event == LAST_BOUNDARY). */ + } + else if (ctx->want_part) + { + if (ctx->part_data) + { + /* FIXME: We may need to flush things. */ + ctx->err = ctx->part_data (ctx->cookie, NULL, 0); + } + ctx->want_part = 0; + } + } + + ctx->msg = NULL; + + return rc; +} + + +/* Create a new mime parser object. COOKIE is a values which will be + * used as first argument for all callbacks registered with this + * parser object. */ +gpg_error_t +mime_parser_new (mime_parser_t *r_parser, void *cookie) +{ + mime_parser_t ctx; + + *r_parser = NULL; + + ctx = xtrycalloc (1, sizeof *ctx); + if (!ctx) + return gpg_error_from_syserror (); + ctx->cookie = cookie; + + *r_parser = ctx; + return 0; +} + + +/* Release a mime parser object. */ +void +mime_parser_release (mime_parser_t ctx) +{ + if (!ctx) + return; + + if (ctx->b64state) + { + b64dec_finish (ctx->b64state); + xfree (ctx->b64state); + } + xfree (ctx); +} + + +/* Set verbose and debug mode. */ +void +mime_parser_set_verbose (mime_parser_t ctx, int level) +{ + if (!level) + { + ctx->verbose = 0; + ctx->debug = 0; + } + else + { + ctx->verbose = 1; + if (level > 10) + ctx->debug = 1; + } +} + + +/* Set a callback for the transition from header to body. LEVEL is + * the current nesting level, starting with 0. This callback can be + * used to evaluate headers before any other action is done. Note + * that if a new NEW_PART callback needs to be called it is done after + * this T2BODY callback. */ +void +mime_parser_set_t2body (mime_parser_t ctx, + gpg_error_t (*fnc) (void *cookie, int level)) +{ + ctx->t2body = fnc; +} + + +/* Set the callback used to announce a new part. It will be called + * with the media type and media subtype of the part. If no + * Content-type header was given both values are the empty string. + * The callback should return 0 on success or an error code. The + * error code GPG_ERR_FALSE indicates that the caller is not + * interested in the part and data shall not be returned via a + * registered part_data callback. The error code GPG_ERR_TRUE + * indicates that the parts shall be redurned in decoded format + * (i.e. base64 or QP encoding is removed). */ +void +mime_parser_set_new_part (mime_parser_t ctx, + gpg_error_t (*fnc) (void *cookie, + const char *mediatype, + const char *mediasubtype)) +{ + ctx->new_part = fnc; +} + + +/* Set the callback used to return the data of a part to the caller. + * The end of the part is indicated by passing NUL for DATA. */ +void +mime_parser_set_part_data (mime_parser_t ctx, + gpg_error_t (*fnc) (void *cookie, + const void *data, + size_t datalen)) +{ + ctx->part_data = fnc; +} + + +/* Set the callback to collect encrypted data. A NULL passed to the + * callback indicates the end of the encrypted data; the callback may + * then decrypt the collected data. */ +void +mime_parser_set_collect_encrypted (mime_parser_t ctx, + gpg_error_t (*fnc) (void *cookie, + const char *data)) +{ + ctx->collect_encrypted = fnc; +} + + +/* Set the callback to collect signed data. A NULL passed to the + * callback indicates the end of the signed data. */ +void +mime_parser_set_collect_signeddata (mime_parser_t ctx, + gpg_error_t (*fnc) (void *cookie, + const char *data)) +{ + ctx->collect_signeddata = fnc; +} + + +/* Set the callback to collect the signature. A NULL passed to the + * callback indicates the end of the signature; the callback may the + * verify the signature. */ +void +mime_parser_set_collect_signature (mime_parser_t ctx, + gpg_error_t (*fnc) (void *cookie, + const char *data)) +{ + ctx->collect_signature = fnc; +} + + +/* Return the RFC888 parser context. This is only available inside a + * callback. */ +rfc822parse_t +mime_parser_rfc822parser (mime_parser_t ctx) +{ + return ctx->msg; +} + + +/* Helper for mime_parser_parse. */ +static gpg_error_t +process_part_data (mime_parser_t ctx, char *line, size_t *length) +{ + gpg_error_t err; + size_t nbytes; + + if (!ctx->want_part) + return 0; + if (!ctx->part_data) + return 0; + + if (ctx->decode_part == 1) + { + *length = qp_decode (line, *length, NULL); + } + else if (ctx->decode_part == 2) + { + log_assert (ctx->b64state); + err = b64dec_proc (ctx->b64state, line, *length, &nbytes); + if (err) + return err; + *length = nbytes; + } + + return ctx->part_data (ctx->cookie, line, *length); +} + + +/* Read and parse a message from FP and call the appropriate + * callbacks. */ +gpg_error_t +mime_parser_parse (mime_parser_t ctx, estream_t fp) +{ + gpg_error_t err; + rfc822parse_t msg = NULL; + unsigned int lineno = 0; + size_t length; + char *line; + + line = ctx->line; + + msg = rfc822parse_open (parse_message_cb, ctx); + if (!msg) + { + err = gpg_error_from_syserror (); + log_error ("can't open mail parser: %s", gpg_strerror (err)); + goto leave; + } + + /* Fixme: We should not use fgets because it can't cope with + embedded nul characters. */ + while (es_fgets (ctx->line, sizeof (ctx->line), fp)) + { + lineno++; + if (lineno == 1 && !strncmp (line, "From ", 5)) + continue; /* We better ignore a leading From line. */ + + length = strlen (line); + if (length && line[length - 1] == '\n') + line[--length] = 0; + else + log_error ("mail parser detected too long or" + " non terminated last line (lnr=%u)\n", lineno); + if (length && line[length - 1] == '\r') + line[--length] = 0; + + ctx->err = 0; + if (rfc822parse_insert (msg, line, length)) + { + err = gpg_error_from_syserror (); + log_error ("mail parser failed: %s", gpg_strerror (err)); + goto leave; + } + if (ctx->err) + { + /* Error from a callback detected. */ + err = ctx->err; + goto leave; + } + + + /* Debug output. Note that the boundary is shown before n_skip + * is evaluated. */ + if (ctx->show.boundary) + { + if (ctx->debug) + log_debug ("# Boundary: %s\n", line); + ctx->show.boundary = 0; + } + if (ctx->show.n_skip) + ctx->show.n_skip--; + else if (ctx->show.data) + { + if (ctx->show.as_note) + { + if (ctx->verbose) + log_debug ("# Note: %s\n", line); + ctx->show.as_note = 0; + } + else if (ctx->debug) + log_debug ("# Data: %s\n", line); + } + else if (ctx->show.header && ctx->verbose) + log_debug ("# Header: %s\n", line); + + if (ctx->pgpmime == PGPMIME_IN_ENCVERSION) + { + trim_trailing_spaces (line); + if (!*line) + ; /* Skip empty lines. */ + else if (!strcmp (line, "Version: 1")) + ctx->pgpmime = PGPMIME_WAIT_ENCDATA; + else + { + log_error ("invalid PGP/MIME structure;" + " garbage in pgp-encrypted part ('%s')\n", line); + ctx->pgpmime = PGPMIME_INVALID; + } + } + else if (ctx->pgpmime == PGPMIME_IN_ENCDATA) + { + if (ctx->collect_encrypted) + { + err = ctx->collect_encrypted (ctx->cookie, line); + if (!err) + err = ctx->collect_encrypted (ctx->cookie, "\r\n"); + if (err) + goto leave; + } + } + else if (ctx->pgpmime == PGPMIME_GOT_ENCDATA) + { + ctx->pgpmime = PGPMIME_NONE; + if (ctx->collect_encrypted) + ctx->collect_encrypted (ctx->cookie, NULL); + } + else if (ctx->pgpmime == PGPMIME_IN_SIGNEDDATA) + { + /* If we are processing signed data, store the signed data. + * We need to delay the hashing of the CR/LF because the + * last line ending belongs to the next boundary. This is + * the reason why we can't use the PGPMIME state as a + * condition. */ + if (ctx->debug) + log_debug ("# hashing %s'%s'\n", + ctx->delay_hashing? "CR,LF+":"", line); + if (ctx->collect_signeddata) + { + if (ctx->delay_hashing) + ctx->collect_signeddata (ctx->cookie, "\r\n"); + ctx->collect_signeddata (ctx->cookie, line); + } + ctx->delay_hashing = 1; + + err = process_part_data (ctx, line, &length); + if (err) + goto leave; + } + else if (ctx->pgpmime == PGPMIME_IN_SIGNATURE) + { + if (ctx->collect_signeddata) + { + ctx->collect_signature (ctx->cookie, line); + ctx->collect_signature (ctx->cookie, "\r\n"); + } + } + else if (ctx->pgpmime == PGPMIME_GOT_SIGNATURE) + { + ctx->pgpmime = PGPMIME_NONE; + if (ctx->collect_signeddata) + ctx->collect_signature (ctx->cookie, NULL); + } + else + { + err = process_part_data (ctx, line, &length); + if (err) + goto leave; + } + } + + rfc822parse_close (msg); + msg = NULL; + err = 0; + + leave: + rfc822parse_cancel (msg); + return err; +} diff --git a/tools/mime-parser.h b/tools/mime-parser.h new file mode 100644 index 0000000..4152966 --- /dev/null +++ b/tools/mime-parser.h @@ -0,0 +1,60 @@ +/* mime-parser.h - Parse MIME structures (high level rfc822 parser). + * Copyright (C) 2016 g10 Code GmbH + * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef GNUPG_MIME_PARSER_H +#define GNUPG_MIME_PARSER_H + +#include "rfc822parse.h" + +struct mime_parser_context_s; +typedef struct mime_parser_context_s *mime_parser_t; + +gpg_error_t mime_parser_new (mime_parser_t *r_ctx, void *cookie); +void mime_parser_release (mime_parser_t ctx); + +void mime_parser_set_verbose (mime_parser_t ctx, int level); +void mime_parser_set_t2body (mime_parser_t ctx, + gpg_error_t (*fnc) (void *cookie, int level)); +void mime_parser_set_new_part (mime_parser_t ctx, + gpg_error_t (*fnc) (void *cookie, + const char *mediatype, + const char *mediasubtype)); +void mime_parser_set_part_data (mime_parser_t ctx, + gpg_error_t (*fnc) (void *cookie, + const void *data, + size_t datalen)); +void mime_parser_set_collect_encrypted (mime_parser_t ctx, + gpg_error_t (*fnc) (void *cookie, + const char *data)); +void mime_parser_set_collect_signeddata (mime_parser_t ctx, + gpg_error_t (*fnc) (void *cookie, + const char *data)); +void mime_parser_set_collect_signature (mime_parser_t ctx, + gpg_error_t (*fnc) (void *cookie, + const char *data)); + +gpg_error_t mime_parser_parse (mime_parser_t ctx, estream_t fp); + + +rfc822parse_t mime_parser_rfc822parser (mime_parser_t ctx); + + + +#endif /*GNUPG_MIME_PARSER_H*/ diff --git a/tools/no-libgcrypt.c b/tools/no-libgcrypt.c new file mode 100644 index 0000000..8739968 --- /dev/null +++ b/tools/no-libgcrypt.c @@ -0,0 +1,162 @@ +/* no-libgcrypt.c - Replacement functions for libgcrypt. + * Copyright (C) 2003 Free Software Foundation, Inc. + * + * This file is free software; as a special exception the author gives + * unlimited permission to copy and/or distribute it, with or without + * modifications, as long as this notice is preserved. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY, to the extent permitted by law; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "../common/util.h" +#include "../common/i18n.h" + + +/* Replace libgcrypt's malloc functions which are used by + ../common/libcommon.a . ../common/util.h defines macros to map them + to xmalloc etc. */ +static void +out_of_memory (void) +{ + log_fatal (_("error allocating enough memory: %s\n"), strerror (errno)); +} + + +void * +gcry_malloc (size_t n) +{ + return malloc (n); +} + +void * +gcry_malloc_secure (size_t n) +{ + return malloc (n); +} + +void * +gcry_xmalloc (size_t n) +{ + void *p = malloc (n); + if (!p) + out_of_memory (); + return p; +} + +char * +gcry_strdup (const char *string) +{ + char *p = malloc (strlen (string)+1); + if (p) + strcpy (p, string); + return p; +} + + +void * +gcry_realloc (void *a, size_t n) +{ + return realloc (a, n); +} + +void * +gcry_xrealloc (void *a, size_t n) +{ + void *p = realloc (a, n); + if (!p) + out_of_memory (); + return p; +} + + + +void * +gcry_calloc (size_t n, size_t m) +{ + return calloc (n, m); +} + +void * +gcry_xcalloc (size_t n, size_t m) +{ + void *p = calloc (n, m); + if (!p) + out_of_memory (); + return p; +} + + +char * +gcry_xstrdup (const char *string) +{ + void *p = malloc (strlen (string)+1); + if (!p) + out_of_memory (); + strcpy( p, string ); + return p; +} + +void +gcry_free (void *a) +{ + if (a) + free (a); +} + + +/* We need this dummy because exechelp.c uses gcry_control to + terminate the secure memeory. */ +gcry_error_t +gcry_control (enum gcry_ctl_cmds cmd, ...) +{ + (void)cmd; + return 0; +} + +void +gcry_set_outofcore_handler (gcry_handler_no_mem_t h, void *opaque) +{ + (void)h; + (void)opaque; +} + +void +gcry_set_fatalerror_handler (gcry_handler_error_t fnc, void *opaque) +{ + (void)fnc; + (void)opaque; +} + +void +gcry_set_log_handler (gcry_handler_log_t f, void *opaque) +{ + (void)f; + (void)opaque; +} + + +void +gcry_create_nonce (void *buffer, size_t length) +{ + (void)buffer; + (void)length; + + log_fatal ("unexpected call to gcry_create_nonce\n"); +} + + +const char * +gcry_cipher_algo_name (int algo) +{ + (void)algo; + return "?"; +} diff --git a/tools/rfc822parse.c b/tools/rfc822parse.c new file mode 100644 index 0000000..0a4e2bc --- /dev/null +++ b/tools/rfc822parse.c @@ -0,0 +1,1331 @@ +/* rfc822parse.c - Simple mail and MIME parser + * Copyright (C) 1999, 2000 Werner Koch, Duesseldorf + * Copyright (C) 2003, 2004 g10 Code GmbH + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + + +/* According to RFC822 binary zeroes are allowed at many places. We do + * not handle this correct especially in the field parsing code. It + * should be easy to fix and the API provides a interfaces which + * returns the length but in addition makes sure that returned strings + * are always ended by a \0. + * + * Furthermore, the case of field names is changed and thus it is not + * always a good idea to use these modified header + * lines (e.g. signatures may break). + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdarg.h> +#include <assert.h> + +#include "rfc822parse.h" + +/* All valid characters in a header name. */ +#define HEADER_NAME_CHARS ("abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "-01234567890") + + +enum token_type + { + tSPACE, + tATOM, + tQUOTED, + tDOMAINLIT, + tSPECIAL + }; + +/* For now we directly use our TOKEN as the parse context */ +typedef struct rfc822parse_field_context *TOKEN; +struct rfc822parse_field_context +{ + TOKEN next; + enum token_type type; + struct { + unsigned int cont:1; + unsigned int lowered:1; + } flags; + /*TOKEN owner_pantry; */ + char data[1]; +}; + +struct hdr_line +{ + struct hdr_line *next; + int cont; /* This is a continuation of the previous line. */ + unsigned char line[1]; +}; + +typedef struct hdr_line *HDR_LINE; + + +struct part +{ + struct part *right; /* The next part. */ + struct part *down; /* A contained part. */ + HDR_LINE hdr_lines; /* Header lines os that part. */ + HDR_LINE *hdr_lines_tail; /* Helper for adding lines. */ + char *boundary; /* Only used in the first part. */ +}; +typedef struct part *part_t; + +struct rfc822parse_context +{ + rfc822parse_cb_t callback; + void *callback_value; + int callback_error; + int in_body; + int in_preamble; /* Wether we are before the first boundary. */ + part_t parts; /* The tree of parts. */ + part_t current_part; /* Whom we are processing (points into parts). */ + const char *boundary; /* Current boundary. */ +}; + +static HDR_LINE find_header (rfc822parse_t msg, const char *name, + int which, HDR_LINE * rprev); + + +static size_t +length_sans_trailing_ws (const unsigned char *line, size_t len) +{ + const unsigned char *p, *mark; + size_t n; + + for (mark=NULL, p=line, n=0; n < len; n++, p++) + { + if (strchr (" \t\r\n", *p )) + { + if( !mark ) + mark = p; + } + else + mark = NULL; + } + + if (mark) + return mark - line; + return len; +} + + +static void +lowercase_string (unsigned char *string) +{ + for (; *string; string++) + if (*string >= 'A' && *string <= 'Z') + *string = *string - 'A' + 'a'; +} + + +static int +my_toupper (int c) +{ + if (c >= 'a' && c <= 'z') + c &= ~0x20; + return c; +} + +/* This is the same as ascii_strcasecmp. */ +static int +my_strcasecmp (const char *a, const char *b) +{ + if (a == b) + return 0; + + for (; *a && *b; a++, b++) + { + if (*a != *b && my_toupper(*a) != my_toupper(*b)) + break; + } + return *a == *b? 0 : (my_toupper (*a) - my_toupper (*b)); +} + + +#ifndef HAVE_STPCPY +static char * +my_stpcpy (char *a,const char *b) +{ + while (*b) + *a++ = *b++; + *a = 0; + + return (char*)a; +} +#define stpcpy my_stpcpy +#endif + + +/* If a callback has been registerd, call it for the event of type + EVENT. */ +static int +do_callback (rfc822parse_t msg, rfc822parse_event_t event) +{ + int rc; + + if (!msg->callback || msg->callback_error) + return 0; + rc = msg->callback (msg->callback_value, event, msg); + if (rc) + msg->callback_error = rc; + return rc; +} + +static part_t +new_part (void) +{ + part_t part; + + part = calloc (1, sizeof *part); + if (part) + { + part->hdr_lines_tail = &part->hdr_lines; + } + return part; +} + + +static void +release_part (part_t part) +{ + part_t tmp; + HDR_LINE hdr, hdr2; + + for (; part; part = tmp) + { + tmp = part->right; + if (part->down) + release_part (part->down); + for (hdr = part->hdr_lines; hdr; hdr = hdr2) + { + hdr2 = hdr->next; + free (hdr); + } + free (part->boundary); + free (part); + } +} + + +static void +release_handle_data (rfc822parse_t msg) +{ + release_part (msg->parts); + msg->parts = NULL; + msg->current_part = NULL; + msg->boundary = NULL; +} + + +/* Check that the header name is valid. We allow all lower and + * uppercase letters and, except for the first character, digits and + * the dash. The check stops at the first colon or at string end. + * Returns true if the name is valid. */ +int +rfc822_valid_header_name_p (const char *name) +{ + const char *s; + size_t namelen; + + if ((s=strchr (name, ':'))) + namelen = s - name; + else + namelen = strlen (name); + + if (!namelen + || strspn (name, HEADER_NAME_CHARS) != namelen + || strchr ("-0123456789", *name)) + return 0; + return 1; +} + + +/* Transform a header NAME into a standard capitalized format. + * Conversion stops at the colon. */ +void +rfc822_capitalize_header_name (char *name) +{ + unsigned char *p = name; + int first = 1; + + /* Special cases first. */ + if (!my_strcasecmp (name, "MIME-Version")) + { + strcpy (name, "MIME-Version"); + return; + } + + /* Regular cases. */ + for (; *p && *p != ':'; p++) + { + if (*p == '-') + first = 1; + else if (first) + { + if (*p >= 'a' && *p <= 'z') + *p = *p - 'a' + 'A'; + first = 0; + } + else if (*p >= 'A' && *p <= 'Z') + *p = *p - 'A' + 'a'; + } +} + + + +/* Create a new parsing context for an entire rfc822 message and + return it. CB and CB_VALUE may be given to callback for certain + events. NULL is returned on error with errno set appropriately. */ +rfc822parse_t +rfc822parse_open (rfc822parse_cb_t cb, void *cb_value) +{ + rfc822parse_t msg = calloc (1, sizeof *msg); + if (msg) + { + msg->parts = msg->current_part = new_part (); + if (!msg->parts) + { + free (msg); + msg = NULL; + } + else + { + msg->callback = cb; + msg->callback_value = cb_value; + if (do_callback (msg, RFC822PARSE_OPEN)) + { + release_handle_data (msg); + free (msg); + msg = NULL; + } + } + } + return msg; +} + + +void +rfc822parse_cancel (rfc822parse_t msg) +{ + if (msg) + { + do_callback (msg, RFC822PARSE_CANCEL); + release_handle_data (msg); + free (msg); + } +} + + +void +rfc822parse_close (rfc822parse_t msg) +{ + if (msg) + { + do_callback (msg, RFC822PARSE_CLOSE); + release_handle_data (msg); + free (msg); + } +} + +static part_t +find_parent (part_t tree, part_t target) +{ + part_t part; + + for (part = tree->down; part; part = part->right) + { + if (part == target) + return tree; /* Found. */ + if (part->down) + { + part_t tmp = find_parent (part, target); + if (tmp) + return tmp; + } + } + return NULL; +} + +static void +set_current_part_to_parent (rfc822parse_t msg) +{ + part_t parent; + + assert (msg->current_part); + parent = find_parent (msg->parts, msg->current_part); + if (!parent) + return; /* Already at the top. */ + +#ifndef NDEBUG + { + part_t part; + for (part = parent->down; part; part = part->right) + if (part == msg->current_part) + break; + assert (part); + } +#endif + msg->current_part = parent; + + parent = find_parent (msg->parts, parent); + msg->boundary = parent? parent->boundary: NULL; +} + + + +/**************** + * We have read in all header lines and are about to receive the body + * part. The delimiter line has already been processed. + * + * FIXME: we's better return an error in case of memory failures. + */ +static int +transition_to_body (rfc822parse_t msg) +{ + rfc822parse_field_t ctx; + int rc; + + rc = do_callback (msg, RFC822PARSE_T2BODY); + if (!rc) + { + /* Store the boundary if we have multipart type. */ + ctx = rfc822parse_parse_field (msg, "Content-Type", -1); + if (ctx) + { + const char *s; + + s = rfc822parse_query_media_type (ctx, NULL); + if (s && !strcmp (s,"multipart")) + { + s = rfc822parse_query_parameter (ctx, "boundary", 0); + if (s) + { + assert (!msg->current_part->boundary); + msg->current_part->boundary = malloc (strlen (s) + 1); + if (msg->current_part->boundary) + { + part_t part; + + strcpy (msg->current_part->boundary, s); + msg->boundary = msg->current_part->boundary; + part = new_part (); + if (!part) + { + int save_errno = errno; + rfc822parse_release_field (ctx); + errno = save_errno; + return -1; + } + rc = do_callback (msg, RFC822PARSE_LEVEL_DOWN); + assert (!msg->current_part->down); + msg->current_part->down = part; + msg->current_part = part; + msg->in_preamble = 1; + } + } + } + rfc822parse_release_field (ctx); + } + } + + return rc; +} + +/* We have just passed a MIME boundary and need to prepare for new part. + headers. */ +static int +transition_to_header (rfc822parse_t msg) +{ + part_t part; + + assert (msg->current_part); + assert (!msg->current_part->right); + + part = new_part (); + if (!part) + return -1; + + msg->current_part->right = part; + msg->current_part = part; + return 0; +} + + +static int +insert_header (rfc822parse_t msg, const unsigned char *line, size_t length) +{ + HDR_LINE hdr; + + assert (msg->current_part); + if (!length) + { + msg->in_body = 1; + return transition_to_body (msg); + } + + if (!msg->current_part->hdr_lines) + do_callback (msg, RFC822PARSE_BEGIN_HEADER); + + length = length_sans_trailing_ws (line, length); + hdr = malloc (sizeof (*hdr) + length); + if (!hdr) + return -1; + hdr->next = NULL; + hdr->cont = (*line == ' ' || *line == '\t'); + memcpy (hdr->line, line, length); + hdr->line[length] = 0; /* Make it a string. */ + + /* Transform a field name into canonical format. */ + if (!hdr->cont && strchr (line, ':')) + rfc822_capitalize_header_name (hdr->line); + + *msg->current_part->hdr_lines_tail = hdr; + msg->current_part->hdr_lines_tail = &hdr->next; + + /* Lets help the caller to prevent mail loops and issue an event for + * every Received header. */ + if (length >= 9 && !memcmp (line, "Received:", 9)) + do_callback (msg, RFC822PARSE_RCVD_SEEN); + return 0; +} + + +/**************** + * Note: We handle the body transparent to allow binary zeroes in it. + */ +static int +insert_body (rfc822parse_t msg, const unsigned char *line, size_t length) +{ + int rc = 0; + + if (length > 2 && *line == '-' && line[1] == '-' && msg->boundary) + { + size_t blen = strlen (msg->boundary); + + if (length == blen + 2 + && !memcmp (line+2, msg->boundary, blen)) + { + rc = do_callback (msg, RFC822PARSE_BOUNDARY); + msg->in_body = 0; + if (!rc && !msg->in_preamble) + rc = transition_to_header (msg); + msg->in_preamble = 0; + } + else if (length == blen + 4 + && line[length-2] =='-' && line[length-1] == '-' + && !memcmp (line+2, msg->boundary, blen)) + { + rc = do_callback (msg, RFC822PARSE_LAST_BOUNDARY); + msg->boundary = NULL; /* No current boundary anymore. */ + set_current_part_to_parent (msg); + + /* Fixme: The next should actually be send right before the + next boundary, so that we can mark the epilogue. */ + if (!rc) + rc = do_callback (msg, RFC822PARSE_LEVEL_UP); + } + } + if (msg->in_preamble && !rc) + rc = do_callback (msg, RFC822PARSE_PREAMBLE); + + return rc; +} + +/* Insert the next line into the parser. Return 0 on success or true + on error with errno set appropriately. */ +int +rfc822parse_insert (rfc822parse_t msg, const unsigned char *line, size_t length) +{ + return (msg->in_body + ? insert_body (msg, line, length) + : insert_header (msg, line, length)); +} + + +/* Tell the parser that we have finished the message. */ +int +rfc822parse_finish (rfc822parse_t msg) +{ + return do_callback (msg, RFC822PARSE_FINISH); +} + + + +/**************** + * Get a copy of a header line. The line is returned as one long + * string with LF to separate the continuation line. Caller must free + * the return buffer. WHICH may be used to enumerate over all lines. + * Wildcards are allowed. This function works on the current headers; + * i.e. the regular mail headers or the MIME headers of the current + * part. + * + * WHICH gives the mode: + * -1 := Take the last occurrence + * n := Take the n-th one. + * + * Returns a newly allocated buffer or NULL on error. errno is set in + * case of a memory failure or set to 0 if the requested field is not + * available. + * + * If VALUEOFF is not NULL it will receive the offset of the first non + * space character in the value part of the line (i.e. after the first + * colon). + */ +char * +rfc822parse_get_field (rfc822parse_t msg, const char *name, int which, + size_t *valueoff) +{ + HDR_LINE h, h2; + char *buf, *p; + size_t n; + + h = find_header (msg, name, which, NULL); + if (!h) + { + errno = 0; + return NULL; /* no such field */ + } + + n = strlen (h->line) + 1; + for (h2 = h->next; h2 && h2->cont; h2 = h2->next) + n += strlen (h2->line) + 1; + + buf = p = malloc (n); + if (buf) + { + p = stpcpy (p, h->line); + *p++ = '\n'; + for (h2 = h->next; h2 && h2->cont; h2 = h2->next) + { + p = stpcpy (p, h2->line); + *p++ = '\n'; + } + p[-1] = 0; + } + + if (valueoff) + { + p = strchr (buf, ':'); + if (!p) + *valueoff = 0; /* Oops: should never happen. */ + else + { + p++; + while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') + p++; + *valueoff = p - buf; + } + } + + return buf; +} + + +/**************** + * Enumerate all header. Caller has to provide the address of a pointer + * which has to be initialzed to NULL, the caller should then never change this + * pointer until he has closed the enumeration by passing again the address + * of the pointer but with msg set to NULL. + * The function returns pointers to all the header lines or NULL when + * all lines have been enumerated or no headers are available. + */ +const char * +rfc822parse_enum_header_lines (rfc822parse_t msg, void **context) +{ + HDR_LINE l; + + if (!msg) /* Close. */ + return NULL; + + if (*context == msg || !msg->current_part) + return NULL; + + l = *context ? (HDR_LINE) *context : msg->current_part->hdr_lines; + + if (l) + { + *context = l->next ? (void *) (l->next) : (void *) msg; + return l->line; + } + *context = msg; /* Mark end of list. */ + return NULL; +} + + + +/**************** + * Find a header field. If the Name does end in an asterisk this is meant + * to be a wildcard. + * + * which -1 : Retrieve the last field + * >0 : Retrieve the n-th field + + * RPREV may be used to return the predecessor of the returned field; + * which may be NULL for the very first one. It has to be initialzed + * to either NULL in which case the search start at the first header line, + * or it may point to a headerline, where the search should start + */ +static HDR_LINE +find_header (rfc822parse_t msg, const char *name, int which, HDR_LINE *rprev) +{ + HDR_LINE hdr, prev = NULL, mark = NULL; + unsigned char *p; + size_t namelen, n; + int found = 0; + int glob = 0; + + if (!msg->current_part) + return NULL; + + namelen = strlen (name); + if (namelen && name[namelen - 1] == '*') + { + namelen--; + glob = 1; + } + + hdr = msg->current_part->hdr_lines; + if (rprev && *rprev) + { + /* spool forward to the requested starting place. + * we cannot simply set this as we have to return + * the previous list element too */ + for (; hdr && hdr != *rprev; prev = hdr, hdr = hdr->next) + ; + } + + for (; hdr; prev = hdr, hdr = hdr->next) + { + if (hdr->cont) + continue; + if (!(p = strchr (hdr->line, ':'))) + continue; /* invalid header, just skip it. */ + n = p - hdr->line; + if (!n) + continue; /* invalid name */ + if ((glob ? (namelen <= n) : (namelen == n)) + && !memcmp (hdr->line, name, namelen)) + { + found++; + if (which == -1) + mark = hdr; + else if (found == which) + { + if (rprev) + *rprev = prev; + return hdr; + } + } + } + if (mark && rprev) + *rprev = prev; + return mark; +} + + + +static const char * +skip_ws (const char *s) +{ + while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n') + s++; + return s; +} + + +static void +release_token_list (TOKEN t) +{ + while (t) + { + TOKEN t2 = t->next; + /* fixme: If we have owner_pantry, put the token back to + * this pantry so that it can be reused later */ + free (t); + t = t2; + } +} + + +static TOKEN +new_token (enum token_type type, const char *buf, size_t length) +{ + TOKEN t; + + /* fixme: look through our pantries to find a suitable + * token for reuse */ + t = malloc (sizeof *t + length); + if (t) + { + t->next = NULL; + t->type = type; + memset (&t->flags, 0, sizeof (t->flags)); + t->data[0] = 0; + if (buf) + { + memcpy (t->data, buf, length); + t->data[length] = 0; /* Make sure it is a C string. */ + } + else + t->data[0] = 0; + } + return t; +} + +static TOKEN +append_to_token (TOKEN old, const char *buf, size_t length) +{ + size_t n = strlen (old->data); + TOKEN t; + + t = malloc (sizeof *t + n + length); + if (t) + { + t->next = old->next; + t->type = old->type; + t->flags = old->flags; + memcpy (t->data, old->data, n); + memcpy (t->data + n, buf, length); + t->data[n + length] = 0; + old->next = NULL; + release_token_list (old); + } + return t; +} + + + +/* + Parse a field into tokens as defined by rfc822. + */ +static TOKEN +parse_field (HDR_LINE hdr) +{ + static const char specials[] = "<>@.,;:\\[]\"()"; + static const char specials2[] = "<>@.,;:"; + static const char tspecials[] = "/?=<>@,;:\\[]\"()"; + static const char tspecials2[] = "/?=<>@.,;:"; /* FIXME: really + include '.'?*/ + static struct + { + const unsigned char *name; + size_t namelen; + } tspecial_header[] = { + { "Content-Type", 12}, + { "Content-Transfer-Encoding", 25}, + { "Content-Disposition", 19}, + { NULL, 0} + }; + const char *delimiters; + const char *delimiters2; + const unsigned char *line, *s, *s2; + size_t n; + int i, invalid = 0; + TOKEN t, tok, *tok_tail; + + errno = 0; + if (!hdr) + return NULL; + + tok = NULL; + tok_tail = &tok; + + line = hdr->line; + if (!(s = strchr (line, ':'))) + return NULL; /* oops */ + + n = s - line; + if (!n) + return NULL; /* oops: invalid name */ + + delimiters = specials; + delimiters2 = specials2; + for (i = 0; tspecial_header[i].name; i++) + { + if (n == tspecial_header[i].namelen + && !memcmp (line, tspecial_header[i].name, n)) + { + delimiters = tspecials; + delimiters2 = tspecials2; + break; + } + } + + s++; /* Move over the colon. */ + for (;;) + { + while (!*s) + { + if (!hdr->next || !hdr->next->cont) + return tok; /* Ready. */ + + /* Next item is a header continuation line. */ + hdr = hdr->next; + s = hdr->line; + } + + if (*s == '(') + { + int level = 1; + int in_quote = 0; + + invalid = 0; + for (s++;; s++) + { + while (!*s) + { + if (!hdr->next || !hdr->next->cont) + goto oparen_out; + /* Next item is a header continuation line. */ + hdr = hdr->next; + s = hdr->line; + } + + if (in_quote) + { + if (*s == '\"') + in_quote = 0; + else if (*s == '\\' && s[1]) /* what about continuation? */ + s++; + } + else if (*s == ')') + { + if (!--level) + break; + } + else if (*s == '(') + level++; + else if (*s == '\"') + in_quote = 1; + } + oparen_out: + if (!*s) + ; /* Actually this is an error, but we don't care about it. */ + else + s++; + } + else if (*s == '\"' || *s == '[') + { + /* We do not check for non-allowed nesting of domainliterals */ + int term = *s == '\"' ? '\"' : ']'; + invalid = 0; + s++; + t = NULL; + + for (;;) + { + for (s2 = s; *s2; s2++) + { + if (*s2 == term) + break; + else if (*s2 == '\\' && s2[1]) /* what about continuation? */ + s2++; + } + + t = (t + ? append_to_token (t, s, s2 - s) + : new_token (term == '\"'? tQUOTED : tDOMAINLIT, s, s2 - s)); + if (!t) + goto failure; + + if (*s2 || !hdr->next || !hdr->next->cont) + break; + /* Next item is a header continuation line. */ + hdr = hdr->next; + s = hdr->line; + } + *tok_tail = t; + tok_tail = &t->next; + s = s2; + if (*s) + s++; /* skip the delimiter */ + } + else if ((s2 = strchr (delimiters2, *s))) + { /* Special characters which are not handled above. */ + invalid = 0; + t = new_token (tSPECIAL, s, 1); + if (!t) + goto failure; + *tok_tail = t; + tok_tail = &t->next; + s++; + } + else if (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n') + { + invalid = 0; + s = skip_ws (s + 1); + } + else if (*s > 0x20 && !(*s & 128)) + { /* Atom. */ + invalid = 0; + for (s2 = s + 1; *s2 > 0x20 + && !(*s2 & 128) && !strchr (delimiters, *s2); s2++) + ; + t = new_token (tATOM, s, s2 - s); + if (!t) + goto failure; + *tok_tail = t; + tok_tail = &t->next; + s = s2; + } + else + { /* Invalid character. */ + if (!invalid) + { /* For parsing we assume only one space. */ + t = new_token (tSPACE, NULL, 0); + if (!t) + goto failure; + *tok_tail = t; + tok_tail = &t->next; + invalid = 1; + } + s++; + } + } + /*NOTREACHED*/ + + failure: + { + int save = errno; + release_token_list (tok); + errno = save; + } + return NULL; +} + + + + +/**************** + * Find and parse a header field. + * WHICH indicates what to do if there are multiple instance of the same + * field (like "Received"); the following value are defined: + * -1 := Take the last occurrence + * 0 := Reserved + * n := Take the n-th one. + * Returns a handle for further operations on the parse context of the field + * or NULL if the field was not found. + */ +rfc822parse_field_t +rfc822parse_parse_field (rfc822parse_t msg, const char *name, int which) +{ + HDR_LINE hdr; + + if (!which) + return NULL; + + hdr = find_header (msg, name, which, NULL); + if (!hdr) + return NULL; + return parse_field (hdr); +} + +void +rfc822parse_release_field (rfc822parse_field_t ctx) +{ + if (ctx) + release_token_list (ctx); +} + + + +/**************** + * Check whether T points to a parameter. + * A parameter starts with a semicolon and it is assumed that t + * points to exactly this one. + */ +static int +is_parameter (TOKEN t) +{ + t = t->next; + if (!t || t->type != tATOM) + return 0; + t = t->next; + if (!t || !(t->type == tSPECIAL && t->data[0] == '=')) + return 0; + t = t->next; + if (!t) + return 1; /* We assume that an non existing value is an empty one. */ + return t->type == tQUOTED || t->type == tATOM; +} + +/* + Some header (Content-type) have a special syntax where attribute=value + pairs are used after a leading semicolon. The parse_field code + knows about these fields and changes the parsing to the one defined + in RFC2045. + Returns a pointer to the value which is valid as long as the + parse context is valid; NULL is returned in case that attr is not + defined in the header, a missing value is reppresented by an empty string. + + With LOWER_VALUE set to true, a matching field valuebe be + lowercased. + + Note, that ATTR should be lowercase. + */ +const char * +rfc822parse_query_parameter (rfc822parse_field_t ctx, const char *attr, + int lower_value) +{ + TOKEN t, a; + + for (t = ctx; t; t = t->next) + { + /* skip to the next semicolon */ + for (; t && !(t->type == tSPECIAL && t->data[0] == ';'); t = t->next) + ; + if (!t) + return NULL; + if (is_parameter (t)) + { /* Look closer. */ + a = t->next; /* We know that this is an atom */ + if ( !a->flags.lowered ) + { + lowercase_string (a->data); + a->flags.lowered = 1; + } + if (!strcmp (a->data, attr)) + { /* found */ + t = a->next->next; + /* Either T is now an atom, a quoted string or NULL in + * which case we return an empty string. */ + + if ( lower_value && t && !t->flags.lowered ) + { + lowercase_string (t->data); + t->flags.lowered = 1; + } + return t ? t->data : ""; + } + } + } + return NULL; +} + +/**************** + * This function may be used for the Content-Type header to figure out + * the media type and subtype. Note, that the returned strings are + * guaranteed to be lowercase as required by MIME. + * + * Returns: a pointer to the media type and if subtype is not NULL, + * a pointer to the subtype. + */ +const char * +rfc822parse_query_media_type (rfc822parse_field_t ctx, const char **subtype) +{ + TOKEN t = ctx; + const char *type; + + if (t->type != tATOM) + return NULL; + if (!t->flags.lowered) + { + lowercase_string (t->data); + t->flags.lowered = 1; + } + type = t->data; + t = t->next; + if (!t || t->type != tSPECIAL || t->data[0] != '/') + return NULL; + t = t->next; + if (!t || t->type != tATOM) + return NULL; + + if (subtype) + { + if (!t->flags.lowered) + { + lowercase_string (t->data); + t->flags.lowered = 1; + } + *subtype = t->data; + } + return type; +} + + + + + +#ifdef TESTING + +/* Internal debug function to print the structure of the message. */ +static void +dump_structure (rfc822parse_t msg, part_t part, int indent) +{ + if (!part) + { + printf ("*** Structure of this message:\n"); + part = msg->parts; + } + + for (; part; part = part->right) + { + rfc822parse_field_t ctx; + part_t save_part; /* ugly hack - we should have a function to + get part information. */ + const char *s; + + save_part = msg->current_part; + msg->current_part = part; + ctx = rfc822parse_parse_field (msg, "Content-Type", -1); + msg->current_part = save_part; + if (ctx) + { + const char *s1, *s2; + s1 = rfc822parse_query_media_type (ctx, &s2); + if (s1) + printf ("*** %*s %s/%s", indent*2, "", s1, s2); + else + printf ("*** %*s [not found]", indent*2, ""); + + s = rfc822parse_query_parameter (ctx, "boundary", 0); + if (s) + printf (" (boundary=\"%s\")", s); + rfc822parse_release_field (ctx); + } + else + printf ("*** %*s text/plain [assumed]", indent*2, ""); + putchar('\n'); + + if (part->down) + dump_structure (msg, part->down, indent + 1); + } + +} + + + +static void +show_param (rfc822parse_field_t ctx, const char *name) +{ + const char *s; + + if (!ctx) + return; + s = rfc822parse_query_parameter (ctx, name, 0); + if (s) + printf ("*** %s: '%s'\n", name, s); +} + + + +static void +show_event (rfc822parse_event_t event) +{ + const char *s; + + switch (event) + { + case RFC822PARSE_OPEN: s= "Open"; break; + case RFC822PARSE_CLOSE: s= "Close"; break; + case RFC822PARSE_CANCEL: s= "Cancel"; break; + case RFC822PARSE_T2BODY: s= "T2Body"; break; + case RFC822PARSE_FINISH: s= "Finish"; break; + case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break; + case RFC822PARSE_LEVEL_DOWN: s= "Level_Down"; break; + case RFC822PARSE_LEVEL_UP: s= "Level_Up"; break; + case RFC822PARSE_BOUNDARY: s= "Boundary"; break; + case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break; + case RFC822PARSE_BEGIN_HEADER: s= "Begin_Header"; break; + case RFC822PARSE_PREAMBLE: s= "Preamble"; break; + case RFC822PARSE_EPILOGUE: s= "Epilogue"; break; + default: s= "***invalid event***"; break; + } + printf ("*** got RFC822 event %s\n", s); +} + +static int +msg_cb (void *dummy_arg, rfc822parse_event_t event, rfc822parse_t msg) +{ + show_event (event); + if (event == RFC822PARSE_T2BODY) + { + rfc822parse_field_t ctx; + void *ectx; + const char *line; + + for (ectx=NULL; (line = rfc822parse_enum_header_lines (msg, &ectx)); ) + { + printf ("*** HDR: %s\n", line); + } + rfc822parse_enum_header_lines (NULL, &ectx); /* Close enumerator. */ + + ctx = rfc822parse_parse_field (msg, "Content-Type", -1); + if (ctx) + { + const char *s1, *s2; + s1 = rfc822parse_query_media_type (ctx, &s2); + if (s1) + printf ("*** media: '%s/%s'\n", s1, s2); + else + printf ("*** media: [not found]\n"); + show_param (ctx, "boundary"); + show_param (ctx, "protocol"); + rfc822parse_release_field (ctx); + } + else + printf ("*** media: text/plain [assumed]\n"); + + } + + + return 0; +} + + + +int +main (int argc, char **argv) +{ + char line[5000]; + size_t length; + rfc822parse_t msg; + + msg = rfc822parse_open (msg_cb, NULL); + if (!msg) + abort (); + + while (fgets (line, sizeof (line), stdin)) + { + length = strlen (line); + if (length && line[length - 1] == '\n') + line[--length] = 0; + if (length && line[length - 1] == '\r') + line[--length] = 0; + if (rfc822parse_insert (msg, line, length)) + abort (); + } + + dump_structure (msg, NULL, 0); + + rfc822parse_close (msg); + return 0; +} +#endif + +/* +Local Variables: +compile-command: "gcc -Wall -Wno-pointer-sign -g -DTESTING -o rfc822parse rfc822parse.c" +End: +*/ diff --git a/tools/rfc822parse.h b/tools/rfc822parse.h new file mode 100644 index 0000000..e2f2bed --- /dev/null +++ b/tools/rfc822parse.h @@ -0,0 +1,81 @@ +/* rfc822parse.h - Simple mail and MIME parser + * Copyright (C) 1999 Werner Koch, Duesseldorf + * Copyright (C) 2003 g10 Code GmbH + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef RFC822PARSE_H +#define RFC822PARSE_H + +struct rfc822parse_context; +typedef struct rfc822parse_context *rfc822parse_t; + +typedef enum + { + RFC822PARSE_OPEN = 1, + RFC822PARSE_CLOSE, + RFC822PARSE_CANCEL, + RFC822PARSE_T2BODY, + RFC822PARSE_FINISH, + RFC822PARSE_RCVD_SEEN, + RFC822PARSE_LEVEL_DOWN, + RFC822PARSE_LEVEL_UP, + RFC822PARSE_BOUNDARY, + RFC822PARSE_LAST_BOUNDARY, + RFC822PARSE_BEGIN_HEADER, + RFC822PARSE_PREAMBLE, + RFC822PARSE_EPILOGUE + } +rfc822parse_event_t; + +struct rfc822parse_field_context; +typedef struct rfc822parse_field_context *rfc822parse_field_t; + + +typedef int (*rfc822parse_cb_t) (void *opaque, + rfc822parse_event_t event, + rfc822parse_t msg); + +int rfc822_valid_header_name_p (const char *name); +void rfc822_capitalize_header_name (char *name); + +rfc822parse_t rfc822parse_open (rfc822parse_cb_t cb, void *opaque_value); + +void rfc822parse_close (rfc822parse_t msg); + +void rfc822parse_cancel (rfc822parse_t msg); +int rfc822parse_finish (rfc822parse_t msg); + +int rfc822parse_insert (rfc822parse_t msg, + const unsigned char *line, size_t length); + +char *rfc822parse_get_field (rfc822parse_t msg, const char *name, int which, + size_t *valueoff); + +const char *rfc822parse_enum_header_lines (rfc822parse_t msg, void **context); + +rfc822parse_field_t rfc822parse_parse_field (rfc822parse_t msg, + const char *name, + int which); + +void rfc822parse_release_field (rfc822parse_field_t field); + +const char *rfc822parse_query_parameter (rfc822parse_field_t ctx, + const char *attr, int lower_value); + +const char *rfc822parse_query_media_type (rfc822parse_field_t ctx, + const char **subtype); + +#endif /*RFC822PARSE_H */ diff --git a/tools/send-mail.c b/tools/send-mail.c new file mode 100644 index 0000000..6492c43 --- /dev/null +++ b/tools/send-mail.c @@ -0,0 +1,140 @@ +/* send-mail.c - Invoke sendmail or other delivery tool. + * Copyright (C) 2016 g10 Code GmbH + * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/util.h" +#include "../common/exectool.h" +#include "../common/sysutils.h" +#include "send-mail.h" + + +static gpg_error_t +run_sendmail (estream_t data) +{ + gpg_error_t err; + const char pgmname[] = NAME_OF_SENDMAIL; + const char *argv[3]; + + argv[0] = "-oi"; + argv[1] = "-t"; + argv[2] = NULL; + + err = gnupg_exec_tool_stream (pgmname, argv, data, NULL, NULL, NULL, NULL); + if (err) + log_error ("running '%s' failed: %s\n", pgmname, gpg_strerror (err)); + return err; +} + + +/* Send the data in FP as mail. */ +gpg_error_t +send_mail (estream_t fp) +{ + return run_sendmail (fp); +} + + +/* Convenience function to write a mail to a named file. */ +gpg_error_t +send_mail_to_file (estream_t fp, const char *fname) +{ + gpg_error_t err; + estream_t outfp = NULL; + char *buffer = NULL; + size_t buffersize = 32 * 1024; + size_t nbytes, nwritten; + + if (!fname) + fname = "-"; + + buffer = xtrymalloc (buffersize); + if (!buffer) + return gpg_error_from_syserror (); + + + if (!strcmp (fname,"-")) + { + outfp = es_stdout; + es_set_binary (es_stdout); + } + else + { + outfp = es_fopen (fname, "wb"); + if (!outfp) + { + err = gpg_error_from_syserror (); + log_error ("error creating '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + } + + for (;;) + { + if (es_read (fp, buffer, sizeof buffer, &nbytes)) + { + err = gpg_error_from_syserror (); + log_error ("error reading '%s': %s\n", + es_fname_get (fp), gpg_strerror (err)); + goto leave; + } + + if (!nbytes) + { + err = 0; + break; /* Ready. */ + } + + if (es_write (outfp, buffer, nbytes, &nwritten)) + { + err = gpg_error_from_syserror (); + log_error ("error writing '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + else if (nwritten != nbytes) + { + err = gpg_error (GPG_ERR_EIO); + log_error ("error writing '%s': %s\n", fname, "short write"); + goto leave; + } + } + + + leave: + if (err) + { + if (outfp && outfp != es_stdout) + { + es_fclose (outfp); + gnupg_remove (fname); + } + } + else if (outfp && outfp != es_stdout && es_fclose (outfp)) + { + err = gpg_error_from_syserror (); + log_error ("error closing '%s': %s\n", fname, gpg_strerror (err)); + } + + xfree (buffer); + return err; +} diff --git a/tools/send-mail.h b/tools/send-mail.h new file mode 100644 index 0000000..b565a18 --- /dev/null +++ b/tools/send-mail.h @@ -0,0 +1,28 @@ +/* send-mail.h - Invoke sendmail or other delivery tool. + * Copyright (C) 2016 g10 Code GmbH + * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef GNUPG_SEND_MAIL_H +#define GNUPG_SEND_MAIL_H + +gpg_error_t send_mail (estream_t fp); +gpg_error_t send_mail_to_file (estream_t fp, const char *fname); + + +#endif /*GNUPG_SEND_MAIL_H*/ diff --git a/tools/sockprox.c b/tools/sockprox.c new file mode 100644 index 0000000..8648bb5 --- /dev/null +++ b/tools/sockprox.c @@ -0,0 +1,551 @@ +/* sockprox - Proxy for local sockets with logging facilities + * Copyright (C) 2007 g10 Code GmbH. + * + * sockprox 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. + * + * sockprox is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +/* Hacked by Moritz Schulte <moritz@g10code.com>. + + Usage example: + + Run a server which binds to a local socket. For example, + gpg-agent. gpg-agent's local socket is specified with --server. + sockprox opens a new local socket (here "mysock"); the whole + traffic between server and client is written to "/tmp/prot" in this + case. + + ./sockprox --server /tmp/gpg-PKdD8r/S.gpg-agent.ssh \ + --listen mysock --protocol /tmp/prot + + Then, redirect your ssh-agent client to sockprox by setting + SSH_AUTH_SOCK to "mysock". +*/ + + + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <stddef.h> +#include <errno.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <fcntl.h> +#include <assert.h> +#include <pthread.h> + +struct opt +{ + char *protocol_file; + char *server_spec; + char *listen_spec; + int verbose; +}; + +struct opt opt = { NULL, NULL, NULL, 0 }; + +struct thread_data +{ + int client_sock; + FILE *protocol_file; +}; + + + +static int +create_server_socket (const char *filename, int *new_sock) +{ + struct sockaddr_un name; + size_t size; + int sock; + int ret; + int err; + + /* Create the socket. */ + sock = socket (PF_LOCAL, SOCK_STREAM, 0); + if (sock < 0) + { + err = errno; + goto out; + } + + /* Bind a name to the socket. */ + name.sun_family = AF_LOCAL; + strncpy (name.sun_path, filename, sizeof (name.sun_path)); + name.sun_path[sizeof (name.sun_path) - 1] = '\0'; + size = SUN_LEN (&name); + + remove (filename); + + ret = bind (sock, (struct sockaddr *) &name, size); + if (ret < 0) + { + err = errno; + goto out; + } + + ret = listen (sock, 2); + if (ret < 0) + { + err = errno; + goto out; + } + + *new_sock = sock; + err = 0; + + out: + + return err; +} + +static int +connect_to_socket (const char *filename, int *new_sock) +{ + struct sockaddr_un srvr_addr; + size_t len; + int sock; + int ret; + int err; + + sock = socket (PF_LOCAL, SOCK_STREAM, 0); + if (sock == -1) + { + err = errno; + goto out; + } + + memset (&srvr_addr, 0, sizeof srvr_addr); + srvr_addr.sun_family = AF_LOCAL; + strncpy (srvr_addr.sun_path, filename, sizeof (srvr_addr.sun_path) - 1); + srvr_addr.sun_path[sizeof (srvr_addr.sun_path) - 1] = 0; + len = SUN_LEN (&srvr_addr); + + ret = connect (sock, (struct sockaddr *) &srvr_addr, len); + if (ret == -1) + { + close (sock); + err = errno; + goto out; + } + + *new_sock = sock; + err = 0; + + out: + + return err; +} + + + +static int +log_data (unsigned char *data, size_t length, + FILE *from, FILE *to, FILE *protocol) +{ + unsigned int i; + int ret; + int err; + + flockfile (protocol); + fprintf (protocol, "%i -> %i: ", fileno (from), fileno (to)); + for (i = 0; i < length; i++) + fprintf (protocol, "%02X", data[i]); + fprintf (protocol, "\n"); + funlockfile (protocol); + + ret = fflush (protocol); + if (ret == EOF) + err = errno; + else + err = 0; + + return err; +} + +static int +transfer_data (FILE *from, FILE *to, FILE *protocol) +{ + unsigned char buffer[BUFSIZ]; + size_t len, written; + int err; + int ret; + + err = 0; + + while (1) + { + len = fread (buffer, 1, sizeof (buffer), from); + if (len == 0) + break; + + err = log_data (buffer, len, from, to, protocol); + if (err) + break; + + written = fwrite (buffer, 1, len, to); + if (written != len) + { + err = errno; + break; + } + + ret = fflush (to); + if (ret == EOF) + { + err = errno; + break; + } + + if (ferror (from)) + break; + } + + return err; +} + + +static int +io_loop (FILE *client, FILE *server, FILE *protocol) +{ + fd_set active_fd_set, read_fd_set; + int ret; + int err; + + FD_ZERO (&active_fd_set); + FD_SET (fileno (client), &active_fd_set); + FD_SET (fileno (server), &active_fd_set); + + err = 0; + + while (1) + { + read_fd_set = active_fd_set; + + /* FIXME: eof? */ + + ret = select (FD_SETSIZE, &read_fd_set, NULL, NULL, NULL); + if (ret < 0) + { + err = errno; + break; + } + + if (FD_ISSET (fileno (client), &read_fd_set)) + { + if (feof (client)) + break; + + /* Forward data from client to server. */ + err = transfer_data (client, server, protocol); + } + else if (FD_ISSET (fileno (server), &read_fd_set)) + { + if (feof (server)) + break; + + /* Forward data from server to client. */ + err = transfer_data (server, client, protocol); + } + + if (err) + break; + } + + return err; +} + + + + +/* Set the 'O_NONBLOCK' flag of DESC if VALUE is nonzero, + or clear the flag if VALUE is 0. + Return 0 on success, or -1 on error with 'errno' set. */ + +int +set_nonblock_flag (int desc, int value) +{ + int oldflags = fcntl (desc, F_GETFL, 0); + int err; + int ret; + + /* If reading the flags failed, return error indication now. */ + if (oldflags == -1) + return -1; + /* Set just the flag we want to set. */ + if (value != 0) + oldflags |= O_NONBLOCK; + else + oldflags &= ~O_NONBLOCK; + /* Store modified flag word in the descriptor. */ + + ret = fcntl (desc, F_SETFL, oldflags); + if (ret == -1) + err = errno; + else + err = 0; + + return err; +} + + + +void * +serve_client (void *data) +{ + struct thread_data *thread_data = data; + int client_sock = thread_data->client_sock; + int server_sock; + FILE *protocol = thread_data->protocol_file; + FILE *client; + FILE *server; + int err; + + client = NULL; + server = NULL; + + /* Connect to server. */ + err = connect_to_socket (opt.server_spec, &server_sock); + if (err) + goto out; + + /* Set IO mode to nonblicking. */ + err = set_nonblock_flag (server_sock, 1); + if (err) + goto out; + + client = fdopen (client_sock, "r+"); + if (! client) + { + err = errno; + goto out; + } + + server = fdopen (server_sock, "r+"); + if (! server) + { + err = errno; + goto out; + } + + err = io_loop (client, server, protocol); + + out: + + if (client) + fclose (client); + else + close (client_sock); + + if (server) + fclose (server); + else + close (server_sock); + + free (data); + + return NULL; +} + +static int +run_proxy (void) +{ + int client_sock; + int my_sock; + int err; + struct sockaddr_un clientname; + size_t size; + pthread_t mythread; + struct thread_data *thread_data; + FILE *protocol_file; + pthread_attr_t thread_attr; + + protocol_file = NULL; + + err = pthread_attr_init (&thread_attr); + if (err) + goto out; + + err = pthread_attr_setdetachstate (&thread_attr, PTHREAD_CREATE_DETACHED); + if (err) + goto out; + + if (opt.protocol_file) + { + protocol_file = fopen (opt.protocol_file, "a"); + if (! protocol_file) + { + err = errno; + goto out; + } + } + else + protocol_file = stdout; + + err = create_server_socket (opt.listen_spec, &my_sock); + if (err) + goto out; + + while (1) + { + /* Accept new client. */ + size = sizeof (clientname); + client_sock = accept (my_sock, + (struct sockaddr *) &clientname, + &size); + if (client_sock < 0) + { + err = errno; + break; + } + + /* Set IO mode to nonblicking. */ + err = set_nonblock_flag (client_sock, 1); + if (err) + { + close (client_sock); + break; + } + + /* Got new client -> handle in new process. */ + + thread_data = malloc (sizeof (*thread_data)); + if (! thread_data) + { + err = errno; + break; + } + thread_data->client_sock = client_sock; + thread_data->protocol_file = protocol_file; + + err = pthread_create (&mythread, &thread_attr, serve_client, thread_data); + if (err) + break; + } + if (err) + goto out; + + /* ? */ + + out: + + pthread_attr_destroy (&thread_attr); + if (protocol_file) + fclose (protocol_file); /* FIXME, err checking. */ + + return err; +} + + + +static int +print_help (int ret) +{ + printf ("Usage: sockprox [options] " + "--server SERVER-SOCKET --listen PROXY-SOCKET\n"); + exit (ret); +} + +int +main (int argc, char **argv) +{ + struct option long_options[] = + { + { "help", no_argument, 0, 'h' }, + { "verbose", no_argument, &opt.verbose, 1 }, + { "protocol", required_argument, 0, 'p' }, + { "server", required_argument, 0, 's' }, + { "listen", required_argument, 0, 'l' }, + { 0, 0, 0, 0 } + }; + int ret; + int err; + int c; + + while (1) + { + int opt_idx = 0; + c = getopt_long (argc, argv, "hvp:s:l:", + long_options, &opt_idx); + + if (c == -1) + break; + + switch (c) + { + case 0: + if (long_options[opt_idx].flag) + break; + printf ("option %s", long_options[opt_idx].name); + if (optarg) + printf (" with arg %s", optarg); + printf ("\n"); + break; + + case 'p': + opt.protocol_file = optarg; + break; + + case 's': + opt.server_spec = optarg; + break; + + case 'l': + opt.listen_spec = optarg; + break; + + case 'v': + opt.verbose = 1; + break; + + case 'h': + print_help (EXIT_SUCCESS); + break; + + default: + abort (); + } + } + + if (opt.verbose) + { + printf ("server: %s\n", opt.server_spec ? opt.server_spec : ""); + printf ("listen: %s\n", opt.listen_spec ? opt.listen_spec : ""); + printf ("protocol: %s\n", opt.protocol_file ? opt.protocol_file : ""); + } + + if (! (opt.server_spec && opt.listen_spec)) + print_help (EXIT_FAILURE); + + err = run_proxy (); + if (err) + { + fprintf (stderr, "run_proxy() failed: %s\n", strerror (err)); + ret = EXIT_FAILURE; + } + else + /* ? */ + ret = EXIT_SUCCESS; + + return ret; +} + + +/* +Local Variables: +compile-command: "cc -Wall -g -o sockprox sockprox.c -lpthread" +End: +*/ diff --git a/tools/watchgnupg.c b/tools/watchgnupg.c new file mode 100644 index 0000000..fc58d14 --- /dev/null +++ b/tools/watchgnupg.c @@ -0,0 +1,519 @@ +/* watchgnupg.c - Socket server for GnuPG logs + * Copyright (C) 2003, 2004, 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <errno.h> +#include <stdarg.h> +#include <assert.h> +#include <unistd.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <fcntl.h> +#include <time.h> +#ifdef HAVE_SYS_SELECT_H +# include <sys/select.h> +#endif + +#define PGM "watchgnupg" + +/* Allow for a standalone build on most systems. */ +#ifdef VERSION +#define MYVERSION_LINE PGM " ("GNUPG_NAME") " VERSION +#define BUGREPORT_LINE "\nReport bugs to <bug-gnupg@gnu.org>.\n" +#else +#define MYVERSION_LINE PGM " (standalone build) " __DATE__ +#define BUGREPORT_LINE "" +#endif +#if !defined(SUN_LEN) || !defined(PF_LOCAL) || !defined(AF_LOCAL) +#define GNUPG_COMMON_NEED_AFLOCAL +#include "../common/mischelp.h" +#endif + + +static int verbose; +static int time_only; + +static void +die (const char *format, ...) +{ + va_list arg_ptr; + + fflush (stdout); + fprintf (stderr, "%s: ", PGM); + + va_start (arg_ptr, format); + vfprintf (stderr, format, arg_ptr); + va_end (arg_ptr); + putc ('\n', stderr); + + exit (1); +} + + +static void +err (const char *format, ...) +{ + va_list arg_ptr; + + fflush (stdout); + fprintf (stderr, "%s: ", PGM); + + va_start (arg_ptr, format); + vfprintf (stderr, format, arg_ptr); + va_end (arg_ptr); + putc ('\n', stderr); +} + +static void * +xmalloc (size_t n) +{ + void *p = malloc (n); + if (!p) + die ("out of core"); + return p; +} + +static void * +xcalloc (size_t n, size_t m) +{ + void *p = calloc (n, m); + if (!p) + die ("out of core"); + return p; +} + +static void * +xrealloc (void *old, size_t n) +{ + void *p = realloc (old, n); + if (!p) + die ("out of core"); + return p; +} + + +struct client_s { + struct client_s *next; + int fd; + size_t size; /* Allocated size of buffer. */ + size_t len; /* Current length of buffer. */ + unsigned char *buffer; /* Buffer to with data already read. */ + +}; +typedef struct client_s *client_t; + +/* The list of all connected peers. */ +static client_t client_list; + + + + +static void +print_fd_and_time (int fd) +{ + struct tm *tp; + time_t atime = time (NULL); + + tp = localtime (&atime); + if (time_only) + printf ("%3d - %02d:%02d:%02d ", + fd, + tp->tm_hour, tp->tm_min, tp->tm_sec ); + else + printf ("%3d - %04d-%02d-%02d %02d:%02d:%02d ", + fd, + 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, + tp->tm_hour, tp->tm_min, tp->tm_sec ); +} + + +/* Print LINE for the client identified by C. Calling this function + with LINE set to NULL, will flush the internal buffer. */ +static void +print_line (client_t c, const char *line) +{ + const char *s; + size_t n; + + if (!line) + { + if (c->buffer && c->len) + { + print_fd_and_time (c->fd); + fwrite (c->buffer, c->len, 1, stdout); + putc ('\n', stdout); + c->len = 0; + } + return; + } + + while ((s = strchr (line, '\n'))) + { + print_fd_and_time (c->fd); + if (c->buffer && c->len) + { + fwrite (c->buffer, c->len, 1, stdout); + c->len = 0; + } + fwrite (line, s - line + 1, 1, stdout); + line = s + 1; + } + n = strlen (line); + if (n) + { + if (c->len + n >= c->size) + { + c->size += ((n + 255) & ~255); + c->buffer = (c->buffer + ? xrealloc (c->buffer, c->size) + : xmalloc (c->size)); + } + memcpy (c->buffer + c->len, line, n); + c->len += n; + } +} + + +static void +setup_client (int server_fd, int is_un) +{ + struct sockaddr_un addr_un; + struct sockaddr_in addr_in; + struct sockaddr *addr; + socklen_t addrlen; + int fd; + client_t client; + + if (is_un) + { + addr = (struct sockaddr *)&addr_un; + addrlen = sizeof addr_un; + } + else + { + addr = (struct sockaddr *)&addr_in; + addrlen = sizeof addr_in; + } + + fd = accept (server_fd, addr, &addrlen); + if (fd == -1) + { + printf ("[accepting %s connection failed: %s]\n", + is_un? "local":"tcp", strerror (errno)); + } + else if (fd >= FD_SETSIZE) + { + close (fd); + printf ("[connection request denied: too many connections]\n"); + } + else + { + for (client = client_list; client && client->fd != -1; + client = client->next) + ; + if (!client) + { + client = xcalloc (1, sizeof *client); + client->next = client_list; + client_list = client; + } + client->fd = fd; + printf ("[client at fd %d connected (%s)]\n", + client->fd, is_un? "local":"tcp"); + } +} + + + +static void +print_version (int with_help) +{ + fputs (MYVERSION_LINE "\n" + "Copyright (C) 2017 Free Software Foundation, Inc.\n" + "License GPLv3+: " + "GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>\n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law.\n", + stdout); + if (with_help) + fputs + ("\n" + "Usage: " PGM " [OPTIONS] SOCKETNAME\n" + " " PGM " [OPTIONS] PORT [SOCKETNAME]\n" + "Open the local socket SOCKETNAME (or the TCP port PORT)\n" + "and display log messages\n" + "\n" + " --tcp listen on a TCP port and optionally on a local socket\n" + " --force delete an already existing socket file\n" + " --verbose enable extra informational output\n" + " --time-only print only the time; not a full timestamp\n" + " --version print version of the program and exit\n" + " --help display this help and exit\n" + BUGREPORT_LINE, stdout ); + + exit (0); +} + +int +main (int argc, char **argv) +{ + int last_argc = -1; + int force = 0; + int tcp = 0; + + struct sockaddr_un srvr_addr_un; + struct sockaddr_in srvr_addr_in; + struct sockaddr *addr_in = NULL; + struct sockaddr *addr_un = NULL; + socklen_t addrlen_in, addrlen_un; + unsigned short port; + int server_un, server_in; + int flags; + + if (argc) + { + argc--; argv++; + } + while (argc && last_argc != argc ) + { + last_argc = argc; + if (!strcmp (*argv, "--")) + { + argc--; argv++; + break; + } + else if (!strcmp (*argv, "--version")) + print_version (0); + else if (!strcmp (*argv, "--help")) + print_version (1); + else if (!strcmp (*argv, "--verbose")) + { + verbose = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--time-only")) + { + time_only = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--force")) + { + force = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--tcp")) + { + tcp = 1; + argc--; argv++; + } + } + + if (!((!tcp && argc == 1) || (tcp && (argc == 1 || argc == 2)))) + { + fprintf (stderr, "usage: " PGM " socketname\n" + " " PGM " --tcp port [socketname]\n"); + exit (1); + } + + if (tcp) + { + port = atoi (*argv); + argc--; argv++; + } + else + { + port = 0; + } + + setvbuf (stdout, NULL, _IOLBF, 0); + + if (tcp) + { + int i = 1; + server_in = socket (PF_INET, SOCK_STREAM, 0); + if (server_in == -1) + die ("socket(PF_INET) failed: %s\n", strerror (errno)); + if (setsockopt (server_in, SOL_SOCKET, SO_REUSEADDR, + (unsigned char *)&i, sizeof (i))) + err ("setsockopt(SO_REUSEADDR) failed: %s\n", strerror (errno)); + if (verbose) + fprintf (stderr, "listening on port %hu\n", port); + } + else + server_in = -1; + + if (argc) + { + server_un = socket (PF_LOCAL, SOCK_STREAM, 0); + if (server_un == -1) + die ("socket(PF_LOCAL) failed: %s\n", strerror (errno)); + if (verbose) + fprintf (stderr, "listening on socket '%s'\n", *argv); + } + else + server_un = -1; + + /* We better set the listening socket to non-blocking so that we + don't get bitten by race conditions in accept. The should not + happen for Unix Domain sockets but well, shit happens. */ + if (server_in != -1) + { + flags = fcntl (server_in, F_GETFL, 0); + if (flags == -1) + die ("fcntl (F_GETFL) failed: %s\n", strerror (errno)); + if ( fcntl (server_in, F_SETFL, (flags | O_NONBLOCK)) == -1) + die ("fcntl (F_SETFL) failed: %s\n", strerror (errno)); + } + if (server_un != -1) + { + flags = fcntl (server_un, F_GETFL, 0); + if (flags == -1) + die ("fcntl (F_GETFL) failed: %s\n", strerror (errno)); + if ( fcntl (server_un, F_SETFL, (flags | O_NONBLOCK)) == -1) + die ("fcntl (F_SETFL) failed: %s\n", strerror (errno)); + } + + if (tcp) + { + memset (&srvr_addr_in, 0, sizeof srvr_addr_in); + srvr_addr_in.sin_family = AF_INET; + srvr_addr_in.sin_port = htons (port); + srvr_addr_in.sin_addr.s_addr = htonl (INADDR_ANY); + addr_in = (struct sockaddr *)&srvr_addr_in; + addrlen_in = sizeof srvr_addr_in; + } + if (argc) + { + memset (&srvr_addr_un, 0, sizeof srvr_addr_un); + srvr_addr_un.sun_family = AF_LOCAL; + strncpy (srvr_addr_un.sun_path, *argv, sizeof (srvr_addr_un.sun_path)-1); + srvr_addr_un.sun_path[sizeof (srvr_addr_un.sun_path) - 1] = 0; + addr_un = (struct sockaddr *)&srvr_addr_un; + addrlen_un = SUN_LEN (&srvr_addr_un); + } + else + addrlen_un = 0; /* Silent gcc. */ + + if (server_in != -1 && bind (server_in, addr_in, addrlen_in)) + die ("bind to port %hu failed: %s\n", port, strerror (errno)); + + again: + if (server_un != -1 && bind (server_un, addr_un, addrlen_un)) + { + if (errno == EADDRINUSE && force) + { + force = 0; + remove (srvr_addr_un.sun_path); + goto again; + } + else + die ("bind to '%s' failed: %s\n", *argv, strerror (errno)); + } + + if (server_in != -1 && listen (server_in, 5)) + die ("listen on inet failed: %s\n", strerror (errno)); + if (server_un != -1 && listen (server_un, 5)) + die ("listen on local failed: %s\n", strerror (errno)); + + for (;;) + { + fd_set rfds; + int max_fd; + client_t client; + + /* Usually we don't have that many connections, thus it is okay + to set them always from scratch and don't maintain an active + fd_set. */ + FD_ZERO (&rfds); + max_fd = -1; + if (server_in != -1) + { + FD_SET (server_in, &rfds); + max_fd = server_in; + } + if (server_un != -1) + { + FD_SET (server_un, &rfds); + if (server_un > max_fd) + max_fd = server_un; + } + for (client = client_list; client; client = client->next) + if (client->fd != -1) + { + FD_SET (client->fd, &rfds); + if (client->fd > max_fd) + max_fd = client->fd; + } + + if (select (max_fd + 1, &rfds, NULL, NULL, NULL) <= 0) + continue; /* Ignore any errors. */ + + if (server_in != -1 && FD_ISSET (server_in, &rfds)) + setup_client (server_in, 0); + if (server_un != -1 && FD_ISSET (server_un, &rfds)) + setup_client (server_un, 1); + + for (client = client_list; client; client = client->next) + if (client->fd != -1 && FD_ISSET (client->fd, &rfds)) + { + char line[256]; + int n; + + n = read (client->fd, line, sizeof line - 1); + if (n < 0) + { + int save_errno = errno; + print_line (client, NULL); /* flush */ + printf ("[client at fd %d read error: %s]\n", + client->fd, strerror (save_errno)); + close (client->fd); + client->fd = -1; + } + else if (!n) + { + print_line (client, NULL); /* flush */ + close (client->fd); + printf ("[client at fd %d disconnected]\n", client->fd); + client->fd = -1; + } + else + { + line[n] = 0; + print_line (client, line); + } + } + } + + return 0; +} + + +/* +Local Variables: +compile-command: "gcc -Wall -g -o watchgnupg watchgnupg.c" +End: +*/ diff --git a/tools/wks-receive.c b/tools/wks-receive.c new file mode 100644 index 0000000..e5d2ed4 --- /dev/null +++ b/tools/wks-receive.c @@ -0,0 +1,534 @@ +/* wks-receive.c - Receive a WKS mail + * Copyright (C) 2016 g10 Code GmbH + * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/util.h" +#include "../common/ccparray.h" +#include "../common/exectool.h" +#include "gpg-wks.h" +#include "rfc822parse.h" +#include "mime-parser.h" + + +/* Limit of acceptable signed data. */ +#define MAX_SIGNEDDATA 10000 + +/* Limit of acceptable signature. */ +#define MAX_SIGNATURE 10000 + +/* Limit of acceptable encrypted data. */ +#define MAX_ENCRYPTED 100000 + +/* Data for a received object. */ +struct receive_ctx_s +{ + mime_parser_t parser; + estream_t encrypted; + estream_t plaintext; + estream_t signeddata; + estream_t signature; + estream_t key_data; + estream_t wkd_data; + unsigned int collect_key_data:1; + unsigned int collect_wkd_data:1; + unsigned int draft_version_2:1; /* This is a draft version 2 request. */ + unsigned int multipart_mixed_seen:1; +}; +typedef struct receive_ctx_s *receive_ctx_t; + + + +static void +decrypt_data_status_cb (void *opaque, const char *keyword, char *args) +{ + receive_ctx_t ctx = opaque; + (void)ctx; + if (DBG_CRYPTO) + log_debug ("gpg status: %s %s\n", keyword, args); +} + + +/* Decrypt the collected data. */ +static void +decrypt_data (receive_ctx_t ctx) +{ + gpg_error_t err; + ccparray_t ccp; + const char **argv; + int c; + + es_rewind (ctx->encrypted); + + if (!ctx->plaintext) + ctx->plaintext = es_fopenmem (0, "w+b"); + if (!ctx->plaintext) + { + err = gpg_error_from_syserror (); + log_error ("error allocating space for plaintext: %s\n", + gpg_strerror (err)); + return; + } + + ccparray_init (&ccp, 0); + + ccparray_put (&ccp, "--no-options"); + /* We limit the output to 64 KiB to avoid DoS using compression + * tricks. A regular client will anyway only send a minimal key; + * that is one w/o key signatures and attribute packets. */ + ccparray_put (&ccp, "--max-output=0x10000"); + ccparray_put (&ccp, "--batch"); + if (opt.verbose) + ccparray_put (&ccp, "--verbose"); + ccparray_put (&ccp, "--always-trust"); + ccparray_put (&ccp, "--decrypt"); + ccparray_put (&ccp, "--"); + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = gnupg_exec_tool_stream (opt.gpg_program, argv, ctx->encrypted, + NULL, ctx->plaintext, + decrypt_data_status_cb, ctx); + if (err) + { + log_error ("decryption failed: %s\n", gpg_strerror (err)); + goto leave; + } + + if (DBG_CRYPTO) + { + es_rewind (ctx->plaintext); + log_debug ("plaintext: '"); + while ((c = es_getc (ctx->plaintext)) != EOF) + log_printf ("%c", c); + log_printf ("'\n"); + } + es_rewind (ctx->plaintext); + + leave: + xfree (argv); +} + + +static void +verify_signature_status_cb (void *opaque, const char *keyword, char *args) +{ + receive_ctx_t ctx = opaque; + (void)ctx; + if (DBG_CRYPTO) + log_debug ("gpg status: %s %s\n", keyword, args); +} + +/* Verify the signed data. */ +static void +verify_signature (receive_ctx_t ctx) +{ + gpg_error_t err; + ccparray_t ccp; + const char **argv; + + log_assert (ctx->signeddata); + log_assert (ctx->signature); + es_rewind (ctx->signeddata); + es_rewind (ctx->signature); + + ccparray_init (&ccp, 0); + + ccparray_put (&ccp, "--no-options"); + ccparray_put (&ccp, "--batch"); + if (opt.verbose) + ccparray_put (&ccp, "--verbose"); + ccparray_put (&ccp, "--enable-special-filenames"); + ccparray_put (&ccp, "--status-fd=2"); + ccparray_put (&ccp, "--always-trust"); /* To avoid trustdb checks. */ + ccparray_put (&ccp, "--verify"); + ccparray_put (&ccp, "--"); + ccparray_put (&ccp, "-&@INEXTRA@"); + ccparray_put (&ccp, "-"); + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = gnupg_exec_tool_stream (opt.gpg_program, argv, ctx->signeddata, + ctx->signature, NULL, + verify_signature_status_cb, ctx); + if (err) + { + log_error ("verification failed: %s\n", gpg_strerror (err)); + goto leave; + } + + log_debug ("Fixme: Verification result is not used\n"); + + leave: + xfree (argv); +} + + +static gpg_error_t +collect_encrypted (void *cookie, const char *data) +{ + receive_ctx_t ctx = cookie; + + if (!ctx->encrypted) + if (!(ctx->encrypted = es_fopenmem (MAX_ENCRYPTED, "w+b,samethread"))) + return gpg_error_from_syserror (); + if (data) + es_fputs (data, ctx->encrypted); + + if (es_ferror (ctx->encrypted)) + return gpg_error_from_syserror (); + + if (!data) + { + decrypt_data (ctx); + } + + return 0; +} + + +static gpg_error_t +collect_signeddata (void *cookie, const char *data) +{ + receive_ctx_t ctx = cookie; + + if (!ctx->signeddata) + if (!(ctx->signeddata = es_fopenmem (MAX_SIGNEDDATA, "w+b,samethread"))) + return gpg_error_from_syserror (); + if (data) + es_fputs (data, ctx->signeddata); + + if (es_ferror (ctx->signeddata)) + return gpg_error_from_syserror (); + return 0; +} + +static gpg_error_t +collect_signature (void *cookie, const char *data) +{ + receive_ctx_t ctx = cookie; + + if (!ctx->signature) + if (!(ctx->signature = es_fopenmem (MAX_SIGNATURE, "w+b,samethread"))) + return gpg_error_from_syserror (); + if (data) + es_fputs (data, ctx->signature); + + if (es_ferror (ctx->signature)) + return gpg_error_from_syserror (); + + if (!data) + { + verify_signature (ctx); + } + + return 0; +} + + +/* The callback for the transition from header to body. We use it to + * look at some header values. */ +static gpg_error_t +t2body (void *cookie, int level) +{ + receive_ctx_t ctx = cookie; + rfc822parse_t msg; + char *value; + size_t valueoff; + + log_info ("t2body for level %d\n", level); + if (!level) + { + /* This is the outermost header. */ + msg = mime_parser_rfc822parser (ctx->parser); + if (msg) + { + value = rfc822parse_get_field (msg, "Wks-Draft-Version", + -1, &valueoff); + if (value) + { + if (atoi(value+valueoff) >= 2 ) + ctx->draft_version_2 = 1; + free (value); + } + } + } + + return 0; +} + + +static gpg_error_t +new_part (void *cookie, const char *mediatype, const char *mediasubtype) +{ + receive_ctx_t ctx = cookie; + gpg_error_t err = 0; + + ctx->collect_key_data = 0; + ctx->collect_wkd_data = 0; + + if (!strcmp (mediatype, "application") + && !strcmp (mediasubtype, "pgp-keys")) + { + log_info ("new '%s/%s' message part\n", mediatype, mediasubtype); + if (ctx->key_data) + { + log_error ("we already got a key - ignoring this part\n"); + err = gpg_error (GPG_ERR_FALSE); + } + else + { + ctx->key_data = es_fopenmem (0, "w+b"); + if (!ctx->key_data) + { + err = gpg_error_from_syserror (); + log_error ("error allocating space for key: %s\n", + gpg_strerror (err)); + } + else + { + ctx->collect_key_data = 1; + err = gpg_error (GPG_ERR_TRUE); /* We want the part decoded. */ + } + } + } + else if (!strcmp (mediatype, "application") + && !strcmp (mediasubtype, "vnd.gnupg.wks")) + { + log_info ("new '%s/%s' message part\n", mediatype, mediasubtype); + if (ctx->wkd_data) + { + log_error ("we already got a wkd part - ignoring this part\n"); + err = gpg_error (GPG_ERR_FALSE); + } + else + { + ctx->wkd_data = es_fopenmem (0, "w+b"); + if (!ctx->wkd_data) + { + err = gpg_error_from_syserror (); + log_error ("error allocating space for key: %s\n", + gpg_strerror (err)); + } + else + { + ctx->collect_wkd_data = 1; + err = gpg_error (GPG_ERR_TRUE); /* We want the part decoded. */ + } + } + } + else if (!strcmp (mediatype, "multipart") + && !strcmp (mediasubtype, "mixed")) + { + ctx->multipart_mixed_seen = 1; + } + else if (!strcmp (mediatype, "text")) + { + /* Check that we receive a text part only after a + * application/mixed. This is actually a too simple test and we + * should eventually employ a strict MIME structure check. */ + if (!ctx->multipart_mixed_seen) + err = gpg_error (GPG_ERR_UNEXPECTED_MSG); + } + else + { + log_error ("unexpected '%s/%s' message part\n", mediatype, mediasubtype); + err = gpg_error (GPG_ERR_FALSE); /* We do not want the part. */ + } + + return err; +} + + +static gpg_error_t +part_data (void *cookie, const void *data, size_t datalen) +{ + receive_ctx_t ctx = cookie; + + if (data) + { + if (DBG_MIME) + log_debug ("part_data: '%.*s'\n", (int)datalen, (const char*)data); + if (ctx->collect_key_data) + { + if (es_write (ctx->key_data, data, datalen, NULL) + || es_fputs ("\n", ctx->key_data)) + return gpg_error_from_syserror (); + } + if (ctx->collect_wkd_data) + { + if (es_write (ctx->wkd_data, data, datalen, NULL) + || es_fputs ("\n", ctx->wkd_data)) + return gpg_error_from_syserror (); + } + } + else + { + if (DBG_MIME) + log_debug ("part_data: finished\n"); + ctx->collect_key_data = 0; + ctx->collect_wkd_data = 0; + } + return 0; +} + + +/* Receive a WKS mail from FP and process it accordingly. On success + * the RESULT_CB is called with the mediatype and a stream with the + * decrypted data. */ +gpg_error_t +wks_receive (estream_t fp, + gpg_error_t (*result_cb)(void *opaque, + const char *mediatype, + estream_t data, + unsigned int flags), + void *cb_data) +{ + gpg_error_t err; + receive_ctx_t ctx; + mime_parser_t parser; + estream_t plaintext = NULL; + int c; + unsigned int flags = 0; + + ctx = xtrycalloc (1, sizeof *ctx); + if (!ctx) + return gpg_error_from_syserror (); + + err = mime_parser_new (&parser, ctx); + if (err) + goto leave; + if (DBG_PARSER) + mime_parser_set_verbose (parser, 1); + mime_parser_set_t2body (parser, t2body); + mime_parser_set_new_part (parser, new_part); + mime_parser_set_part_data (parser, part_data); + mime_parser_set_collect_encrypted (parser, collect_encrypted); + mime_parser_set_collect_signeddata (parser, collect_signeddata); + mime_parser_set_collect_signature (parser, collect_signature); + + ctx->parser = parser; + + err = mime_parser_parse (parser, fp); + if (err) + goto leave; + + if (ctx->key_data) + log_info ("key data found\n"); + if (ctx->wkd_data) + log_info ("wkd data found\n"); + if (ctx->draft_version_2) + { + log_info ("draft version 2 requested\n"); + flags |= WKS_RECEIVE_DRAFT2; + } + + if (ctx->plaintext) + { + if (opt.verbose) + log_info ("parsing decrypted message\n"); + plaintext = ctx->plaintext; + ctx->plaintext = NULL; + if (ctx->encrypted) + es_rewind (ctx->encrypted); + if (ctx->signeddata) + es_rewind (ctx->signeddata); + if (ctx->signature) + es_rewind (ctx->signature); + err = mime_parser_parse (parser, plaintext); + if (err) + return err; + } + + if (!ctx->key_data && !ctx->wkd_data) + { + log_error ("no suitable data found in the message\n"); + err = gpg_error (GPG_ERR_NO_DATA); + goto leave; + } + + if (ctx->key_data) + { + if (DBG_MIME) + { + es_rewind (ctx->key_data); + log_debug ("Key: '"); + log_printf ("\n"); + while ((c = es_getc (ctx->key_data)) != EOF) + log_printf ("%c", c); + log_printf ("'\n"); + } + if (result_cb) + { + es_rewind (ctx->key_data); + err = result_cb (cb_data, "application/pgp-keys", + ctx->key_data, flags); + if (err) + goto leave; + } + } + if (ctx->wkd_data) + { + if (DBG_MIME) + { + es_rewind (ctx->wkd_data); + log_debug ("WKD: '"); + log_printf ("\n"); + while ((c = es_getc (ctx->wkd_data)) != EOF) + log_printf ("%c", c); + log_printf ("'\n"); + } + if (result_cb) + { + es_rewind (ctx->wkd_data); + err = result_cb (cb_data, "application/vnd.gnupg.wks", + ctx->wkd_data, flags); + if (err) + goto leave; + } + } + + + leave: + es_fclose (plaintext); + mime_parser_release (parser); + ctx->parser = NULL; + es_fclose (ctx->encrypted); + es_fclose (ctx->plaintext); + es_fclose (ctx->signeddata); + es_fclose (ctx->signature); + es_fclose (ctx->key_data); + es_fclose (ctx->wkd_data); + xfree (ctx); + return err; +} diff --git a/tools/wks-util.c b/tools/wks-util.c new file mode 100644 index 0000000..3044fe2 --- /dev/null +++ b/tools/wks-util.c @@ -0,0 +1,1271 @@ +/* wks-utils.c - Common helper functions for wks tools + * Copyright (C) 2016 g10 Code GmbH + * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "../common/util.h" +#include "../common/status.h" +#include "../common/ccparray.h" +#include "../common/exectool.h" +#include "../common/zb32.h" +#include "../common/userids.h" +#include "../common/mbox-util.h" +#include "../common/sysutils.h" +#include "mime-maker.h" +#include "send-mail.h" +#include "gpg-wks.h" + +/* The stream to output the status information. Output is disabled if + this is NULL. */ +static estream_t statusfp; + + + +/* Set the status FD. */ +void +wks_set_status_fd (int fd) +{ + static int last_fd = -1; + + if (fd != -1 && last_fd == fd) + return; + + if (statusfp && statusfp != es_stdout && statusfp != es_stderr) + es_fclose (statusfp); + statusfp = NULL; + if (fd == -1) + return; + + if (fd == 1) + statusfp = es_stdout; + else if (fd == 2) + statusfp = es_stderr; + else + statusfp = es_fdopen (fd, "w"); + if (!statusfp) + { + log_fatal ("can't open fd %d for status output: %s\n", + fd, gpg_strerror (gpg_error_from_syserror ())); + } + last_fd = fd; +} + + +/* Write a status line with code NO followed by the outout of the + * printf style FORMAT. The caller needs to make sure that LFs and + * CRs are not printed. */ +void +wks_write_status (int no, const char *format, ...) +{ + va_list arg_ptr; + + if (!statusfp) + return; /* Not enabled. */ + + es_fputs ("[GNUPG:] ", statusfp); + es_fputs (get_status_string (no), statusfp); + if (format) + { + es_putc (' ', statusfp); + va_start (arg_ptr, format); + es_vfprintf (statusfp, format, arg_ptr); + va_end (arg_ptr); + } + es_putc ('\n', statusfp); +} + + + + +/* Append UID to LIST and return the new item. On success LIST is + * updated. C-style escaping is removed from UID. On error ERRNO is + * set and NULL returned. */ +static uidinfo_list_t +append_to_uidinfo_list (uidinfo_list_t *list, const char *uid, time_t created) +{ + uidinfo_list_t r, sl; + char *plainuid; + + plainuid = decode_c_string (uid); + if (!plainuid) + return NULL; + + sl = xtrymalloc (sizeof *sl + strlen (plainuid)); + if (!sl) + { + xfree (plainuid); + return NULL; + } + + strcpy (sl->uid, plainuid); + sl->created = created; + sl->flags = 0; + sl->mbox = mailbox_from_userid (plainuid); + sl->next = NULL; + if (!*list) + *list = sl; + else + { + for (r = *list; r->next; r = r->next ) + ; + r->next = sl; + } + + xfree (plainuid); + return sl; +} + + +/* Free the list of uid infos at LIST. */ +void +free_uidinfo_list (uidinfo_list_t list) +{ + while (list) + { + uidinfo_list_t tmp = list->next; + xfree (list->mbox); + xfree (list); + list = tmp; + } +} + + + +struct get_key_status_parm_s +{ + const char *fpr; + int found; + int count; +}; + + +static void +get_key_status_cb (void *opaque, const char *keyword, char *args) +{ + struct get_key_status_parm_s *parm = opaque; + + /*log_debug ("%s: %s\n", keyword, args);*/ + if (!strcmp (keyword, "EXPORTED")) + { + parm->count++; + if (!ascii_strcasecmp (args, parm->fpr)) + parm->found = 1; + } +} + +/* Get a key by fingerprint from gpg's keyring and make sure that the + * mail address ADDRSPEC is included in the key. If EXACT is set the + * returned user id must match Addrspec exactly and not just in the + * addr-spec (mailbox) part. The key is returned as a new memory + * stream at R_KEY. */ +gpg_error_t +wks_get_key (estream_t *r_key, const char *fingerprint, const char *addrspec, + int exact) +{ + gpg_error_t err; + ccparray_t ccp; + const char **argv = NULL; + estream_t key = NULL; + struct get_key_status_parm_s parm; + char *filterexp = NULL; + + memset (&parm, 0, sizeof parm); + + *r_key = NULL; + + key = es_fopenmem (0, "w+b"); + if (!key) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); + goto leave; + } + + /* Prefix the key with the MIME content type. */ + es_fputs ("Content-Type: application/pgp-keys\n" + "\n", key); + + filterexp = es_bsprintf ("keep-uid=%s= %s", exact? "uid":"mbox", addrspec); + if (!filterexp) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); + goto leave; + } + + ccparray_init (&ccp, 0); + + ccparray_put (&ccp, "--no-options"); + if (opt.verbose < 2) + ccparray_put (&ccp, "--quiet"); + else + ccparray_put (&ccp, "--verbose"); + ccparray_put (&ccp, "--batch"); + ccparray_put (&ccp, "--status-fd=2"); + ccparray_put (&ccp, "--always-trust"); + ccparray_put (&ccp, "--armor"); + ccparray_put (&ccp, "--export-options=export-minimal"); + ccparray_put (&ccp, "--export-filter"); + ccparray_put (&ccp, filterexp); + ccparray_put (&ccp, "--export"); + ccparray_put (&ccp, "--"); + ccparray_put (&ccp, fingerprint); + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + parm.fpr = fingerprint; + err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL, + NULL, key, + get_key_status_cb, &parm); + if (!err && parm.count > 1) + err = gpg_error (GPG_ERR_TOO_MANY); + else if (!err && !parm.found) + err = gpg_error (GPG_ERR_NOT_FOUND); + if (err) + { + log_error ("export failed: %s\n", gpg_strerror (err)); + goto leave; + } + + es_rewind (key); + *r_key = key; + key = NULL; + + leave: + es_fclose (key); + xfree (argv); + xfree (filterexp); + return err; +} + + + +/* Helper for wks_list_key and wks_filter_uid. */ +static void +key_status_cb (void *opaque, const char *keyword, char *args) +{ + (void)opaque; + + if (DBG_CRYPTO) + log_debug ("gpg status: %s %s\n", keyword, args); +} + + +/* Run gpg on KEY and store the primary fingerprint at R_FPR and the + * list of mailboxes at R_MBOXES. Returns 0 on success; on error NULL + * is stored at R_FPR and R_MBOXES and an error code is returned. + * R_FPR may be NULL if the fingerprint is not needed. */ +gpg_error_t +wks_list_key (estream_t key, char **r_fpr, uidinfo_list_t *r_mboxes) +{ + gpg_error_t err; + ccparray_t ccp; + const char **argv; + estream_t listing; + char *line = NULL; + size_t length_of_line = 0; + size_t maxlen; + ssize_t len; + char **fields = NULL; + int nfields; + int lnr; + char *fpr = NULL; + uidinfo_list_t mboxes = NULL; + + if (r_fpr) + *r_fpr = NULL; + *r_mboxes = NULL; + + /* Open a memory stream. */ + listing = es_fopenmem (0, "w+b"); + if (!listing) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); + return err; + } + + ccparray_init (&ccp, 0); + + ccparray_put (&ccp, "--no-options"); + if (opt.verbose < 2) + ccparray_put (&ccp, "--quiet"); + else + ccparray_put (&ccp, "--verbose"); + ccparray_put (&ccp, "--batch"); + ccparray_put (&ccp, "--status-fd=2"); + ccparray_put (&ccp, "--always-trust"); + ccparray_put (&ccp, "--with-colons"); + ccparray_put (&ccp, "--dry-run"); + ccparray_put (&ccp, "--import-options=import-minimal,import-show"); + ccparray_put (&ccp, "--import"); + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = gnupg_exec_tool_stream (opt.gpg_program, argv, key, + NULL, listing, + key_status_cb, NULL); + if (err) + { + log_error ("import failed: %s\n", gpg_strerror (err)); + goto leave; + } + + es_rewind (listing); + lnr = 0; + maxlen = 2048; /* Set limit. */ + while ((len = es_read_line (listing, &line, &length_of_line, &maxlen)) > 0) + { + lnr++; + if (!maxlen) + { + log_error ("received line too long\n"); + err = gpg_error (GPG_ERR_LINE_TOO_LONG); + goto leave; + } + /* Strip newline and carriage return, if present. */ + while (len > 0 + && (line[len - 1] == '\n' || line[len - 1] == '\r')) + line[--len] = '\0'; + /* log_debug ("line '%s'\n", line); */ + + xfree (fields); + fields = strtokenize_nt (line, ":"); + if (!fields) + { + err = gpg_error_from_syserror (); + log_error ("strtokenize failed: %s\n", gpg_strerror (err)); + goto leave; + } + for (nfields = 0; fields[nfields]; nfields++) + ; + if (!nfields) + { + err = gpg_error (GPG_ERR_INV_ENGINE); + goto leave; + } + if (!strcmp (fields[0], "sec")) + { + /* gpg may return "sec" as the first record - but we do not + * accept secret keys. */ + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + if (lnr == 1 && strcmp (fields[0], "pub")) + { + /* First record is not a public key. */ + err = gpg_error (GPG_ERR_INV_ENGINE); + goto leave; + } + if (lnr > 1 && !strcmp (fields[0], "pub")) + { + /* More than one public key. */ + err = gpg_error (GPG_ERR_TOO_MANY); + goto leave; + } + if (!strcmp (fields[0], "sub") || !strcmp (fields[0], "ssb")) + break; /* We can stop parsing here. */ + + if (!strcmp (fields[0], "fpr") && nfields > 9 && !fpr) + { + fpr = xtrystrdup (fields[9]); + if (!fpr) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + else if (!strcmp (fields[0], "uid") && nfields > 9) + { + if (!append_to_uidinfo_list (&mboxes, fields[9], + parse_timestamp (fields[5], NULL))) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + } + if (len < 0 || es_ferror (listing)) + { + err = gpg_error_from_syserror (); + log_error ("error reading memory stream\n"); + goto leave; + } + + if (!fpr) + { + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + + if (r_fpr) + { + *r_fpr = fpr; + fpr = NULL; + } + *r_mboxes = mboxes; + mboxes = NULL; + + leave: + xfree (fpr); + free_uidinfo_list (mboxes); + xfree (fields); + es_free (line); + xfree (argv); + es_fclose (listing); + return err; +} + + +/* Run gpg as a filter on KEY and write the output to a new stream + * stored at R_NEWKEY. The new key will contain only the user id UID. + * Returns 0 on success. Only one key is expected in KEY. If BINARY + * is set the resulting key is returned as a binary (non-armored) + * keyblock. */ +gpg_error_t +wks_filter_uid (estream_t *r_newkey, estream_t key, const char *uid, + int binary) +{ + gpg_error_t err; + ccparray_t ccp; + const char **argv = NULL; + estream_t newkey; + char *filterexp = NULL; + + *r_newkey = NULL; + + /* Open a memory stream. */ + newkey = es_fopenmem (0, "w+b"); + if (!newkey) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); + return err; + } + + /* Prefix the key with the MIME content type. */ + if (!binary) + es_fputs ("Content-Type: application/pgp-keys\n" + "\n", newkey); + + filterexp = es_bsprintf ("keep-uid=-t uid= %s", uid); + if (!filterexp) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); + goto leave; + } + + ccparray_init (&ccp, 0); + + ccparray_put (&ccp, "--no-options"); + if (opt.verbose < 2) + ccparray_put (&ccp, "--quiet"); + else + ccparray_put (&ccp, "--verbose"); + ccparray_put (&ccp, "--batch"); + ccparray_put (&ccp, "--status-fd=2"); + ccparray_put (&ccp, "--always-trust"); + if (!binary) + ccparray_put (&ccp, "--armor"); + ccparray_put (&ccp, "--import-options=import-export"); + ccparray_put (&ccp, "--import-filter"); + ccparray_put (&ccp, filterexp); + ccparray_put (&ccp, "--import"); + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = gnupg_exec_tool_stream (opt.gpg_program, argv, key, + NULL, newkey, + key_status_cb, NULL); + if (err) + { + log_error ("import/export failed: %s\n", gpg_strerror (err)); + goto leave; + } + + es_rewind (newkey); + *r_newkey = newkey; + newkey = NULL; + + leave: + xfree (filterexp); + xfree (argv); + es_fclose (newkey); + return err; +} + + +/* Helper to write mail to the output(s). */ +gpg_error_t +wks_send_mime (mime_maker_t mime) +{ + gpg_error_t err; + estream_t mail; + + /* Without any option we take a short path. */ + if (!opt.use_sendmail && !opt.output) + { + es_set_binary (es_stdout); + return mime_maker_make (mime, es_stdout); + } + + + mail = es_fopenmem (0, "w+b"); + if (!mail) + { + err = gpg_error_from_syserror (); + return err; + } + + err = mime_maker_make (mime, mail); + + if (!err && opt.output) + { + es_rewind (mail); + err = send_mail_to_file (mail, opt.output); + } + + if (!err && opt.use_sendmail) + { + es_rewind (mail); + err = send_mail (mail); + } + + es_fclose (mail); + return err; +} + + +/* Parse the policy flags by reading them from STREAM and storing them + * into FLAGS. If IGNORE_UNKNOWN is set unknown keywords are + * ignored. */ +gpg_error_t +wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown) +{ + enum tokens { + TOK_SUBMISSION_ADDRESS, + TOK_MAILBOX_ONLY, + TOK_DANE_ONLY, + TOK_AUTH_SUBMIT, + TOK_MAX_PENDING, + TOK_PROTOCOL_VERSION + }; + static struct { + const char *name; + enum tokens token; + } keywords[] = { + { "submission-address", TOK_SUBMISSION_ADDRESS }, + { "mailbox-only", TOK_MAILBOX_ONLY }, + { "dane-only", TOK_DANE_ONLY }, + { "auth-submit", TOK_AUTH_SUBMIT }, + { "max-pending", TOK_MAX_PENDING }, + { "protocol-version", TOK_PROTOCOL_VERSION } + }; + gpg_error_t err = 0; + int lnr = 0; + char line[1024]; + char *p, *keyword, *value; + int i, n; + + memset (flags, 0, sizeof *flags); + + while (es_fgets (line, DIM(line)-1, stream) ) + { + lnr++; + n = strlen (line); + if (!n || line[n-1] != '\n') + { + err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG + : GPG_ERR_INCOMPLETE_LINE); + break; + } + trim_trailing_spaces (line); + /* Skip empty and comment lines. */ + for (p=line; spacep (p); p++) + ; + if (!*p || *p == '#') + continue; + + if (*p == ':') + { + err = gpg_error (GPG_ERR_SYNTAX); + break; + } + + keyword = p; + value = NULL; + if ((p = strchr (p, ':'))) + { + /* Colon found: Keyword with value. */ + *p++ = 0; + for (; spacep (p); p++) + ; + if (!*p) + { + err = gpg_error (GPG_ERR_MISSING_VALUE); + break; + } + value = p; + } + + for (i=0; i < DIM (keywords); i++) + if (!ascii_strcasecmp (keywords[i].name, keyword)) + break; + if (!(i < DIM (keywords))) + { + if (ignore_unknown) + continue; + err = gpg_error (GPG_ERR_INV_NAME); + break; + } + + switch (keywords[i].token) + { + case TOK_SUBMISSION_ADDRESS: + if (!value || !*value) + { + err = gpg_error (GPG_ERR_SYNTAX); + goto leave; + } + xfree (flags->submission_address); + flags->submission_address = xtrystrdup (value); + if (!flags->submission_address) + { + err = gpg_error_from_syserror (); + goto leave; + } + break; + case TOK_MAILBOX_ONLY: flags->mailbox_only = 1; break; + case TOK_DANE_ONLY: flags->dane_only = 1; break; + case TOK_AUTH_SUBMIT: flags->auth_submit = 1; break; + case TOK_MAX_PENDING: + if (!value) + { + err = gpg_error (GPG_ERR_SYNTAX); + goto leave; + } + /* FIXME: Define whether these are seconds, hours, or days + * and decide whether to allow other units. */ + flags->max_pending = atoi (value); + break; + case TOK_PROTOCOL_VERSION: + if (!value) + { + err = gpg_error (GPG_ERR_SYNTAX); + goto leave; + } + flags->protocol_version = atoi (value); + break; + } + } + + if (!err && !es_feof (stream)) + err = gpg_error_from_syserror (); + + leave: + if (err) + log_error ("error reading '%s', line %d: %s\n", + es_fname_get (stream), lnr, gpg_strerror (err)); + + return err; +} + + +void +wks_free_policy (policy_flags_t policy) +{ + if (policy) + { + xfree (policy->submission_address); + memset (policy, 0, sizeof *policy); + } +} + + +/* Write the content of SRC to the new file FNAME. */ +static gpg_error_t +write_to_file (estream_t src, const char *fname) +{ + gpg_error_t err; + estream_t dst; + char buffer[4096]; + size_t nread, written; + + dst = es_fopen (fname, "wb"); + if (!dst) + return gpg_error_from_syserror (); + + do + { + nread = es_fread (buffer, 1, sizeof buffer, src); + if (!nread) + break; + written = es_fwrite (buffer, 1, nread, dst); + if (written != nread) + break; + } + while (!es_feof (src) && !es_ferror (src) && !es_ferror (dst)); + if (!es_feof (src) || es_ferror (src) || es_ferror (dst)) + { + err = gpg_error_from_syserror (); + es_fclose (dst); + gnupg_remove (fname); + return err; + } + + if (es_fclose (dst)) + { + err = gpg_error_from_syserror (); + log_error ("error closing '%s': %s\n", fname, gpg_strerror (err)); + return err; + } + + return 0; +} + + +/* Return the filename and optionally the addrspec for USERID at + * R_FNAME and R_ADDRSPEC. R_ADDRSPEC might also be set on error. If + * HASH_ONLY is set only the has is returned at R_FNAME and no file is + * created. */ +gpg_error_t +wks_fname_from_userid (const char *userid, int hash_only, + char **r_fname, char **r_addrspec) +{ + gpg_error_t err; + char *addrspec = NULL; + const char *domain; + char *hash = NULL; + const char *s; + char shaxbuf[32]; /* Used for SHA-1 and SHA-256 */ + + *r_fname = NULL; + if (r_addrspec) + *r_addrspec = NULL; + + addrspec = mailbox_from_userid (userid); + if (!addrspec) + { + if (opt.verbose || hash_only) + log_info ("\"%s\" is not a proper mail address\n", userid); + err = gpg_error (GPG_ERR_INV_USER_ID); + goto leave; + } + + domain = strchr (addrspec, '@'); + log_assert (domain); + domain++; + if (strchr (domain, '/') || strchr (domain, '\\')) + { + log_info ("invalid domain detected ('%s')\n", domain); + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + + /* Hash user ID and create filename. */ + s = strchr (addrspec, '@'); + log_assert (s); + gcry_md_hash_buffer (GCRY_MD_SHA1, shaxbuf, addrspec, s - addrspec); + hash = zb32_encode (shaxbuf, 8*20); + if (!hash) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (hash_only) + { + *r_fname = hash; + hash = NULL; + err = 0; + } + else + { + *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL); + if (!*r_fname) + err = gpg_error_from_syserror (); + else + err = 0; + } + + leave: + if (r_addrspec && addrspec) + *r_addrspec = addrspec; + else + xfree (addrspec); + xfree (hash); + return err; +} + + +/* Compute the the full file name for the key with ADDRSPEC and return + * it at R_FNAME. */ +gpg_error_t +wks_compute_hu_fname (char **r_fname, const char *addrspec) +{ + gpg_error_t err; + char *hash; + const char *domain; + char sha1buf[20]; + char *fname; + struct stat sb; + + *r_fname = NULL; + + domain = strchr (addrspec, '@'); + if (!domain || !domain[1] || domain == addrspec) + return gpg_error (GPG_ERR_INV_ARG); + domain++; + if (strchr (domain, '/') || strchr (domain, '\\')) + { + log_info ("invalid domain detected ('%s')\n", domain); + return gpg_error (GPG_ERR_NOT_FOUND); + } + + gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, addrspec, domain - addrspec - 1); + hash = zb32_encode (sha1buf, 8*20); + if (!hash) + return gpg_error_from_syserror (); + + /* Try to create missing directories below opt.directory. */ + fname = make_filename_try (opt.directory, domain, NULL); + if (fname && gnupg_stat (fname, &sb) + && gpg_err_code_from_syserror () == GPG_ERR_ENOENT) + if (!gnupg_mkdir (fname, "-rwxr-xr-x") && opt.verbose) + log_info ("directory '%s' created\n", fname); + xfree (fname); + fname = make_filename_try (opt.directory, domain, "hu", NULL); + if (fname && gnupg_stat (fname, &sb) + && gpg_err_code_from_syserror () == GPG_ERR_ENOENT) + if (!gnupg_mkdir (fname, "-rwxr-xr-x") && opt.verbose) + log_info ("directory '%s' created\n", fname); + xfree (fname); + + /* Create the filename. */ + fname = make_filename_try (opt.directory, domain, "hu", hash, NULL); + err = fname? 0 : gpg_error_from_syserror (); + + if (err) + xfree (fname); + else + *r_fname = fname; /* Okay. */ + xfree (hash); + return err; +} + + +/* Make sure that a policy file exists for addrspec. Directories must + * already exist. */ +static gpg_error_t +ensure_policy_file (const char *addrspec) +{ + gpg_err_code_t ec; + gpg_error_t err; + const char *domain; + char *fname; + estream_t fp; + + domain = strchr (addrspec, '@'); + if (!domain || !domain[1] || domain == addrspec) + return gpg_error (GPG_ERR_INV_ARG); + domain++; + if (strchr (domain, '/') || strchr (domain, '\\')) + { + log_info ("invalid domain detected ('%s')\n", domain); + return gpg_error (GPG_ERR_NOT_FOUND); + } + + /* Create the filename. */ + fname = make_filename_try (opt.directory, domain, "policy", NULL); + err = fname? 0 : gpg_error_from_syserror (); + if (err) + goto leave; + + /* First a quick check whether it already exists. */ + if (!(ec = gnupg_access (fname, F_OK))) + { + err = 0; /* File already exists. */ + goto leave; + } + err = gpg_error (ec); + if (gpg_err_code (err) == GPG_ERR_ENOENT) + err = 0; + else + { + log_error ("domain %s: problem with '%s': %s\n", + domain, fname, gpg_strerror (err)); + goto leave; + } + + /* Now create the file. */ + fp = es_fopen (fname, "wxb"); + if (!fp) + { + err = gpg_error_from_syserror (); + if (gpg_err_code (err) == GPG_ERR_EEXIST) + err = 0; /* Was created between the gnupg_access() and es_fopen(). */ + else + log_error ("domain %s: error creating '%s': %s\n", + domain, fname, gpg_strerror (err)); + goto leave; + } + + es_fprintf (fp, "# Policy flags for domain %s\n", domain); + if (es_ferror (fp) || es_fclose (fp)) + { + err = gpg_error_from_syserror (); + log_error ("error writing '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + + if (opt.verbose) + log_info ("policy file '%s' created\n", fname); + + /* Make sure the policy file world readable. */ + if (gnupg_chmod (fname, "-rw-r--r--")) + { + err = gpg_error_from_syserror (); + log_error ("can't set permissions of '%s': %s\n", + fname, gpg_strerror (err)); + goto leave; + } + + leave: + xfree (fname); + return err; +} + + +/* Helper form wks_cmd_install_key. */ +static gpg_error_t +install_key_from_spec_file (const char *fname) +{ + gpg_error_t err; + estream_t fp; + char *line = NULL; + size_t linelen = 0; + size_t maxlen = 2048; + char *fields[2]; + unsigned int lnr = 0; + + if (!fname || !strcmp (fname, "")) + fp = es_stdin; + else + fp = es_fopen (fname, "rb"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + + while (es_read_line (fp, &line, &linelen, &maxlen) > 0) + { + if (!maxlen) + { + err = gpg_error (GPG_ERR_LINE_TOO_LONG); + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + lnr++; + trim_spaces (line); + if (!*line || *line == '#') + continue; + if (split_fields (line, fields, DIM(fields)) < 2) + { + log_error ("error reading '%s': syntax error at line %u\n", + fname, lnr); + continue; + } + err = wks_cmd_install_key (fields[0], fields[1]); + if (err) + goto leave; + } + if (es_ferror (fp)) + { + err = gpg_error_from_syserror (); + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + + leave: + if (fp != es_stdin) + es_fclose (fp); + es_free (line); + return err; +} + + +/* The core of the code to install a key as a file. */ +gpg_error_t +wks_install_key_core (estream_t key, const char *addrspec) +{ + gpg_error_t err; + char *huname = NULL; + + /* Hash user ID and create filename. */ + err = wks_compute_hu_fname (&huname, addrspec); + if (err) + goto leave; + + /* Now that wks_compute_hu_fname has created missing directories we + * can create a policy file if it does not exist. */ + err = ensure_policy_file (addrspec); + if (err) + goto leave; + + /* Publish. */ + err = write_to_file (key, huname); + if (err) + { + log_error ("copying key to '%s' failed: %s\n", huname,gpg_strerror (err)); + goto leave; + } + + /* Make sure it is world readable. */ + if (gnupg_chmod (huname, "-rw-r--r--")) + log_error ("can't set permissions of '%s': %s\n", + huname, gpg_strerror (gpg_err_code_from_syserror())); + + leave: + xfree (huname); + return err; +} + + +/* Install a single key into the WKD by reading FNAME and extracting + * USERID. If USERID is NULL FNAME is expected to be a list of fpr + * mbox lines and for each line the respective key will be + * installed. */ +gpg_error_t +wks_cmd_install_key (const char *fname, const char *userid) +{ + gpg_error_t err; + KEYDB_SEARCH_DESC desc; + estream_t fp = NULL; + char *addrspec = NULL; + char *fpr = NULL; + uidinfo_list_t uidlist = NULL; + uidinfo_list_t uid, thisuid; + time_t thistime; + int any; + + if (!userid) + return install_key_from_spec_file (fname); + + addrspec = mailbox_from_userid (userid); + if (!addrspec) + { + log_error ("\"%s\" is not a proper mail address\n", userid); + err = gpg_error (GPG_ERR_INV_USER_ID); + goto leave; + } + + if (!classify_user_id (fname, &desc, 1) + && (desc.mode == KEYDB_SEARCH_MODE_FPR + || desc.mode == KEYDB_SEARCH_MODE_FPR20)) + { + /* FNAME looks like a fingerprint. Get the key from the + * standard keyring. */ + err = wks_get_key (&fp, fname, addrspec, 0); + if (err) + { + log_error ("error getting key '%s' (uid='%s'): %s\n", + fname, addrspec, gpg_strerror (err)); + goto leave; + } + } + else /* Take it from the file */ + { + fp = es_fopen (fname, "rb"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + } + + /* List the key so that we can figure out the newest UID with the + * requested addrspec. */ + err = wks_list_key (fp, &fpr, &uidlist); + if (err) + { + log_error ("error parsing key: %s\n", gpg_strerror (err)); + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + thistime = 0; + thisuid = NULL; + any = 0; + for (uid = uidlist; uid; uid = uid->next) + { + if (!uid->mbox) + continue; /* Should not happen anyway. */ + if (ascii_strcasecmp (uid->mbox, addrspec)) + continue; /* Not the requested addrspec. */ + any = 1; + if (uid->created > thistime) + { + thistime = uid->created; + thisuid = uid; + } + } + if (!thisuid) + thisuid = uidlist; /* This is the case for a missing timestamp. */ + if (!any) + { + log_error ("public key in '%s' has no mail address '%s'\n", + fname, addrspec); + err = gpg_error (GPG_ERR_INV_USER_ID); + goto leave; + } + + if (opt.verbose) + log_info ("using key with user id '%s'\n", thisuid->uid); + + { + estream_t fp2; + + es_rewind (fp); + err = wks_filter_uid (&fp2, fp, thisuid->uid, 1); + if (err) + { + log_error ("error filtering key: %s\n", gpg_strerror (err)); + err = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + es_fclose (fp); + fp = fp2; + } + + err = wks_install_key_core (fp, addrspec); + if (!opt.quiet) + log_info ("key %s published for '%s'\n", fpr, addrspec); + + + leave: + free_uidinfo_list (uidlist); + xfree (fpr); + xfree (addrspec); + es_fclose (fp); + return err; +} + + +/* Remove the key with mail address in USERID. */ +gpg_error_t +wks_cmd_remove_key (const char *userid) +{ + gpg_error_t err; + char *addrspec = NULL; + char *fname = NULL; + + err = wks_fname_from_userid (userid, 0, &fname, &addrspec); + if (err) + goto leave; + + if (gnupg_remove (fname)) + { + err = gpg_error_from_syserror (); + if (gpg_err_code (err) == GPG_ERR_ENOENT) + { + if (!opt.quiet) + log_info ("key for '%s' is not installed\n", addrspec); + log_inc_errorcount (); + err = 0; + } + else + log_error ("error removing '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + + if (opt.verbose) + log_info ("key for '%s' removed\n", addrspec); + err = 0; + + leave: + xfree (fname); + xfree (addrspec); + return err; +} + + +/* Print the WKD hash for the user id to stdout. */ +gpg_error_t +wks_cmd_print_wkd_hash (const char *userid) +{ + gpg_error_t err; + char *addrspec, *fname; + + err = wks_fname_from_userid (userid, 1, &fname, &addrspec); + if (err) + return err; + + es_printf ("%s %s\n", fname, addrspec); + + xfree (fname); + xfree (addrspec); + return err; +} + + +/* Print the WKD URL for the user id to stdout. */ +gpg_error_t +wks_cmd_print_wkd_url (const char *userid) +{ + gpg_error_t err; + char *addrspec, *fname; + char *domain; + + err = wks_fname_from_userid (userid, 1, &fname, &addrspec); + if (err) + return err; + + domain = strchr (addrspec, '@'); + if (domain) + *domain++ = 0; + + es_printf ("https://openpgpkey.%s/.well-known/openpgpkey/%s/hu/%s?l=%s\n", + domain, domain, fname, addrspec); + + xfree (fname); + xfree (addrspec); + return err; +} |