summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/ChangeLog-20111284
-rw-r--r--tools/Makefile.am198
-rw-r--r--tools/Makefile.in1589
-rw-r--r--tools/Manifest6
-rwxr-xr-xtools/addgnupghome122
-rwxr-xr-xtools/applygnupgdefaults81
-rw-r--r--tools/call-dirmngr.c381
-rw-r--r--tools/call-dirmngr.h35
-rw-r--r--tools/ccidmon.c882
-rw-r--r--tools/clean-sat.c35
-rwxr-xr-xtools/convert-from-10655
-rw-r--r--tools/gpg-check-pattern-w32info.rc52
-rw-r--r--tools/gpg-check-pattern.c633
-rw-r--r--tools/gpg-check-pattern.w32-manifest.in18
-rw-r--r--tools/gpg-connect-agent-w32info.rc52
-rw-r--r--tools/gpg-connect-agent.c2266
-rw-r--r--tools/gpg-connect-agent.w32-manifest.in18
-rw-r--r--tools/gpg-wks-client-w32info.rc52
-rw-r--r--tools/gpg-wks-client.c1984
-rw-r--r--tools/gpg-wks-client.w32-manifest.in18
-rw-r--r--tools/gpg-wks-server.c2058
-rw-r--r--tools/gpg-wks.h128
-rw-r--r--tools/gpg-zip.in151
-rw-r--r--tools/gpgconf-comp.c3462
-rw-r--r--tools/gpgconf-w32info.rc52
-rw-r--r--tools/gpgconf.c1605
-rw-r--r--tools/gpgconf.h131
-rw-r--r--tools/gpgconf.w32-manifest.in18
-rw-r--r--tools/gpgparsemail.c816
-rw-r--r--tools/gpgsplit.c897
-rw-r--r--tools/gpgtar-create.c1297
-rw-r--r--tools/gpgtar-extract.c485
-rw-r--r--tools/gpgtar-list.c590
-rw-r--r--tools/gpgtar-w32info.rc52
-rw-r--r--tools/gpgtar.c638
-rw-r--r--tools/gpgtar.h156
-rw-r--r--tools/gpgtar.w32-manifest.in18
-rwxr-xr-xtools/lspgpot27
-rwxr-xr-xtools/mail-signed-keys114
-rw-r--r--tools/make-dns-cert.c247
-rw-r--r--tools/mime-maker.c777
-rw-r--r--tools/mime-maker.h50
-rw-r--r--tools/mime-parser.c833
-rw-r--r--tools/mime-parser.h60
-rw-r--r--tools/no-libgcrypt.c162
-rw-r--r--tools/rfc822parse.c1331
-rw-r--r--tools/rfc822parse.h81
-rw-r--r--tools/send-mail.c140
-rw-r--r--tools/send-mail.h28
-rw-r--r--tools/sockprox.c551
-rw-r--r--tools/watchgnupg.c519
-rw-r--r--tools/wks-receive.c534
-rw-r--r--tools/wks-util.c1271
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;
+}