From 82a17c9fb3d64ccdd474c3bedf564368f77e84a4 Mon Sep 17 00:00:00 2001 From: Repo Admin Date: Sat, 19 Oct 2002 07:55:27 +0000 Subject: This commit was manufactured by cvs2svn to create branch 'GNUPG-1-9-BRANCH'. --- agent/command.c | 697 -------------------------------------------------------- 1 file changed, 697 deletions(-) delete mode 100644 agent/command.c (limited to 'agent/command.c') diff --git a/agent/command.c b/agent/command.c deleted file mode 100644 index 55ee8b84a..000000000 --- a/agent/command.c +++ /dev/null @@ -1,697 +0,0 @@ -/* command.c - gpg-agent command handler - * Copyright (C) 2001, 2002 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA - */ - -/* FIXME: we should not use the default assuan buffering but setup - some buffering in secure mempory to protect session keys etc. */ - -#include -#include -#include -#include -#include -#include -#include - -#include "agent.h" -#include "../assuan/assuan.h" - -/* maximum allowed size of the inquired ciphertext */ -#define MAXLEN_CIPHERTEXT 4096 -/* maximum allowed size of the key parameters */ -#define MAXLEN_KEYPARAM 1024 - -#define set_error(e,t) assuan_set_error (ctx, ASSUAN_ ## e, (t)) - - -#if MAX_DIGEST_LEN < 20 -#error MAX_DIGEST_LEN shorter than keygrip -#endif - -/* Data used to associate an Assuan context with local server data */ -struct server_local_s { - ASSUAN_CONTEXT assuan_ctx; - int message_fd; - int use_cache_for_signing; -}; - - - - - -static void -reset_notify (ASSUAN_CONTEXT ctx) -{ - CTRL ctrl = assuan_get_pointer (ctx); - - memset (ctrl->keygrip, 0, 20); - ctrl->have_keygrip = 0; - ctrl->digest.valuelen = 0; -} - - -/* Check whether the option NAME appears in LINE */ -static int -has_option (const char *line, const char *name) -{ - const char *s; - int n = strlen (name); - - s = strstr (line, name); - return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n))); -} - - - - -/* ISTRUSTED - - Return OK when we have an entry with this fingerprint in our - trustlist */ -static int -cmd_istrusted (ASSUAN_CONTEXT ctx, char *line) -{ - int rc, n, i; - char *p; - char fpr[41]; - - /* parse the fingerprint value */ - for (p=line,n=0; hexdigitp (p); p++, n++) - ; - if (*p || !(n == 40 || n == 32)) - return set_error (Parameter_Error, "invalid fingerprint"); - i = 0; - if (n==32) - { - strcpy (fpr, "00000000"); - i += 8; - } - for (p=line; i < 40; p++, i++) - fpr[i] = *p >= 'a'? (*p & 0xdf): *p; - fpr[i] = 0; - rc = agent_istrusted (fpr); - if (!rc) - return 0; - else if (rc == -1) - return ASSUAN_Not_Trusted; - else - { - log_error ("command is_trusted failed: %s\n", gnupg_strerror (rc)); - return map_to_assuan_status (rc); - } -} - -/* LISTTRUSTED - - List all entries from the trustlist */ -static int -cmd_listtrusted (ASSUAN_CONTEXT ctx, char *line) -{ - int rc = agent_listtrusted (ctx); - if (rc) - log_error ("command listtrusted failed: %s\n", gnupg_strerror (rc)); - return map_to_assuan_status (rc); -} - - -/* MARKTRUSTED - - Store a new key in into the trustlist*/ -static int -cmd_marktrusted (ASSUAN_CONTEXT ctx, char *line) -{ - int rc, n, i; - char *p; - char fpr[41]; - int flag; - - /* parse the fingerprint value */ - for (p=line,n=0; hexdigitp (p); p++, n++) - ; - if (!spacep (p) || !(n == 40 || n == 32)) - return set_error (Parameter_Error, "invalid fingerprint"); - i = 0; - if (n==32) - { - strcpy (fpr, "00000000"); - i += 8; - } - for (p=line; i < 40; p++, i++) - fpr[i] = *p >= 'a'? (*p & 0xdf): *p; - fpr[i] = 0; - - while (spacep (p)) - p++; - flag = *p++; - if ( (flag != 'S' && flag != 'P') || !spacep (p) ) - return set_error (Parameter_Error, "invalid flag - must be P or S"); - while (spacep (p)) - p++; - - rc = agent_marktrusted (p, fpr, flag); - if (rc) - log_error ("command marktrusted failed: %s\n", gnupg_strerror (rc)); - return map_to_assuan_status (rc); -} - - - - -/* HAVEKEY - - Return success when the secret key is available */ -static int -cmd_havekey (ASSUAN_CONTEXT ctx, char *line) -{ - int n; - char *p; - unsigned char buf[20]; - - /* parse the hash value */ - for (p=line,n=0; hexdigitp (p); p++, n++) - ; - if (*p) - return set_error (Parameter_Error, "invalid hexstring"); - if ((n&1)) - return set_error (Parameter_Error, "odd number of digits"); - n /= 2; - if (n != 20) - return set_error (Parameter_Error, "invalid length of keygrip"); - - for (p=line, n=0; n < 20; p += 2, n++) - buf[n] = xtoi_2 (p); - - if (agent_key_available (buf)) - return ASSUAN_No_Secret_Key; - - return 0; -} - - -/* SIGKEY - SETKEY - - Set the key used for a sign or decrypt operation */ -static int -cmd_sigkey (ASSUAN_CONTEXT ctx, char *line) -{ - int n; - char *p; - CTRL ctrl = assuan_get_pointer (ctx); - unsigned char *buf; - - /* parse the hash value */ - for (p=line,n=0; hexdigitp (p); p++, n++) - ; - if (*p) - return set_error (Parameter_Error, "invalid hexstring"); - if ((n&1)) - return set_error (Parameter_Error, "odd number of digits"); - n /= 2; - if (n != 20) - return set_error (Parameter_Error, "invalid length of keygrip"); - - buf = ctrl->keygrip; - for (p=line, n=0; n < 20; p += 2, n++) - buf[n] = xtoi_2 (p); - ctrl->have_keygrip = 1; - return 0; -} - -/* SETHASH - - The client can use this command to tell the server about the data - (which usually is a hash) to be signed. */ -static int -cmd_sethash (ASSUAN_CONTEXT ctx, char *line) -{ - int n; - char *p; - CTRL ctrl = assuan_get_pointer (ctx); - unsigned char *buf; - char *endp; - int algo; - - /* parse the algo number and check it */ - algo = (int)strtoul (line, &endp, 10); - for (line = endp; *line == ' ' || *line == '\t'; line++) - ; - if (!algo || gcry_md_test_algo (algo)) - return set_error (Unsupported_Algorithm, NULL); - ctrl->digest.algo = algo; - - /* parse the hash value */ - for (p=line,n=0; hexdigitp (p); p++, n++) - ; - if (*p) - return set_error (Parameter_Error, "invalid hexstring"); - if ((n&1)) - return set_error (Parameter_Error, "odd number of digits"); - n /= 2; - if (n != 16 && n != 20 && n != 24 && n != 32) - return set_error (Parameter_Error, "unsupported length of hash"); - if (n > MAX_DIGEST_LEN) - return set_error (Parameter_Error, "hash value to long"); - - buf = ctrl->digest.value; - ctrl->digest.valuelen = n; - for (p=line, n=0; n < ctrl->digest.valuelen; p += 2, n++) - buf[n] = xtoi_2 (p); - for (; n < ctrl->digest.valuelen; n++) - buf[n] = 0; - return 0; -} - - -/* PKSIGN - - Perform the actual sign operation. Neither input nor output are - sensitive to eavesdropping */ -static int -cmd_pksign (ASSUAN_CONTEXT ctx, char *line) -{ - int rc; - int ignore_cache = 0; - CTRL ctrl = assuan_get_pointer (ctx); - - if (opt.ignore_cache_for_signing) - ignore_cache = 1; - else if (!ctrl->server_local->use_cache_for_signing) - ignore_cache = 1; - - rc = agent_pksign (ctrl, assuan_get_data_fp (ctx), ignore_cache); - if (rc) - log_error ("command pksign failed: %s\n", gnupg_strerror (rc)); - return map_to_assuan_status (rc); -} - -/* PKDECRYPT - - Perform the actual decrypt operation. Input is not - sensitive to eavesdropping */ -static int -cmd_pkdecrypt (ASSUAN_CONTEXT ctx, char *line) -{ - int rc; - CTRL ctrl = assuan_get_pointer (ctx); - char *value; - size_t valuelen; - - /* First inquire the data to decrypt */ - rc = assuan_inquire (ctx, "CIPHERTEXT", - &value, &valuelen, MAXLEN_CIPHERTEXT); - if (rc) - return rc; - - rc = agent_pkdecrypt (ctrl, value, valuelen, assuan_get_data_fp (ctx)); - xfree (value); - if (rc) - log_error ("command pkdecrypt failed: %s\n", gnupg_strerror (rc)); - return map_to_assuan_status (rc); -} - - -/* GENKEY - - Generate a new key, store the secret part and return the public - part. Here is an example transaction: - - C: GENKEY - S: INQUIRE KEYPARM - C: D (genkey (rsa (nbits 1024))) - C: END - S: D (public-key - S: D (rsa (n 326487324683264) (e 10001))) - S OK key created -*/ - -static int -cmd_genkey (ASSUAN_CONTEXT ctx, char *line) -{ - CTRL ctrl = assuan_get_pointer (ctx); - int rc; - char *value; - size_t valuelen; - - /* First inquire the parameters */ - rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM); - if (rc) - return rc; - - rc = agent_genkey (ctrl, value, valuelen, assuan_get_data_fp (ctx)); - xfree (value); - if (rc) - log_error ("command genkey failed: %s\n", gnupg_strerror (rc)); - return map_to_assuan_status (rc); -} - - -static void -plus_to_blank (char *s) -{ - for (; *s; s++) - { - if (*s == '+') - *s = ' '; - } -} - -/* GET_PASSPHRASE [ ] - - This function is usually used to ask for a passphrase to be used - for conventional encryption, but may also be used by programs which - need specal handling of passphrases. This command uses a syntax - which helps clients to use the agent with minimum effort. The - agent either returns with an error or with a OK followed by the hex - encoded passphrase. Note that the length of the strings is - implicitly limited by the maximum length of a command. -*/ - -static int -cmd_get_passphrase (ASSUAN_CONTEXT ctx, char *line) -{ - int rc; - const char *pw; - char *response; - char *cacheid = NULL, *desc = NULL, *prompt = NULL, *errtext = NULL; - char *p; - void *cache_marker; - - /* parse the stuff */ - for (p=line; *p == ' '; p++) - ; - cacheid = p; - p = strchr (cacheid, ' '); - if (p) - { - *p++ = 0; - while (*p == ' ') - p++; - errtext = p; - p = strchr (errtext, ' '); - if (p) - { - *p++ = 0; - while (*p == ' ') - p++; - prompt = p; - p = strchr (prompt, ' '); - if (p) - { - *p++ = 0; - while (*p == ' ') - p++; - desc = p; - p = strchr (desc, ' '); - if (p) - *p = 0; /* ignore garbage */ - } - } - } - if (!cacheid || !*cacheid || strlen (cacheid) > 50) - return set_error (Parameter_Error, "invalid length of cacheID"); - if (!desc) - return set_error (Parameter_Error, "no description given"); - - if (!strcmp (cacheid, "X")) - cacheid = NULL; - if (!strcmp (errtext, "X")) - errtext = NULL; - if (!strcmp (prompt, "X")) - prompt = NULL; - if (!strcmp (desc, "X")) - desc = NULL; - - /* Note: we store the hexified versions in the cache. */ - pw = cacheid ? agent_get_cache (cacheid, &cache_marker) : NULL; - if (pw) - { - assuan_begin_confidential (ctx); - rc = assuan_set_okay_line (ctx, pw); - agent_unlock_cache_entry (&cache_marker); - } - else - { - /* Note, that we only need to replace the + characters and - should leave the other escaping in place because the escaped - string is send verbatim to the pinentry which does the - unescaping (but not the + replacing) */ - if (errtext) - plus_to_blank (errtext); - if (prompt) - plus_to_blank (prompt); - if (desc) - plus_to_blank (desc); - - rc = agent_get_passphrase (&response, desc, prompt, errtext); - if (!rc) - { - if (cacheid) - agent_put_cache (cacheid, response, 0); - assuan_begin_confidential (ctx); - rc = assuan_set_okay_line (ctx, response); - xfree (response); - } - } - - if (rc) - log_error ("command get_passphrase failed: %s\n", gnupg_strerror (rc)); - return map_to_assuan_status (rc); -} - - -/* CLEAR_PASSPHRASE - - may be used to invalidate the cache entry for a passphrase. The - function returns with OK even when there is no cached passphrase. -*/ - -static int -cmd_clear_passphrase (ASSUAN_CONTEXT ctx, char *line) -{ - char *cacheid = NULL; - char *p; - - /* parse the stuff */ - for (p=line; *p == ' '; p++) - ; - cacheid = p; - p = strchr (cacheid, ' '); - if (p) - *p = 0; /* ignore garbage */ - if (!cacheid || !*cacheid || strlen (cacheid) > 50) - return set_error (Parameter_Error, "invalid length of cacheID"); - - agent_put_cache (cacheid, NULL, 0); - return 0; -} - - -/* LEARN [--send] - - Learn something about the currently inserted smartcard. With - --send the new certificates are send back. */ -static int -cmd_learn (ASSUAN_CONTEXT ctx, char *line) -{ - int rc; - - rc = agent_handle_learn (has_option (line, "--send")? ctx : NULL); - if (rc) - log_error ("command learn failed: %s\n", gnupg_strerror (rc)); - return map_to_assuan_status (rc); -} - - - -static int -option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value) -{ - CTRL ctrl = assuan_get_pointer (ctx); - - /* FIXME: We should not change opt. here. It is not a problem right - now but as soon as we are allowing concurrent connections we mess - things up */ - if (!strcmp (key, "display")) - { - if (opt.display) - free (opt.display); - opt.display = strdup (value); - if (!opt.display) - return ASSUAN_Out_Of_Core; - } - else if (!strcmp (key, "ttyname")) - { - if (opt.ttyname) - free (opt.ttyname); - opt.ttyname = strdup (value); - if (!opt.ttyname) - return ASSUAN_Out_Of_Core; - } - else if (!strcmp (key, "ttytype")) - { - if (opt.ttytype) - free (opt.ttytype); - opt.ttytype = strdup (value); - if (!opt.ttytype) - return ASSUAN_Out_Of_Core; - } - else if (!strcmp (key, "lc-ctype")) - { - if (opt.lc_ctype) - free (opt.lc_ctype); - opt.lc_ctype = strdup (value); - if (!opt.lc_ctype) - return ASSUAN_Out_Of_Core; - } - else if (!strcmp (key, "lc-messages")) - { - if (opt.lc_messages) - free (opt.lc_messages); - opt.lc_messages = strdup (value); - if (!opt.lc_messages) - return ASSUAN_Out_Of_Core; - } - else if (!strcmp (key, "use-cache-for-signing")) - ctrl->server_local->use_cache_for_signing = *value? atoi (value) : 0; - else - return ASSUAN_Invalid_Option; - - return 0; -} - - -/* Tell the assuan library about our commands */ -static int -register_commands (ASSUAN_CONTEXT ctx) -{ - static struct { - const char *name; - int cmd_id; - int (*handler)(ASSUAN_CONTEXT, char *line); - } table[] = { - { "ISTRUSTED", 0, cmd_istrusted }, - { "HAVEKEY", 0, cmd_havekey }, - { "SIGKEY", 0, cmd_sigkey }, - { "SETKEY", 0, cmd_sigkey }, - { "SETHASH", 0, cmd_sethash }, - { "PKSIGN", 0, cmd_pksign }, - { "PKDECRYPT", 0, cmd_pkdecrypt }, - { "GENKEY", 0, cmd_genkey }, - { "GET_PASSPHRASE",0, cmd_get_passphrase }, - { "CLEAR_PASSPHRASE",0, cmd_clear_passphrase }, - { "LISTTRUSTED", 0, cmd_listtrusted }, - { "MARKTRUSTED", 0, cmd_marktrusted }, - { "LEARN", 0, cmd_learn }, - { "", ASSUAN_CMD_INPUT, NULL }, - { "", ASSUAN_CMD_OUTPUT, NULL }, - { NULL } - }; - int i, j, rc; - - for (i=j=0; table[i].name; i++) - { - rc = assuan_register_command (ctx, - table[i].cmd_id? table[i].cmd_id - : (ASSUAN_CMD_USER + j++), - table[i].name, table[i].handler); - if (rc) - return rc; - } - assuan_register_reset_notify (ctx, reset_notify); - assuan_register_option_handler (ctx, option_handler); - return 0; -} - - -/* Startup the server. If LISTEN_FD and FD is given as -1, this is a simple - piper server, otherwise it is a regular server */ -void -start_command_handler (int listen_fd, int fd) -{ - int rc; - ASSUAN_CONTEXT ctx; - struct server_control_s ctrl; - - memset (&ctrl, 0, sizeof ctrl); - - if (listen_fd == -1 && fd == -1) - { - int filedes[2]; - - filedes[0] = 0; - filedes[1] = 1; - rc = assuan_init_pipe_server (&ctx, filedes); - } - else if (listen_fd != -1) - { - rc = assuan_init_socket_server (&ctx, listen_fd); - } - else - { - rc = assuan_init_connected_socket_server (&ctx, fd); - } - if (rc) - { - log_error ("failed to initialize the server: %s\n", - assuan_strerror(rc)); - agent_exit (2); - } - rc = register_commands (ctx); - if (rc) - { - log_error ("failed to register commands with Assuan: %s\n", - assuan_strerror(rc)); - agent_exit (2); - } - - assuan_set_pointer (ctx, &ctrl); - ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local); - ctrl.server_local->assuan_ctx = ctx; - ctrl.server_local->message_fd = -1; - ctrl.server_local->use_cache_for_signing = 1; - - if (DBG_ASSUAN) - assuan_set_log_stream (ctx, log_get_stream ()); - - for (;;) - { - rc = assuan_accept (ctx); - if (rc == -1) - { - break; - } - else if (rc) - { - log_info ("Assuan accept problem: %s\n", assuan_strerror (rc)); - break; - } - - rc = assuan_process (ctx); - if (rc) - { - log_info ("Assuan processing failed: %s\n", assuan_strerror (rc)); - continue; - } - } - - - assuan_deinit_server (ctx); -} - -- cgit From 9ca4830a5b8392bc7bb01211407c876fd40b49d4 Mon Sep 17 00:00:00 2001 From: Repo Admin Date: Tue, 5 Aug 2003 17:11:04 +0000 Subject: This commit was manufactured by cvs2svn to create branch 'GNUPG-1-9-BRANCH'. --- agent/ChangeLog | 565 +++++++++++ agent/Makefile.am | 62 ++ agent/agent.h | 226 +++++ agent/call-scd.c | 661 +++++++++++++ agent/command.c | 782 +++++++++++++++ agent/divert-scd.c | 319 +++++++ agent/findkey.c | 359 +++++++ agent/genkey.c | 240 +++++ agent/gpg-agent.c | 1063 +++++++++++++++++++++ agent/learncard.c | 448 +++++++++ agent/minip12.c | 1140 ++++++++++++++++++++++ agent/minip12.h | 33 + agent/pkdecrypt.c | 138 +++ agent/pksign.c | 185 ++++ agent/protect-tool.c | 977 +++++++++++++++++++ agent/protect.c | 971 +++++++++++++++++++ agent/simple-pwquery.c | 486 ++++++++++ common/ChangeLog | 219 +++++ common/Makefile.am | 58 ++ common/README | 11 + common/errors.h | 110 +++ common/gettime.c | 250 +++++ common/iobuf.c | 2415 +++++++++++++++++++++++++++++++++++++++++++++++ common/iobuf.h | 170 ++++ common/maperror.c | 157 +++ common/membuf.c | 89 ++ common/membuf.h | 41 + common/miscellaneous.c | 126 +++ common/simple-pwquery.c | 486 ++++++++++ common/simple-pwquery.h | 69 ++ common/ttyio.c | 508 ++++++++++ common/ttyio.h | 40 + common/util.h | 120 +++ common/yesno.c | 96 ++ kbx/Makefile.am | 52 + kbx/kbxutil.c | 339 +++++++ kbx/keybox-blob.c | 1008 ++++++++++++++++++++ scd/ChangeLog | 242 +++++ scd/Makefile.am | 72 ++ scd/apdu.c | 558 +++++++++++ scd/apdu.h | 73 ++ scd/app-common.h | 128 +++ scd/app-openpgp.c | 1482 +++++++++++++++++++++++++++++ scd/app.c | 278 ++++++ scd/card-common.h | 73 ++ scd/card-p15.c | 502 ++++++++++ scd/card.c | 564 +++++++++++ scd/command.c | 1034 ++++++++++++++++++++ scd/iso7816.c | 371 ++++++++ scd/iso7816.h | 56 ++ scd/sc-copykeys.c | 731 ++++++++++++++ scd/sc-investigate.c | 209 ++++ scd/scdaemon.c | 638 +++++++++++++ scd/scdaemon.h | 127 +++ sm/ChangeLog | 816 ++++++++++++++++ sm/Makefile.am | 55 ++ sm/call-agent.c | 713 ++++++++++++++ sm/call-dirmngr.c | 632 +++++++++++++ sm/certchain.c | 793 ++++++++++++++++ sm/certcheck.c | 300 ++++++ sm/certdump.c | 457 +++++++++ sm/certlist.c | 315 +++++++ sm/certreqgen.c | 699 ++++++++++++++ sm/decrypt.c | 506 ++++++++++ sm/delete.c | 165 ++++ sm/encrypt.c | 550 +++++++++++ sm/export.c | 249 +++++ sm/fingerprint.c | 271 ++++++ sm/gpgsm.c | 1458 ++++++++++++++++++++++++++++ sm/gpgsm.h | 274 ++++++ sm/import.c | 349 +++++++ sm/keydb.c | 1282 +++++++++++++++++++++++++ sm/keylist.c | 617 ++++++++++++ sm/server.c | 1070 +++++++++++++++++++++ sm/sign.c | 621 ++++++++++++ sm/verify.c | 550 +++++++++++ 76 files changed, 34899 insertions(+) create mode 100644 agent/ChangeLog create mode 100644 agent/Makefile.am create mode 100644 agent/agent.h create mode 100644 agent/call-scd.c create mode 100644 agent/command.c create mode 100644 agent/divert-scd.c create mode 100644 agent/findkey.c create mode 100644 agent/genkey.c create mode 100644 agent/gpg-agent.c create mode 100644 agent/learncard.c create mode 100644 agent/minip12.c create mode 100644 agent/minip12.h create mode 100644 agent/pkdecrypt.c create mode 100644 agent/pksign.c create mode 100644 agent/protect-tool.c create mode 100644 agent/protect.c create mode 100644 agent/simple-pwquery.c create mode 100644 common/ChangeLog create mode 100644 common/Makefile.am create mode 100644 common/README create mode 100644 common/errors.h create mode 100644 common/gettime.c create mode 100644 common/iobuf.c create mode 100644 common/iobuf.h create mode 100644 common/maperror.c create mode 100644 common/membuf.c create mode 100644 common/membuf.h create mode 100644 common/miscellaneous.c create mode 100644 common/simple-pwquery.c create mode 100644 common/simple-pwquery.h create mode 100644 common/ttyio.c create mode 100644 common/ttyio.h create mode 100644 common/util.h create mode 100644 common/yesno.c create mode 100644 kbx/Makefile.am create mode 100644 kbx/kbxutil.c create mode 100644 kbx/keybox-blob.c create mode 100644 scd/ChangeLog create mode 100644 scd/Makefile.am create mode 100644 scd/apdu.c create mode 100644 scd/apdu.h create mode 100644 scd/app-common.h create mode 100644 scd/app-openpgp.c create mode 100644 scd/app.c create mode 100644 scd/card-common.h create mode 100644 scd/card-p15.c create mode 100644 scd/card.c create mode 100644 scd/command.c create mode 100644 scd/iso7816.c create mode 100644 scd/iso7816.h create mode 100644 scd/sc-copykeys.c create mode 100644 scd/sc-investigate.c create mode 100644 scd/scdaemon.c create mode 100644 scd/scdaemon.h create mode 100644 sm/ChangeLog create mode 100644 sm/Makefile.am create mode 100644 sm/call-agent.c create mode 100644 sm/call-dirmngr.c create mode 100644 sm/certchain.c create mode 100644 sm/certcheck.c create mode 100644 sm/certdump.c create mode 100644 sm/certlist.c create mode 100644 sm/certreqgen.c create mode 100644 sm/decrypt.c create mode 100644 sm/delete.c create mode 100644 sm/encrypt.c create mode 100644 sm/export.c create mode 100644 sm/fingerprint.c create mode 100644 sm/gpgsm.c create mode 100644 sm/gpgsm.h create mode 100644 sm/import.c create mode 100644 sm/keydb.c create mode 100644 sm/keylist.c create mode 100644 sm/server.c create mode 100644 sm/sign.c create mode 100644 sm/verify.c (limited to 'agent/command.c') diff --git a/agent/ChangeLog b/agent/ChangeLog new file mode 100644 index 000000000..10f4d45fa --- /dev/null +++ b/agent/ChangeLog @@ -0,0 +1,565 @@ +2003-07-31 Werner Koch + + * Makefile.am (gpg_agent_LDADD): Added INTLLIBS. + (gpg_protect_tool_SOURCES): Added simple-pwquery.[ch] + +2003-07-27 Werner Koch + + Adjusted for gcry_mpi_print and gcry_mpi_scan API change. + +2003-07-15 Werner Koch + + * simple-pwquery.c, simple-pwquery.h: Moved to ../common. + * Makefile.am (gpg_protect_tool_LDADD): Add simple-pwquery.o. + Removed it from xx_SOURCES. + +2003-07-04 Werner Koch + + * gpg-agent.c (handle_connections): Kludge to allow use of Pth 1 + and 2. + +2003-06-30 Werner Koch + + * call-scd.c (learn_status_cb): Store the serialno in PARM. + +2003-06-26 Werner Koch + + * call-scd.c (agent_card_serialno): Don't do a RESET anymore. + +2003-06-25 Werner Koch + + * command.c (cmd_scd): New. + * call-scd.c (agent_card_scd): New. + * divert-scd.c (divert_generic_cmd): New + + * call-scd.c (agent_card_learn): New callback args SINFO. + (learn_status_cb): Pass all other status lines to the sinfo + callback. + * learncard.c (release_sinfo, sinfo_cb): New. + (agent_handle_learn): Pass the new cb to the learn function and + pass the collected information back to the client's assuan + connection. + + * gpg-agent.c (main): Moved pth_init before gcry_check_version. + +2003-06-24 Werner Koch + + * gpg-agent.c (handle_connections): Adjusted for Pth 2.0 + + Adjusted for changes in the libgcrypt API. Some more fixes for the + libgpg-error stuff. + +2003-06-04 Werner Koch + + Renamed error codes from INVALID to INV and removed _ERROR suffixes. + +2003-06-03 Werner Koch + + Changed all error codes in all files to the new libgpg-error scheme. + + * agent.h: Include gpg-error.h and errno.h + * Makefile.am: Link with libgpg-error + + * query.c: assuan.h is now a system header. + * genkey.c (agent_genkey): Fixed silly use of xmalloc by + xtrymalloc. + +2003-04-29 Werner Koch + + * command.c (register_commands): Adjusted for new Assuan semantics. + + * Makefile.am: Don't override LDFLAGS. + +2002-12-04 Werner Koch + + * gpg-agent.c: New variable config_filename. + (parse_rereadable_options): New. + (main): Use it here. Add setting of default values, set + config_filename. + (reread_configuration): Filled with actual code. + +2002-12-03 Werner Koch + + * protect-tool.c (read_key): Don't run make_canonical on a NULL + buffer. + + * command.c (parse_hexstring): New. + (cmd_sethash): Use it. + (parse_keygrip): New. + (cmd_havekey, cmd_sigkey): Use it. + (cmd_passwd): New. + * genkey.c (agent_protect_and_store): New. + (store_key): Add arg FORCE. + (agent_genkey): Pass false to this force of store_key. + +2002-11-13 Werner Koch + + * gpg-agent.c (main): Switch all messages to utf-8. + + * simple-pwquery.c (agent_send_all_options): Use $GPG_TTY and + stdin with ttyname. + + * cache.c (new_data): Uiih - /sizeof d/sizeof *d/. + +2002-11-10 Werner Koch + + * command.c (option_handler): Fix keep_tty check. + +2002-11-06 Werner Koch + + * gpg-agent.c (main): Make sure we have a default ttyname. + * command.c (option_handler): Check opt.keep_tty here + * query.c (start_pinentry): but not anymore here. + +2002-11-05 Werner Koch + + * agent.h (opt,server_control_s): Move display and lc_ variables + to the control struct so that they are per connection. + * gpg-agent.c (agent_init_default_ctrl): New. + (main): Assign those command line options to new default_* variables. + Reset DISPLAY in server mode so that tehre is no implicit default. + * command.c (start_command_handler): Initialize and deinitialize + the control values. + (option_handler): Work on the ctrl values and not on the opt. + * query.c (start_pinentry): New argument CTRL to set the display + connection specific. Changed all callers to pass this value. + (agent_askpin,agent_get_passphrase,agent_get_confirmation): Add + CTRL arg and pass it ot start_pinentry. + * command.c (cmd_get_passphrase): Pass CTRL argument. + * trustlist.c (agent_marktrusted): Add CTRL argument + * command.c (cmd_marktrusted): Pass CTRL argument + * divert-scd.c (ask_for_card): Add CTRL arg. + (divert_pksign,divert_pkdecrypt): Ditto. Changed caller. + (getpin_cb): Use OPAQUE to pass the CTRL variable. Changed both + users. + * findkey.c (unprotect): Add CTRL arg. + (agent_key_from_file): Ditto. + + * query.c (unlock_pinentry): Disconnect the pinentry so that we + start a new one for each request. This is required to support + clients with different environments (e.g. X magic cookies). + +2002-09-05 Neal H. Walfield + + * gpg-agent.c (main) [USE_GNU_PTH]: No need to call + assuan_set_io_func as assuan is smart. + +2002-09-25 Werner Koch + + * gpg-agent.c (handle_signal): Flush cache on SIGHUP. + * cache.c (agent_flush_cache): New. + + * gpg-agent.c, agent.h: Add --keep-display and --keep-tty. + * query.c (start_pinentry): Implement them. The option passing + needs more thoughts. + +2002-09-09 Werner Koch + + * gpg-agent.c (create_private_keys_directory) + (create_directories): New. + (main): Try to create a home directory. + +2002-09-04 Neal H. Walfield + + * gpg-agent.c (main): Use sigaction, not signal. + +2002-09-03 Neal H. Walfield + + * findkey.c: Include . + (agent_write_private_key): Prefer POSIX compatibity, open and + fdopen, over the simplicity of GNU extensions, fopen(file, "x"). + +2002-08-22 Werner Koch + + * query.c (agent_askpin): Provide the default desc text depending + on the pininfo. Do the basic PIN verification only when + min_digits is set. + +2002-08-21 Werner Koch + + * query.c (agent_askpin): Hack to show the right default prompt. + (agent_get_passphrase): Ditto. + + * trans.c: Removed and replaced all usages with standard _() + + * divert-scd.c (getpin_cb): Pass a more descritive text to the + pinentry. + + * Makefile.am: Renamed the binary protect-tool to gpg-protect-tool. + * protect-tool.c: Removed the note about internal use only. + + * gpg-agent.c (main): New option --daemon so that the program is + not accidently started in the background. + +2002-08-16 Werner Koch + + * call-scd.c (learn_status_cb): Handle CERTINFO status. + (agent_card_learn): Add args for certinfo cb. + * learncard.c (release_certinfo,certinfo_cb): New. + (send_cert_back): New. With factored out code from .. + (agent_handle_learn): here. Return certinfo stuff. + +2002-07-26 Werner Koch + + * gpg-agent.c (main): New option --ignore-cache-for-signing. + * command.c (option_handler): New server option + use-cache-for-signing defaulting to true. + (cmd_pksign): handle global and per session option. + * findkey.c (agent_key_from_file, unprotect): New arg + ignore_cache. Changed all callers. + * pksign.c (agent_pksign): Likewise. + +2002-06-29 Werner Koch + + * query.c (start_pinentry): Use GNUPG_DERAULT_PINENTRY. + * call-scd.c (start_scd): Use GNUPG_DEFAULT_SCDAEMON. + +2002-06-28 Werner Koch + + * protect-tool.c (export_p12_file): New. + (main): New command --p12-export. + * minip12.c (create_final,p12_build,compute_tag_length): New. + (store_tag_length): New. + +2002-06-27 Werner Koch + + * minip12.c (crypt_block): Renamed from decrypt_block, add arg to + allow encryption. + + * Makefile.am (pkglib_PROGRAMS): Put protect-tool there. + + * findkey.c (agent_write_private_key,agent_key_from_file) + (agent_key_available): Use GNUPG_PRIVATE_KEYS_DIR constant. + * gpg-agent.c (main): Use GNUPG_DEFAULT_HOMEDIR constant. + + * protect-tool.c (store_private_key): New. + (import_p12_file): Store the new file if requested. + (main): New options --force and --store. + + * gpg-agent.c (main): Set a global flag when running detached. + * query.c (start_pinentry): Pass the list of FD to keep in the + child when not running detached. + * call-scd.c (start_scd): Ditto. + +2002-06-26 Werner Koch + + * command.c (cmd_istrusted, cmd_listtrusted, cmd_marktrusted) + (cmd_pksign, cmd_pkdecrypt, cmd_genkey, cmd_get_passphrase) + (cmd_learn): Print an error message for a failed operation. + + * simple-pwquery.c, simple-pwquery.h: New. + * protect-tool. (get_passphrase): New, used to get a passphrase + from the agent if none was given on the command line. + +2002-06-25 Werner Koch + + * protect-tool.c (rsa_key_check): New. + (import_p12_file): New. + (main): New command --p12-import. + * minip12.c, minip12.h: New. + +2002-06-24 Werner Koch + + * protect-tool.c (read_file): New. + (read_key): Factored most code out to read_file. + +2002-06-17 Werner Koch + + * agent.h: Add a callback function to the pin_entry_info structure. + * query.c (agent_askpin): Use the callback to check for a correct + PIN. Removed the start_err_text argument because it is not + anymore needed; changed callers. + * findkey.c (unprotect): Replace our own check loop by a callback. + (try_unprotect_cb): New. + * genkey.c (reenter_compare_cb): New. + (agent_genkey): Use this callback here. Fixed setting of the pi2 + variable and a segv in case of an empty PIN. + + * divert-scd.c (getpin_cb): Removed some unused stuff and + explained what we still have to change. + +2002-06-12 Werner Koch + + * gpg-agent.c (main): New option --disable-pth. + +2002-06-11 Werner Koch + + * protect-tool.c: Add command --show-keygrip + (show_keygrip): New. + +2002-05-23 Werner Koch + + * call-scd.c: Seirialized all scdaeom access when using Pth. + + * cache.c: Made the cache Pth-thread-safe. + (agent_unlock_cache_entry): New. + * findkey.c (unprotect): Unlock the returned cache value. + * command.c (cmd_get_passphrase): Ditto. + + * gpg-agent.c (main): Register pth_read/write with Assuan. + +2002-05-22 Werner Koch + + * query.c: Serialized all pinentry access when using Pth. + + * gpg-agent.c (handle_signal,start_connection_thread) + (handle_connections): New + (main): Use the new Pth stuff to allow concurrent connections. + * command.c (start_command_handler): Add new arg FD so that the + fucntion can also be used for an already connected socket. + * Makefile.am: Link with Pth. + +2002-05-14 Werner Koch + + * cache.c (housekeeping, agent_put_cache): Use our time() wrapper. + +2002-04-26 Werner Koch + + * cache.c (agent_put_cache): Reinitialize the creation time and + the ttl when reusing a slot. + + * call-scd.c (start_scd): Print debug messages only with debug + flags set. + * query.c (start_pinentry): Ditto. + +2002-04-25 Marcus Brinkmann + + * agent.h (agent_get_confirmation): Replace paramter prompt with + two parameters ok and cancel. + * query.c (agent_get_confirmation): Likewise. Implement this. + * trustlist.c (agent_marktrusted): Fix invocation of + agent_get_confirmation. + * divert-scd.c (ask_for_card): Likewise. + +2002-04-24 Marcus Brinkmann + + * agent.h (struct opt): Add members display, ttyname, ttytype, + lc_ctype, and lc_messages. + * gpg-agent.c (enum cmd_and_opt_values): Add oDisplay, oTTYname, + oTTYtype, oLCctype, and LCmessages. + (main): Handle these options. + * command.c (option_handler): New function. + (register_commands): Register option handler. + * query.c (start_pinentry): Pass the various display and tty + options to the pinentry. + +2002-04-05 Werner Koch + + * protect-tool.c (show_file): New. Used as default action. + +2002-03-28 Werner Koch + + * divert-scd.c (encode_md_for_card): Don't do the pkcs-1 padding, + the scdaemon should take care of it. + (ask_for_card): Hack to not display the trailing zero. + +2002-03-11 Werner Koch + + * learncard.c (kpinfo_cb): Remove the content restrictions from + the keyID. + +2002-03-06 Werner Koch + + * learncard.c: New. + * divert-scd.c (ask_for_card): The serial number is binary so + convert it to hex here. + * findkey.c (agent_write_private_key): New. + * genkey.c (store_key): And use it here. + + * pkdecrypt.c (agent_pkdecrypt): Changed the way the diversion is done. + * divert-scd.c (divert_pkdecrypt): Changed interface and + implemented it. + +2002-03-05 Werner Koch + + * call-scd.c (inq_needpin): New. + (agent_card_pksign): Add getpin_cb args. + (agent_card_pkdecrypt): New. + +2002-03-04 Werner Koch + + * pksign.c (agent_pksign): Changed how the diversion is done. + * divert-scd.c (divert_pksign): Changed interface and implemented it. + (encode_md_for_card): New. + * call-scd.c (agent_card_pksign): New. + +2002-02-28 Werner Koch + + * pksign.c (agent_pksign): Detect whether a Smartcard is to be + used and divert the operation in this case. + * pkdecrypt.c (agent_pkdecrypt): Likewise + * findkey.c (agent_key_from_file): Add optional arg shadow_info + and have it return information about a shadowed key. + * protect.c (agent_get_shadow_info): New. + + * protect.c (snext,sskip,smatch): Moved to + * sexp-parse.h: new file. + * divert-scd.c: New. + +2002-02-27 Werner Koch + + * protect.c (agent_shadow_key): New. + + * command.c (cmd_learn): New command LEARN. + * gpg-agent.c: New option --scdaemon-program. + * call-scd.c (start_scd): New. Based on query.c + * query.c: Add 2 more arguments to all uses of assuan_transact. + +2002-02-18 Werner Koch + + * findkey.c (unprotect): Show an error message for a bad passphrase. + + * command.c (cmd_marktrusted): Implemented. + * trustlist.c (agent_marktrusted): New. + (open_list): Add APPEND arg. + + * query.c (agent_get_confirmation): New. + +2002-02-06 Werner Koch + + * cache.c (housekeeping): Fixed linking in the remove case. + +2002-02-01 Werner Koch + + * gpg-agent.c: New option --default-cache-ttl. + * cache.c (agent_put_cache): Use it. + + * cache.c: Add a few debug outputs. + + * protect.c (agent_private_key_type): New. + * agent.h: Add PRIVATE_KEY_ enums. + * findkey.c (agent_key_from_file): Use it to decide whether we + have to unprotect a key. + (unprotect): Cache the passphrase. + + * findkey.c (agent_key_from_file,agent_key_available): The key + files do now require a ".key" suffix to make a script's life + easier. + * genkey.c (store_key): Ditto. + +2002-01-31 Werner Koch + + * genkey.c (store_key): Protect the key. + (agent_genkey): Ask for the passphrase. + * findkey.c (unprotect): Actually unprotect the key. + * query.c (agent_askpin): Add an optional start_err_text. + +2002-01-30 Werner Koch + + * protect.c: New. + (hash_passphrase): Based on the GnuPG 1.0.6 version. + * protect-tool.c: New + +2002-01-29 Werner Koch + + * findkey.c (agent_key_available): New. + * command.c (cmd_havekey): New. + (register_commands): And register new command. + +2002-01-20 Werner Koch + + * command.c (cmd_get_passphrase): Remove the plus signs. + + * query.c (start_pinentry): Send no-grab option to pinentry + * gpg-agent.c (main): Move variable grab as no_grab to agent.h. + +2002-01-19 Werner Koch + + * gpg-agent.c (main): Disable core dumps. + + * cache.c: New. + * command.c (cmd_get_passphrase): Use the cache. + (cmd_clear_passphrase): Ditto. + + * gpg-agent.c: Removed unused cruft and implement the socket + based server. + (my_strusage): Take bug report address from configure.ac. + * command.c (start_command_handler): Add an argument to start as + regular server. + (start_command_handler): Enable Assuan logging. + +2002-01-15 Werner Koch + + * trustlist.c: New. + * command.c (cmd_istrusted, cmd_listtrusted, cmd_marktrusted): New. + +2002-01-07 Werner Koch + + * genkey.c: Store the secret part and return the public part. + +2002-01-03 Werner Koch + + * command.c (cmd_get_passphrase): New. + (cmd_clear_passphrase): New. + * query.c (agent_get_passphrase): New. + +2002-01-02 Werner Koch + + * genkey.c: New. + * command.c (cmd_genkey): New. + + * command.c (rc_to_assuan_status): Removed and changed all callers + to use map_to_assuan_status. + +2001-12-19 Werner Koch + + * keyformat.txt: New. + +2001-12-19 Marcus Brinkmann + + * query.c (start_pinentry): Add new argument to assuan_pipe_connect. + +2001-12-18 Werner Koch + + * Makefile.am: Use LIBGCRYPT macros + +2001-12-14 Werner Koch + + * gpg-agent.c (main): New option --batch. New option --debug-wait + n, so that it is possible to attach gdb when used in server mode. + * query.c (agent_askpin): Don't ask in batch mode. + + * command.c: Removed the conversion macros as they are now in + ../common/util.h. + +2001-12-14 Marcus Brinkmann + + * query.c (LINELENGTH): Removed. + (agent_askpin): Use ASSUAN_LINELENGTH, not LINELENGTH. + +2001-11-19 Werner Koch + + * gpg-agent.c: Removed all GUI code, removed code for old + protocol. New code to use the Assuan protocol as a server and + also to communicate with a new ask-passphrase utility. + +2000-11-22 Werner Koch + + * gpg-agent.c (main): csh support by Dan Winship, new options --sh + and --csh and set default by consulting $SHELL. + +Mon Aug 21 17:59:17 CEST 2000 Werner Koch + + * gpg-agent.c (passphrase_dialog): Cleanup the window and added the + user supplied text to the window. + (main): Fixed segv in gtk_init when used without a command to start. + + * gpg-agent.c: --flush option. + (req_flush): New. + (req_clear_passphrase): Implemented. + +Fri Aug 18 14:27:14 CEST 2000 Werner Koch + + * gpg-agent.c: New. + * Makefile.am: New. + + + Copyright 2001, 2002 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. diff --git a/agent/Makefile.am b/agent/Makefile.am new file mode 100644 index 000000000..400aa2fd2 --- /dev/null +++ b/agent/Makefile.am @@ -0,0 +1,62 @@ +# Copyright (C) 2001, 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 2 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +## Process this file with automake to produce Makefile.in + +localedir = $(datadir)/locale +INCLUDES = -I../intl -DLOCALEDIR=\"$(localedir)\" + +bin_PROGRAMS = gpg-agent +pkglib_PROGRAMS = gpg-protect-tool + +AM_CPPFLAGS = -I$(top_srcdir)/common $(LIBGCRYPT_CFLAGS) \ + $(LIBASSUAN_CFLAGS) $(PTH_CFLAGS) + +gpg_agent_SOURCES = \ + gpg-agent.c agent.h \ + command.c \ + query.c \ + cache.c \ + trans.c \ + findkey.c \ + pksign.c \ + pkdecrypt.c \ + genkey.c \ + protect.c \ + trustlist.c \ + divert-scd.c \ + call-scd.c \ + learncard.c \ + sexp-parse.h + + +gpg_agent_LDADD = ../jnlib/libjnlib.a ../common/libcommon.a \ + $(LIBGCRYPT_LIBS) $(PTH_LIBS) $(LIBASSUAN_LIBS) \ + -lgpg-error @INTLLIBS@ + +gpg_protect_tool_SOURCES = \ + protect-tool.c \ + protect.c \ + simple-pwquery.c simple-pwquery.h \ + minip12.c minip12.h + +gpg_protect_tool_LDADD = ../jnlib/libjnlib.a \ + ../common/libcommon.a ../common/libsimple-pwquery.a \ + $(LIBGCRYPT_LIBS) -lgpg-error @INTLLIBS@ + + diff --git a/agent/agent.h b/agent/agent.h new file mode 100644 index 000000000..eb4f4e32d --- /dev/null +++ b/agent/agent.h @@ -0,0 +1,226 @@ +/* agent.h - Global definitions for the agent + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef AGENT_H +#define AGENT_H + +#ifdef GPG_ERR_SOURCE_DEFAULT +#error GPG_ERR_SOURCE_DEFAULT already defined +#endif +#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_GPGAGENT +#include +#include + +#include +#include "../common/util.h" +#include "../common/errors.h" + +/* Convenience function to be used instead of returning the old + GNUPG_Out_Of_Core. */ +static __inline__ gpg_error_t +out_of_core (void) +{ + return gpg_error (gpg_err_code_from_errno (errno)); +} + +#define MAX_DIGEST_LEN 24 + +/* A large struct name "opt" to keep global flags */ +struct { + unsigned int debug; /* debug flags (DBG_foo_VALUE) */ + int verbose; /* verbosity level */ + int quiet; /* be as quiet as possible */ + int dry_run; /* don't change any persistent data */ + int batch; /* batch mode */ + const char *homedir; /* configuration directory name */ + const char *pinentry_program; + const char *scdaemon_program; + int no_grab; /* don't let the pinentry grab the keyboard */ + unsigned long def_cache_ttl; + + int running_detached; /* we are running detached from the tty. */ + + int ignore_cache_for_signing; + int keep_tty; /* don't switch the TTY (for pinentry) on request */ + int keep_display; /* don't switch the DISPLAY (for pinentry) on request */ +} opt; + + +#define DBG_COMMAND_VALUE 1 /* debug commands i/o */ +#define DBG_MPI_VALUE 2 /* debug mpi details */ +#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ +#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */ +#define DBG_CACHE_VALUE 64 /* debug the caching */ +#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */ +#define DBG_HASHING_VALUE 512 /* debug hashing operations */ +#define DBG_ASSUAN_VALUE 1024 + +#define DBG_COMMAND (opt.debug & DBG_COMMAND_VALUE) +#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) +#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE) +#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE) +#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE) +#define DBG_ASSUAN (opt.debug & DBG_ASSUAN_VALUE) + +struct server_local_s; + +struct server_control_s { + struct server_local_s *server_local; + char *display; + char *ttyname; + char *ttytype; + char *lc_ctype; + char *lc_messages; + struct { + int algo; + unsigned char value[MAX_DIGEST_LEN]; + int valuelen; + } digest; + char keygrip[20]; + int have_keygrip; + +}; +typedef struct server_control_s *CTRL; + + +struct pin_entry_info_s { + int min_digits; /* min. number of digits required or 0 for freeform entry */ + int max_digits; /* max. number of allowed digits allowed*/ + int max_tries; + int failed_tries; + int (*check_cb)(struct pin_entry_info_s *); /* CB used to check the PIN */ + void *check_cb_arg; /* optional argument which might be of use in the CB */ + const char *cb_errtext; /* used by the cb to displaye a specific error */ + size_t max_length; /* allocated length of the buffer */ + char pin[1]; +}; + + +enum { + PRIVATE_KEY_UNKNOWN = 0, + PRIVATE_KEY_CLEAR = 1, + PRIVATE_KEY_PROTECTED = 2, + PRIVATE_KEY_SHADOWED = 3 +}; + +/*-- gpg-agent.c --*/ +void agent_exit (int rc); /* also implemented in other tools */ +void agent_init_default_ctrl (struct server_control_s *ctrl); + +/*-- command.c --*/ +void start_command_handler (int, int); + +/*-- findkey.c --*/ +int agent_write_private_key (const unsigned char *grip, + const void *buffer, size_t length, int force); +gcry_sexp_t agent_key_from_file (CTRL ctrl, const unsigned char *grip, + unsigned char **shadow_info, + int ignore_cache); +int agent_key_available (const unsigned char *grip); + +/*-- query.c --*/ +int agent_askpin (CTRL ctrl, + const char *desc_text, struct pin_entry_info_s *pininfo); +int agent_get_passphrase (CTRL ctrl, char **retpass, + const char *desc, const char *prompt, + const char *errtext); +int agent_get_confirmation (CTRL ctrl, const char *desc, const char *ok, + const char *cancel); + +/*-- cache.c --*/ +void agent_flush_cache (void); +int agent_put_cache (const char *key, const char *data, int ttl); +const char *agent_get_cache (const char *key, void **cache_id); +void agent_unlock_cache_entry (void **cache_id); + + +/*-- pksign.c --*/ +int agent_pksign (CTRL ctrl, FILE *outfp, int ignore_cache); + +/*-- pkdecrypt.c --*/ +int agent_pkdecrypt (CTRL ctrl, const char *ciphertext, size_t ciphertextlen, + FILE *outfp); + +/*-- genkey.c --*/ +int agent_genkey (CTRL ctrl, + const char *keyparam, size_t keyparmlen, FILE *outfp); +int agent_protect_and_store (CTRL ctrl, gcry_sexp_t s_skey); + +/*-- protect.c --*/ +int agent_protect (const unsigned char *plainkey, const char *passphrase, + unsigned char **result, size_t *resultlen); +int agent_unprotect (const unsigned char *protectedkey, const char *passphrase, + unsigned char **result, size_t *resultlen); +int agent_private_key_type (const unsigned char *privatekey); +int agent_shadow_key (const unsigned char *pubkey, + const unsigned char *shadow_info, + unsigned char **result); +int agent_get_shadow_info (const unsigned char *shadowkey, + unsigned char const **shadow_info); + + +/*-- trustlist.c --*/ +int agent_istrusted (const char *fpr); +int agent_listtrusted (void *assuan_context); +int agent_marktrusted (CTRL ctrl, const char *name, const char *fpr, int flag); + + +/*-- divert-scd.c --*/ +int divert_pksign (CTRL ctrl, + const unsigned char *digest, size_t digestlen, int algo, + const unsigned char *shadow_info, unsigned char **r_sig); +int divert_pkdecrypt (CTRL ctrl, + const unsigned char *cipher, + const unsigned char *shadow_info, + char **r_buf, size_t *r_len); +int divert_generic_cmd (CTRL ctrl, const char *cmdline, void *assuan_context); + + +/*-- call-scd.c --*/ +int agent_card_learn (void (*kpinfo_cb)(void*, const char *), + void *kpinfo_cb_arg, + void (*certinfo_cb)(void*, const char *), + void *certinfo_cb_arg, + void (*sinfo_cb)(void*, const char *, + size_t, const char *), + void *sinfo_cb_arg); +int agent_card_serialno (char **r_serialno); +int agent_card_pksign (const char *keyid, + int (*getpin_cb)(void *, const char *, char*, size_t), + void *getpin_cb_arg, + const unsigned char *indata, size_t indatalen, + char **r_buf, size_t *r_buflen); +int agent_card_pkdecrypt (const char *keyid, + int (*getpin_cb)(void *, const char *, char*,size_t), + void *getpin_cb_arg, + const unsigned char *indata, size_t indatalen, + char **r_buf, size_t *r_buflen); +int agent_card_readcert (const char *id, char **r_buf, size_t *r_buflen); +int agent_card_readkey (const char *id, unsigned char **r_buf); +int agent_card_scd (const char *cmdline, + int (*getpin_cb)(void *, const char *, char*, size_t), + void *getpin_cb_arg, void *assuan_context); + + +/*-- learncard.c --*/ +int agent_handle_learn (void *assuan_context); + + +#endif /*AGENT_H*/ diff --git a/agent/call-scd.c b/agent/call-scd.c new file mode 100644 index 000000000..14487f1e3 --- /dev/null +++ b/agent/call-scd.c @@ -0,0 +1,661 @@ +/* call-scd.c - fork of the scdaemon to do SC operations + * Copyright (C) 2001, 2002 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* Fixme: For now we have serialized all access to the scdaemon which + make sense becuase the scdaemon can't handle concurrent connections + right now. We should however keep a list of connections and lock + just that connection - it migth make sense to implemtn parts of + this in Assuan.*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef USE_GNU_PTH +# include +#endif + +#include "agent.h" +#include + +#ifdef _POSIX_OPEN_MAX +#define MAX_OPEN_FDS _POSIX_OPEN_MAX +#else +#define MAX_OPEN_FDS 20 +#endif + +static ASSUAN_CONTEXT scd_ctx = NULL; +#ifdef USE_GNU_PTH +static pth_mutex_t scd_lock = PTH_MUTEX_INIT; +#endif + +/* callback parameter for learn card */ +struct learn_parm_s { + void (*kpinfo_cb)(void*, const char *); + void *kpinfo_cb_arg; + void (*certinfo_cb)(void*, const char *); + void *certinfo_cb_arg; + void (*sinfo_cb)(void*, const char *, size_t, const char *); + void *sinfo_cb_arg; +}; + +struct inq_needpin_s { + ASSUAN_CONTEXT ctx; + int (*getpin_cb)(void *, const char *, char*, size_t); + void *getpin_cb_arg; +}; + +struct membuf { + size_t len; + size_t size; + char *buf; + int out_of_core; +}; + + + +/* A simple implementation of a dynamic buffer. Use init_membuf() to + create a buffer, put_membuf to append bytes and get_membuf to + release and return the buffer. Allocation errors are detected but + only returned at the final get_membuf(), this helps not to clutter + the code with out of core checks. */ + +static void +init_membuf (struct membuf *mb, int initiallen) +{ + mb->len = 0; + mb->size = initiallen; + mb->out_of_core = 0; + mb->buf = xtrymalloc (initiallen); + if (!mb->buf) + mb->out_of_core = 1; +} + +static void +put_membuf (struct membuf *mb, const void *buf, size_t len) +{ + if (mb->out_of_core) + return; + + if (mb->len + len >= mb->size) + { + char *p; + + mb->size += len + 1024; + p = xtryrealloc (mb->buf, mb->size); + if (!p) + { + mb->out_of_core = 1; + return; + } + mb->buf = p; + } + memcpy (mb->buf + mb->len, buf, len); + mb->len += len; +} + +static void * +get_membuf (struct membuf *mb, size_t *len) +{ + char *p; + + if (mb->out_of_core) + { + xfree (mb->buf); + mb->buf = NULL; + return NULL; + } + + p = mb->buf; + *len = mb->len; + mb->buf = NULL; + mb->out_of_core = 1; /* don't allow a reuse */ + return p; +} + + + + +static int +unlock_scd (int rc) +{ +#ifdef USE_GNU_PTH + if (!pth_mutex_release (&scd_lock)) + { + log_error ("failed to release the SCD lock\n"); + if (!rc) + rc = gpg_error (GPG_ERR_INTERNAL); + } +#endif + return rc; +} + +/* Fork off the SCdaemon if this has not already been done */ +static int +start_scd (void) +{ + int rc; + const char *pgmname; + ASSUAN_CONTEXT ctx; + const char *argv[3]; + int no_close_list[3]; + int i; + +#ifdef USE_GNU_PTH + if (!pth_mutex_acquire (&scd_lock, 0, NULL)) + { + log_error ("failed to acquire the SCD lock\n"); + return gpg_error (GPG_ERR_INTERNAL); + } +#endif + + if (scd_ctx) + return 0; /* No need to serialize things because the agent is + expected to tun as a single-thread (or may be in + future using libpth) */ + + if (opt.verbose) + log_info ("no running SCdaemon - starting it\n"); + + if (fflush (NULL)) + { + gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("error flushing pending output: %s\n", strerror (errno)); + return unlock_scd (tmperr); + } + + if (!opt.scdaemon_program || !*opt.scdaemon_program) + opt.scdaemon_program = GNUPG_DEFAULT_SCDAEMON; + if ( !(pgmname = strrchr (opt.scdaemon_program, '/'))) + pgmname = opt.scdaemon_program; + else + pgmname++; + + argv[0] = pgmname; + argv[1] = "--server"; + argv[2] = NULL; + + i=0; + if (!opt.running_detached) + { + if (log_get_fd () != -1) + no_close_list[i++] = log_get_fd (); + no_close_list[i++] = fileno (stderr); + } + no_close_list[i] = -1; + + /* connect to the pinentry and perform initial handshaking */ + rc = assuan_pipe_connect (&ctx, opt.scdaemon_program, (char**)argv, + no_close_list); + if (rc) + { + log_error ("can't connect to the SCdaemon: %s\n", + assuan_strerror (rc)); + return unlock_scd (gpg_error (GPG_ERR_NO_SCDAEMON)); + } + scd_ctx = ctx; + + if (DBG_ASSUAN) + log_debug ("connection to SCdaemon established\n"); + return 0; +} + + + +static AssuanError +learn_status_cb (void *opaque, const char *line) +{ + struct learn_parm_s *parm = opaque; + const char *keyword = line; + int keywordlen; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + if (keywordlen == 8 && !memcmp (keyword, "CERTINFO", keywordlen)) + { + parm->certinfo_cb (parm->certinfo_cb_arg, line); + } + else if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen)) + { + parm->kpinfo_cb (parm->kpinfo_cb_arg, line); + } + else if (keywordlen && *line) + { + parm->sinfo_cb (parm->sinfo_cb_arg, keyword, keywordlen, line); + } + + return 0; +} + +/* Perform the learn command and return a list of all private keys + stored on the card. */ +int +agent_card_learn (void (*kpinfo_cb)(void*, const char *), + void *kpinfo_cb_arg, + void (*certinfo_cb)(void*, const char *), + void *certinfo_cb_arg, + void (*sinfo_cb)(void*, const char *, size_t, const char *), + void *sinfo_cb_arg) +{ + int rc; + struct learn_parm_s parm; + + rc = start_scd (); + if (rc) + return rc; + + memset (&parm, 0, sizeof parm); + parm.kpinfo_cb = kpinfo_cb; + parm.kpinfo_cb_arg = kpinfo_cb_arg; + parm.certinfo_cb = certinfo_cb; + parm.certinfo_cb_arg = certinfo_cb_arg; + parm.sinfo_cb = sinfo_cb; + parm.sinfo_cb_arg = sinfo_cb_arg; + rc = assuan_transact (scd_ctx, "LEARN --force", + NULL, NULL, NULL, NULL, + learn_status_cb, &parm); + if (rc) + return unlock_scd (map_assuan_err (rc)); + + return unlock_scd (0); +} + + + +static AssuanError +get_serialno_cb (void *opaque, const char *line) +{ + char **serialno = opaque; + const char *keyword = line; + const char *s; + int keywordlen, n; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) + { + if (*serialno) + return ASSUAN_Unexpected_Status; + for (n=0,s=line; hexdigitp (s); s++, n++) + ; + if (!n || (n&1)|| !(spacep (s) || !*s) ) + return ASSUAN_Invalid_Status; + *serialno = xtrymalloc (n+1); + if (!*serialno) + return ASSUAN_Out_Of_Core; + memcpy (*serialno, line, n); + (*serialno)[n] = 0; + } + + return 0; +} + +/* Return the serial number of the card or an appropriate error. The + serial number is returned as a hexstring. */ +int +agent_card_serialno (char **r_serialno) +{ + int rc; + char *serialno = NULL; + + rc = start_scd (); + if (rc) + return rc; + + /* Hmm, do we really need this reset - scddaemon should do this or + we can do this if we for some reason figure out that the + operation might have failed due to a missing RESET. Hmmm, I feel + this is really SCdaemon's duty */ +/* rc = assuan_transact (scd_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); */ +/* if (rc) */ +/* return unlock_scd (map_assuan_err (rc)); */ + + rc = assuan_transact (scd_ctx, "SERIALNO", + NULL, NULL, NULL, NULL, + get_serialno_cb, &serialno); + if (rc) + { + xfree (serialno); + return unlock_scd (map_assuan_err (rc)); + } + *r_serialno = serialno; + return unlock_scd (0); +} + + +static AssuanError +membuf_data_cb (void *opaque, const void *buffer, size_t length) +{ + struct membuf *data = opaque; + + if (buffer) + put_membuf (data, buffer, length); + return 0; +} + +/* Handle the NEEDPIN inquiry. */ +static AssuanError +inq_needpin (void *opaque, const char *line) +{ + struct inq_needpin_s *parm = opaque; + char *pin; + size_t pinlen; + int rc; + + if (!(!strncmp (line, "NEEDPIN", 7) && (line[7] == ' ' || !line[7]))) + { + log_error ("unsupported inquiry `%s'\n", line); + return ASSUAN_Inquire_Unknown; + } + line += 7; + + pinlen = 90; + pin = gcry_malloc_secure (pinlen); + if (!pin) + return ASSUAN_Out_Of_Core; + + rc = parm->getpin_cb (parm->getpin_cb_arg, line, pin, pinlen); + if (rc) + rc = ASSUAN_Canceled; + if (!rc) + rc = assuan_send_data (parm->ctx, pin, pinlen); + xfree (pin); + + return rc; +} + + + +/* Create a signature using the current card */ +int +agent_card_pksign (const char *keyid, + int (*getpin_cb)(void *, const char *, char*, size_t), + void *getpin_cb_arg, + const unsigned char *indata, size_t indatalen, + char **r_buf, size_t *r_buflen) +{ + int rc, i; + char *p, line[ASSUAN_LINELENGTH]; + struct membuf data; + struct inq_needpin_s inqparm; + size_t len; + unsigned char *sigbuf; + size_t sigbuflen; + + *r_buf = NULL; + rc = start_scd (); + if (rc) + return rc; + + if (indatalen*2 + 50 > DIM(line)) + return unlock_scd (gpg_error (GPG_ERR_GENERAL)); + + sprintf (line, "SETDATA "); + p = line + strlen (line); + for (i=0; i < indatalen ; i++, p += 2 ) + sprintf (p, "%02X", indata[i]); + rc = assuan_transact (scd_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_scd (map_assuan_err (rc)); + + init_membuf (&data, 1024); + inqparm.ctx = scd_ctx; + inqparm.getpin_cb = getpin_cb; + inqparm.getpin_cb_arg = getpin_cb_arg; + snprintf (line, DIM(line)-1, "PKSIGN %s", keyid); + line[DIM(line)-1] = 0; + rc = assuan_transact (scd_ctx, line, + membuf_data_cb, &data, + inq_needpin, &inqparm, + NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return unlock_scd (map_assuan_err (rc)); + } + sigbuf = get_membuf (&data, &sigbuflen); + + /* create an S-expression from it which is formatted like this: + "(7:sig-val(3:rsa(1:sSIGBUFLEN:SIGBUF)))" */ + *r_buflen = 21 + 11 + sigbuflen + 4; + *r_buf = xtrymalloc (*r_buflen); + if (!*r_buf) + { + gpg_error_t tmperr = out_of_core (); + xfree (*r_buf); + return unlock_scd (tmperr); + } + p = stpcpy (*r_buf, "(7:sig-val(3:rsa(1:s" ); + sprintf (p, "%u:", (unsigned int)sigbuflen); + p += strlen (p); + memcpy (p, sigbuf, sigbuflen); + p += sigbuflen; + strcpy (p, ")))"); + xfree (sigbuf); + + assert (gcry_sexp_canon_len (*r_buf, *r_buflen, NULL, NULL)); + return unlock_scd (0); +} + +/* Decipher INDATA using the current card. Note that the returned value is */ +int +agent_card_pkdecrypt (const char *keyid, + int (*getpin_cb)(void *, const char *, char*, size_t), + void *getpin_cb_arg, + const unsigned char *indata, size_t indatalen, + char **r_buf, size_t *r_buflen) +{ + int rc, i; + char *p, line[ASSUAN_LINELENGTH]; + struct membuf data; + struct inq_needpin_s inqparm; + size_t len; + + *r_buf = NULL; + rc = start_scd (); + if (rc) + return rc; + + /* FIXME: use secure memory where appropriate */ + if (indatalen*2 + 50 > DIM(line)) + return unlock_scd (gpg_error (GPG_ERR_GENERAL)); + + sprintf (line, "SETDATA "); + p = line + strlen (line); + for (i=0; i < indatalen ; i++, p += 2 ) + sprintf (p, "%02X", indata[i]); + rc = assuan_transact (scd_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_scd (map_assuan_err (rc)); + + init_membuf (&data, 1024); + inqparm.ctx = scd_ctx; + inqparm.getpin_cb = getpin_cb; + inqparm.getpin_cb_arg = getpin_cb_arg; + snprintf (line, DIM(line)-1, "PKDECRYPT %s", keyid); + line[DIM(line)-1] = 0; + rc = assuan_transact (scd_ctx, line, + membuf_data_cb, &data, + inq_needpin, &inqparm, + NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return unlock_scd (map_assuan_err (rc)); + } + *r_buf = get_membuf (&data, r_buflen); + if (!*r_buf) + return unlock_scd (gpg_error (GPG_ERR_ENOMEM)); + + return unlock_scd (0); +} + + + +/* Read a certificate with ID into R_BUF and R_BUFLEN. */ +int +agent_card_readcert (const char *id, char **r_buf, size_t *r_buflen) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + struct membuf data; + size_t len; + + *r_buf = NULL; + rc = start_scd (); + if (rc) + return rc; + + init_membuf (&data, 1024); + snprintf (line, DIM(line)-1, "READCERT %s", id); + line[DIM(line)-1] = 0; + rc = assuan_transact (scd_ctx, line, + membuf_data_cb, &data, + NULL, NULL, + NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return unlock_scd (map_assuan_err (rc)); + } + *r_buf = get_membuf (&data, r_buflen); + if (!*r_buf) + return unlock_scd (gpg_error (GPG_ERR_ENOMEM)); + + return unlock_scd (0); +} + + + +/* Read a key with ID and return it in an allocate buffer pointed to + by r_BUF as a valid S-expression. */ +int +agent_card_readkey (const char *id, unsigned char **r_buf) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + struct membuf data; + size_t len, buflen; + + *r_buf = NULL; + rc = start_scd (); + if (rc) + return rc; + + init_membuf (&data, 1024); + snprintf (line, DIM(line)-1, "READKEY %s", id); + line[DIM(line)-1] = 0; + rc = assuan_transact (scd_ctx, line, + membuf_data_cb, &data, + NULL, NULL, + NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return unlock_scd (map_assuan_err (rc)); + } + *r_buf = get_membuf (&data, &buflen); + if (!*r_buf) + return unlock_scd (gpg_error (GPG_ERR_ENOMEM)); + + if (!gcry_sexp_canon_len (*r_buf, buflen, NULL, NULL)) + { + xfree (*r_buf); *r_buf = NULL; + return unlock_scd (gpg_error (GPG_ERR_INV_VALUE)); + } + + return unlock_scd (0); +} + + + + +static AssuanError +pass_status_thru (void *opaque, const char *line) +{ + ASSUAN_CONTEXT ctx = opaque; + char keyword[200]; + int i; + + for (i=0; *line && !spacep (line) && i < DIM(keyword)-1; line++, i++) + keyword[i] = *line; + keyword[i] = 0; + /* truncate any remaining keyword stuff. */ + for (; *line && !spacep (line); line++) + ; + while (spacep (line)) + line++; + + assuan_write_status (ctx, keyword, line); + return 0; +} + +static AssuanError +pass_data_thru (void *opaque, const void *buffer, size_t length) +{ + ASSUAN_CONTEXT ctx = opaque; + + assuan_send_data (ctx, buffer, length); + return 0; +} + + +/* Send the line CMDLINE with command for the SCDdaemon to it and send + all status messages back. This command is used as a general quoting + mechanism to pass everything verbatim to SCDAEMOPN. The PIN + inquirey is handled inside gpg-agent. */ +int +agent_card_scd (const char *cmdline, + int (*getpin_cb)(void *, const char *, char*, size_t), + void *getpin_cb_arg, void *assuan_context) +{ + int rc; + struct inq_needpin_s inqparm; + + rc = start_scd (); + if (rc) + return rc; + + inqparm.ctx = scd_ctx; + inqparm.getpin_cb = getpin_cb; + inqparm.getpin_cb_arg = getpin_cb_arg; + rc = assuan_transact (scd_ctx, cmdline, + pass_data_thru, assuan_context, + inq_needpin, &inqparm, + pass_status_thru, assuan_context); + if (rc) + { + return unlock_scd (map_assuan_err (rc)); + } + + return unlock_scd (0); +} + + diff --git a/agent/command.c b/agent/command.c new file mode 100644 index 000000000..ed4ea6b02 --- /dev/null +++ b/agent/command.c @@ -0,0 +1,782 @@ +/* command.c - gpg-agent command handler + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* FIXME: we should not use the default assuan buffering but setup + some buffering in secure mempory to protect session keys etc. */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "agent.h" + +/* maximum allowed size of the inquired ciphertext */ +#define MAXLEN_CIPHERTEXT 4096 +/* maximum allowed size of the key parameters */ +#define MAXLEN_KEYPARAM 1024 + +#define set_error(e,t) assuan_set_error (ctx, ASSUAN_ ## e, (t)) + + +#if MAX_DIGEST_LEN < 20 +#error MAX_DIGEST_LEN shorter than keygrip +#endif + +/* Data used to associate an Assuan context with local server data */ +struct server_local_s { + ASSUAN_CONTEXT assuan_ctx; + int message_fd; + int use_cache_for_signing; +}; + + + + + +static void +reset_notify (ASSUAN_CONTEXT ctx) +{ + CTRL ctrl = assuan_get_pointer (ctx); + + memset (ctrl->keygrip, 0, 20); + ctrl->have_keygrip = 0; + ctrl->digest.valuelen = 0; +} + + +/* Check whether the option NAME appears in LINE */ +static int +has_option (const char *line, const char *name) +{ + const char *s; + int n = strlen (name); + + s = strstr (line, name); + return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n))); +} + +/* Parse a hex string. Return an Assuan error code or 0 on success and the + length of the parsed string in LEN. */ +static int +parse_hexstring (ASSUAN_CONTEXT ctx, const char *string, size_t *len) +{ + const char *p; + size_t n; + + /* parse the hash value */ + for (p=string, n=0; hexdigitp (p); p++, n++) + ; + if (*p) + return set_error (Parameter_Error, "invalid hexstring"); + if ((n&1)) + return set_error (Parameter_Error, "odd number of digits"); + *len = n; + return 0; +} + +/* Parse the keygrip in STRING into the provided buffer BUF. BUF must + provide space for 20 bytes. BUF is not changed if the fucntions + returns an error. */ +static int +parse_keygrip (ASSUAN_CONTEXT ctx, const char *string, unsigned char *buf) +{ + int rc; + size_t n; + const unsigned char *p; + + rc = parse_hexstring (ctx, string, &n); + if (rc) + return rc; + n /= 2; + if (n != 20) + return set_error (Parameter_Error, "invalid length of keygrip"); + + for (p=string, n=0; n < 20; p += 2, n++) + buf[n] = xtoi_2 (p); + + return 0; +} + + + + +/* ISTRUSTED + + Return OK when we have an entry with this fingerprint in our + trustlist */ +static int +cmd_istrusted (ASSUAN_CONTEXT ctx, char *line) +{ + int rc, n, i; + char *p; + char fpr[41]; + + /* parse the fingerprint value */ + for (p=line,n=0; hexdigitp (p); p++, n++) + ; + if (*p || !(n == 40 || n == 32)) + return set_error (Parameter_Error, "invalid fingerprint"); + i = 0; + if (n==32) + { + strcpy (fpr, "00000000"); + i += 8; + } + for (p=line; i < 40; p++, i++) + fpr[i] = *p >= 'a'? (*p & 0xdf): *p; + fpr[i] = 0; + rc = agent_istrusted (fpr); + if (!rc) + return 0; + else if (rc == -1) + return ASSUAN_Not_Trusted; + else + { + log_error ("command is_trusted failed: %s\n", gpg_strerror (rc)); + return map_to_assuan_status (rc); + } +} + +/* LISTTRUSTED + + List all entries from the trustlist */ +static int +cmd_listtrusted (ASSUAN_CONTEXT ctx, char *line) +{ + int rc = agent_listtrusted (ctx); + if (rc) + log_error ("command listtrusted failed: %s\n", gpg_strerror (rc)); + return map_to_assuan_status (rc); +} + + +/* MARKTRUSTED + + Store a new key in into the trustlist*/ +static int +cmd_marktrusted (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc, n, i; + char *p; + char fpr[41]; + int flag; + + /* parse the fingerprint value */ + for (p=line,n=0; hexdigitp (p); p++, n++) + ; + if (!spacep (p) || !(n == 40 || n == 32)) + return set_error (Parameter_Error, "invalid fingerprint"); + i = 0; + if (n==32) + { + strcpy (fpr, "00000000"); + i += 8; + } + for (p=line; i < 40; p++, i++) + fpr[i] = *p >= 'a'? (*p & 0xdf): *p; + fpr[i] = 0; + + while (spacep (p)) + p++; + flag = *p++; + if ( (flag != 'S' && flag != 'P') || !spacep (p) ) + return set_error (Parameter_Error, "invalid flag - must be P or S"); + while (spacep (p)) + p++; + + rc = agent_marktrusted (ctrl, p, fpr, flag); + if (rc) + log_error ("command marktrusted failed: %s\n", gpg_strerror (rc)); + return map_to_assuan_status (rc); +} + + + + +/* HAVEKEY + + Return success when the secret key is available */ +static int +cmd_havekey (ASSUAN_CONTEXT ctx, char *line) +{ + int rc; + unsigned char buf[20]; + + rc = parse_keygrip (ctx, line, buf); + if (rc) + return rc; + + if (agent_key_available (buf)) + return ASSUAN_No_Secret_Key; + + return 0; +} + + +/* SIGKEY + SETKEY + + Set the key used for a sign or decrypt operation */ +static int +cmd_sigkey (ASSUAN_CONTEXT ctx, char *line) +{ + int rc; + CTRL ctrl = assuan_get_pointer (ctx); + + rc = parse_keygrip (ctx, line, ctrl->keygrip); + if (rc) + return rc; + ctrl->have_keygrip = 1; + return 0; +} + + +/* SETHASH + + The client can use this command to tell the server about the data + (which usually is a hash) to be signed. */ +static int +cmd_sethash (ASSUAN_CONTEXT ctx, char *line) +{ + int rc; + size_t n; + char *p; + CTRL ctrl = assuan_get_pointer (ctx); + unsigned char *buf; + char *endp; + int algo; + + /* parse the algo number and check it */ + algo = (int)strtoul (line, &endp, 10); + for (line = endp; *line == ' ' || *line == '\t'; line++) + ; + if (!algo || gcry_md_test_algo (algo)) + return set_error (Unsupported_Algorithm, NULL); + ctrl->digest.algo = algo; + + /* parse the hash value */ + rc = parse_hexstring (ctx, line, &n); + if (rc) + return rc; + n /= 2; + if (n != 16 && n != 20 && n != 24 && n != 32) + return set_error (Parameter_Error, "unsupported length of hash"); + if (n > MAX_DIGEST_LEN) + return set_error (Parameter_Error, "hash value to long"); + + buf = ctrl->digest.value; + ctrl->digest.valuelen = n; + for (p=line, n=0; n < ctrl->digest.valuelen; p += 2, n++) + buf[n] = xtoi_2 (p); + for (; n < ctrl->digest.valuelen; n++) + buf[n] = 0; + return 0; +} + + +/* PKSIGN + + Perform the actual sign operation. Neither input nor output are + sensitive to eavesdropping */ +static int +cmd_pksign (ASSUAN_CONTEXT ctx, char *line) +{ + int rc; + int ignore_cache = 0; + CTRL ctrl = assuan_get_pointer (ctx); + + if (opt.ignore_cache_for_signing) + ignore_cache = 1; + else if (!ctrl->server_local->use_cache_for_signing) + ignore_cache = 1; + + rc = agent_pksign (ctrl, assuan_get_data_fp (ctx), ignore_cache); + if (rc) + log_error ("command pksign failed: %s\n", gpg_strerror (rc)); + return map_to_assuan_status (rc); +} + +/* PKDECRYPT + + Perform the actual decrypt operation. Input is not + sensitive to eavesdropping */ +static int +cmd_pkdecrypt (ASSUAN_CONTEXT ctx, char *line) +{ + int rc; + CTRL ctrl = assuan_get_pointer (ctx); + char *value; + size_t valuelen; + + /* First inquire the data to decrypt */ + rc = assuan_inquire (ctx, "CIPHERTEXT", + &value, &valuelen, MAXLEN_CIPHERTEXT); + if (rc) + return rc; + + rc = agent_pkdecrypt (ctrl, value, valuelen, assuan_get_data_fp (ctx)); + xfree (value); + if (rc) + log_error ("command pkdecrypt failed: %s\n", gpg_strerror (rc)); + return map_to_assuan_status (rc); +} + + +/* GENKEY + + Generate a new key, store the secret part and return the public + part. Here is an example transaction: + + C: GENKEY + S: INQUIRE KEYPARM + C: D (genkey (rsa (nbits 1024))) + C: END + S: D (public-key + S: D (rsa (n 326487324683264) (e 10001))) + S OK key created +*/ + +static int +cmd_genkey (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + char *value; + size_t valuelen; + + /* First inquire the parameters */ + rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM); + if (rc) + return rc; + + rc = agent_genkey (ctrl, value, valuelen, assuan_get_data_fp (ctx)); + xfree (value); + if (rc) + log_error ("command genkey failed: %s\n", gpg_strerror (rc)); + return map_to_assuan_status (rc); +} + + +static void +plus_to_blank (char *s) +{ + for (; *s; s++) + { + if (*s == '+') + *s = ' '; + } +} + +/* GET_PASSPHRASE [ ] + + This function is usually used to ask for a passphrase to be used + for conventional encryption, but may also be used by programs which + need specal handling of passphrases. This command uses a syntax + which helps clients to use the agent with minimum effort. The + agent either returns with an error or with a OK followed by the hex + encoded passphrase. Note that the length of the strings is + implicitly limited by the maximum length of a command. +*/ + +static int +cmd_get_passphrase (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + const char *pw; + char *response; + char *cacheid = NULL, *desc = NULL, *prompt = NULL, *errtext = NULL; + char *p; + void *cache_marker; + + /* parse the stuff */ + for (p=line; *p == ' '; p++) + ; + cacheid = p; + p = strchr (cacheid, ' '); + if (p) + { + *p++ = 0; + while (*p == ' ') + p++; + errtext = p; + p = strchr (errtext, ' '); + if (p) + { + *p++ = 0; + while (*p == ' ') + p++; + prompt = p; + p = strchr (prompt, ' '); + if (p) + { + *p++ = 0; + while (*p == ' ') + p++; + desc = p; + p = strchr (desc, ' '); + if (p) + *p = 0; /* ignore garbage */ + } + } + } + if (!cacheid || !*cacheid || strlen (cacheid) > 50) + return set_error (Parameter_Error, "invalid length of cacheID"); + if (!desc) + return set_error (Parameter_Error, "no description given"); + + if (!strcmp (cacheid, "X")) + cacheid = NULL; + if (!strcmp (errtext, "X")) + errtext = NULL; + if (!strcmp (prompt, "X")) + prompt = NULL; + if (!strcmp (desc, "X")) + desc = NULL; + + /* Note: we store the hexified versions in the cache. */ + pw = cacheid ? agent_get_cache (cacheid, &cache_marker) : NULL; + if (pw) + { + assuan_begin_confidential (ctx); + rc = assuan_set_okay_line (ctx, pw); + agent_unlock_cache_entry (&cache_marker); + } + else + { + /* Note, that we only need to replace the + characters and + should leave the other escaping in place because the escaped + string is send verbatim to the pinentry which does the + unescaping (but not the + replacing) */ + if (errtext) + plus_to_blank (errtext); + if (prompt) + plus_to_blank (prompt); + if (desc) + plus_to_blank (desc); + + rc = agent_get_passphrase (ctrl, &response, desc, prompt, errtext); + if (!rc) + { + if (cacheid) + agent_put_cache (cacheid, response, 0); + assuan_begin_confidential (ctx); + rc = assuan_set_okay_line (ctx, response); + xfree (response); + } + } + + if (rc) + log_error ("command get_passphrase failed: %s\n", gpg_strerror (rc)); + return map_to_assuan_status (rc); +} + + +/* CLEAR_PASSPHRASE + + may be used to invalidate the cache entry for a passphrase. The + function returns with OK even when there is no cached passphrase. +*/ + +static int +cmd_clear_passphrase (ASSUAN_CONTEXT ctx, char *line) +{ + char *cacheid = NULL; + char *p; + + /* parse the stuff */ + for (p=line; *p == ' '; p++) + ; + cacheid = p; + p = strchr (cacheid, ' '); + if (p) + *p = 0; /* ignore garbage */ + if (!cacheid || !*cacheid || strlen (cacheid) > 50) + return set_error (Parameter_Error, "invalid length of cacheID"); + + agent_put_cache (cacheid, NULL, 0); + return 0; +} + + +/* LEARN [--send] + + Learn something about the currently inserted smartcard. With + --send the new certificates are send back. */ +static int +cmd_learn (ASSUAN_CONTEXT ctx, char *line) +{ + int rc; + + rc = agent_handle_learn (has_option (line, "--send")? ctx : NULL); + if (rc) + log_error ("command learn failed: %s\n", gpg_strerror (rc)); + return map_to_assuan_status (rc); +} + + + +/* PASSWD + + Change the passphrase/PID for the key identified by keygrip in LINE. */ +static int +cmd_passwd (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + unsigned char grip[20]; + gcry_sexp_t s_skey = NULL; + unsigned char *shadow_info = NULL; + + rc = parse_keygrip (ctx, line, grip); + if (rc) + return rc; /* we can't jump to leave because this is already an + Assuan error code. */ + + s_skey = agent_key_from_file (ctrl, grip, &shadow_info, 1); + if (!s_skey && !shadow_info) + rc = gpg_error (GPG_ERR_NO_SECKEY); + else if (!s_skey) + { + log_error ("changing a smartcard PIN is not yet supported\n"); + rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + } + else + rc = agent_protect_and_store (ctrl, s_skey); + + gcry_sexp_release (s_skey); + xfree (shadow_info); + if (rc) + log_error ("command passwd failed: %s\n", gpg_strerror (rc)); + return map_to_assuan_status (rc); +} + + +/* SCD + + This is a general quote command to redirect everything to the + SCDAEMON. */ +static int +cmd_scd (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + + rc = divert_generic_cmd (ctrl, line, ctx); + + return map_to_assuan_status (rc); +} + + + +static int +option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value) +{ + CTRL ctrl = assuan_get_pointer (ctx); + + if (!strcmp (key, "display")) + { + if (ctrl->display) + free (ctrl->display); + ctrl->display = strdup (value); + if (!ctrl->display) + return ASSUAN_Out_Of_Core; + } + else if (!strcmp (key, "ttyname")) + { + if (!opt.keep_tty) + { + if (ctrl->ttyname) + free (ctrl->ttyname); + ctrl->ttyname = strdup (value); + if (!ctrl->ttyname) + return ASSUAN_Out_Of_Core; + } + } + else if (!strcmp (key, "ttytype")) + { + if (!opt.keep_tty) + { + if (ctrl->ttytype) + free (ctrl->ttytype); + ctrl->ttytype = strdup (value); + if (!ctrl->ttytype) + return ASSUAN_Out_Of_Core; + } + } + else if (!strcmp (key, "lc-ctype")) + { + if (ctrl->lc_ctype) + free (ctrl->lc_ctype); + ctrl->lc_ctype = strdup (value); + if (!ctrl->lc_ctype) + return ASSUAN_Out_Of_Core; + } + else if (!strcmp (key, "lc-messages")) + { + if (ctrl->lc_messages) + free (ctrl->lc_messages); + ctrl->lc_messages = strdup (value); + if (!ctrl->lc_messages) + return ASSUAN_Out_Of_Core; + } + else if (!strcmp (key, "use-cache-for-signing")) + ctrl->server_local->use_cache_for_signing = *value? atoi (value) : 0; + else + return ASSUAN_Invalid_Option; + + return 0; +} + + +/* Tell the assuan library about our commands */ +static int +register_commands (ASSUAN_CONTEXT ctx) +{ + static struct { + const char *name; + int (*handler)(ASSUAN_CONTEXT, char *line); + } table[] = { + { "ISTRUSTED", cmd_istrusted }, + { "HAVEKEY", cmd_havekey }, + { "SIGKEY", cmd_sigkey }, + { "SETKEY", cmd_sigkey }, + { "SETHASH", cmd_sethash }, + { "PKSIGN", cmd_pksign }, + { "PKDECRYPT", cmd_pkdecrypt }, + { "GENKEY", cmd_genkey }, + { "GET_PASSPHRASE", cmd_get_passphrase }, + { "CLEAR_PASSPHRASE", cmd_clear_passphrase }, + { "LISTTRUSTED", cmd_listtrusted }, + { "MARKTRUSTED", cmd_marktrusted }, + { "LEARN", cmd_learn }, + { "PASSWD", cmd_passwd }, + { "INPUT", NULL }, + { "OUTPUT", NULL }, + { "SCD", cmd_scd }, + { NULL } + }; + int i, rc; + + for (i=0; table[i].name; i++) + { + rc = assuan_register_command (ctx, table[i].name, table[i].handler); + if (rc) + return rc; + } + assuan_register_reset_notify (ctx, reset_notify); + assuan_register_option_handler (ctx, option_handler); + return 0; +} + + +/* Startup the server. If LISTEN_FD and FD is given as -1, this is a simple + piper server, otherwise it is a regular server */ +void +start_command_handler (int listen_fd, int fd) +{ + int rc; + ASSUAN_CONTEXT ctx; + struct server_control_s ctrl; + + memset (&ctrl, 0, sizeof ctrl); + agent_init_default_ctrl (&ctrl); + + if (listen_fd == -1 && fd == -1) + { + int filedes[2]; + + filedes[0] = 0; + filedes[1] = 1; + rc = assuan_init_pipe_server (&ctx, filedes); + } + else if (listen_fd != -1) + { + rc = assuan_init_socket_server (&ctx, listen_fd); + } + else + { + rc = assuan_init_connected_socket_server (&ctx, fd); + } + if (rc) + { + log_error ("failed to initialize the server: %s\n", + assuan_strerror(rc)); + agent_exit (2); + } + rc = register_commands (ctx); + if (rc) + { + log_error ("failed to register commands with Assuan: %s\n", + assuan_strerror(rc)); + agent_exit (2); + } + + assuan_set_pointer (ctx, &ctrl); + ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local); + ctrl.server_local->assuan_ctx = ctx; + ctrl.server_local->message_fd = -1; + ctrl.server_local->use_cache_for_signing = 1; + + if (DBG_ASSUAN) + assuan_set_log_stream (ctx, log_get_stream ()); + + for (;;) + { + rc = assuan_accept (ctx); + if (rc == -1) + { + break; + } + else if (rc) + { + log_info ("Assuan accept problem: %s\n", assuan_strerror (rc)); + break; + } + + rc = assuan_process (ctx); + if (rc) + { + log_info ("Assuan processing failed: %s\n", assuan_strerror (rc)); + continue; + } + } + + + assuan_deinit_server (ctx); + if (ctrl.display) + free (ctrl.display); + if (ctrl.ttyname) + free (ctrl.ttyname); + if (ctrl.ttytype) + free (ctrl.ttytype); + if (ctrl.lc_ctype) + free (ctrl.lc_ctype); + if (ctrl.lc_messages) + free (ctrl.lc_messages); +} + diff --git a/agent/divert-scd.c b/agent/divert-scd.c new file mode 100644 index 000000000..69f184474 --- /dev/null +++ b/agent/divert-scd.c @@ -0,0 +1,319 @@ +/* divert-scd.c - divert operations to the scdaemon + * Copyright (C) 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "agent.h" +#include "sexp-parse.h" +#include "i18n.h" + + +static int +ask_for_card (CTRL ctrl, const unsigned char *shadow_info, char **r_kid) +{ + int rc, i; + const unsigned char *s; + size_t n; + char *serialno; + int no_card = 0; + char *desc; + char *want_sn, *want_kid; + int want_sn_displen; + + *r_kid = NULL; + s = shadow_info; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + want_sn = xtrymalloc (n*2+1); + if (!want_sn) + return out_of_core (); + for (i=0; i < n; i++) + sprintf (want_sn+2*i, "%02X", s[i]); + s += n; + /* We assume that a 20 byte serial number is a standard one which + seems to have the property to have a zero in the last nibble. We + don't display this '0' because it may confuse the user */ + want_sn_displen = strlen (want_sn); + if (want_sn_displen == 20 && want_sn[19] == '0') + want_sn_displen--; + + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + want_kid = xtrymalloc (n+1); + if (!want_kid) + { + gpg_error_t tmperr = out_of_core (); + xfree (want_sn); + return tmperr; + } + memcpy (want_kid, s, n); + want_kid[n] = 0; + + for (;;) + { + rc = agent_card_serialno (&serialno); + if (!rc) + { + log_debug ("detected card with S/N %s\n", serialno); + i = strcmp (serialno, want_sn); + xfree (serialno); + serialno = NULL; + if (!i) + { + xfree (want_sn); + *r_kid = want_kid; + return 0; /* yes, we have the correct card */ + } + } + else if (gpg_err_code (rc) == GPG_ERR_CARD_NOT_PRESENT) + { + log_debug ("no card present\n"); + rc = 0; + no_card = 1; + } + else + { + log_error ("error accesing card: %s\n", gpg_strerror (rc)); + } + + if (!rc) + { + if (asprintf (&desc, + "%s:%%0A%%0A" + " \"%.*s\"", + no_card? "Please insert the card with serial number" + : "Please remove the current card and " + "insert the one with serial number", + want_sn_displen, want_sn) < 0) + { + rc = out_of_core (); + } + else + { + rc = agent_get_confirmation (ctrl, desc, NULL, NULL); + free (desc); + } + } + if (rc) + { + xfree (want_sn); + xfree (want_kid); + return rc; + } + } +} + + +/* Put the DIGEST into an DER encoded comtainer and return it in R_VAL. */ +static int +encode_md_for_card (const unsigned char *digest, size_t digestlen, int algo, + unsigned char **r_val, size_t *r_len) +{ + byte *frame; + byte asn[100]; + size_t asnlen; + + asnlen = DIM(asn); + if (gcry_md_algo_info (algo, GCRYCTL_GET_ASNOID, asn, &asnlen)) + { + log_error ("no object identifier for algo %d\n", algo); + return gpg_error (GPG_ERR_INTERNAL); + } + + frame = xtrymalloc (asnlen + digestlen); + if (!frame) + return out_of_core (); + memcpy (frame, asn, asnlen); + memcpy (frame+asnlen, digest, digestlen); + if (DBG_CRYPTO) + log_printhex ("encoded hash:", frame, asnlen+digestlen); + + *r_val = frame; + *r_len = asnlen+digestlen; + return 0; +} + + +/* Callback used to ask for the PIN which should be set into BUF. The + buf has been allocated by the caller and is of size MAXBUF which + includes the terminating null. The function should return an UTF-8 + string with the passphrase, the buffer may optionally be padded + with arbitrary characters */ +static int +getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf) +{ + struct pin_entry_info_s *pi; + int rc; + char *desc; + CTRL ctrl = opaque; + + if (maxbuf < 2) + return gpg_error (GPG_ERR_INV_VALUE); + + + /* FIXME: keep PI and TRIES in OPAQUE. Frankly this is a whole + mess because we should call the card's verify function from the + pinentry check pin CB. */ + pi = gcry_calloc_secure (1, sizeof (*pi) + 100); + pi->max_length = maxbuf-1; + pi->min_digits = 0; /* we want a real passphrase */ + pi->max_digits = 8; + pi->max_tries = 3; + + if ( asprintf (&desc, _("Please enter the PIN%s%s%s to unlock the card"), + info? " (`":"", + info? info:"", + info? "')":"") < 0) + desc = NULL; + rc = agent_askpin (ctrl, desc?desc:info, pi); + free (desc); + if (!rc) + { + strncpy (buf, pi->pin, maxbuf-1); + buf[maxbuf-1] = 0; + } + xfree (pi); + return rc; +} + + + + +int +divert_pksign (CTRL ctrl, + const unsigned char *digest, size_t digestlen, int algo, + const unsigned char *shadow_info, unsigned char **r_sig) +{ + int rc; + char *kid; + size_t siglen; + char *sigval; + unsigned char *data; + size_t ndata; + + rc = ask_for_card (ctrl, shadow_info, &kid); + if (rc) + return rc; + + rc = encode_md_for_card (digest, digestlen, algo, + &data, &ndata); + if (rc) + return rc; + + rc = agent_card_pksign (kid, getpin_cb, ctrl, + data, ndata, &sigval, &siglen); + if (!rc) + *r_sig = sigval; + xfree (data); + xfree (kid); + + return rc; +} + + +/* Decrypt the the value given asn an S-expression in CIPHER using the + key identified by SHADOW_INFO and return the plaintext in an + allocated buffer in R_BUF. */ +int +divert_pkdecrypt (CTRL ctrl, + const unsigned char *cipher, + const unsigned char *shadow_info, + char **r_buf, size_t *r_len) +{ + int rc; + char *kid; + const unsigned char *s; + size_t n; + const unsigned char *ciphertext; + size_t ciphertextlen; + char *plaintext; + size_t plaintextlen; + + s = cipher; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "enc-val")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "rsa")) + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "a")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + ciphertext = s; + ciphertextlen = n; + + rc = ask_for_card (ctrl, shadow_info, &kid); + if (rc) + return rc; + + rc = agent_card_pkdecrypt (kid, getpin_cb, ctrl, + ciphertext, ciphertextlen, + &plaintext, &plaintextlen); + if (!rc) + { + *r_buf = plaintext; + *r_len = plaintextlen; + } + xfree (kid); + return rc; +} + + +int +divert_generic_cmd (CTRL ctrl, const char *cmdline, void *assuan_context) +{ + return agent_card_scd (cmdline, getpin_cb, ctrl, assuan_context); +} + + + + + diff --git a/agent/findkey.c b/agent/findkey.c new file mode 100644 index 000000000..db36cb1b9 --- /dev/null +++ b/agent/findkey.c @@ -0,0 +1,359 @@ +/* findkey.c - locate the secret key + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "agent.h" + +/* Helper to pass data to the check callback of the unprotect function. */ +struct try_unprotect_arg_s { + const unsigned char *protected_key; + unsigned char *unprotected_key; +}; + + + +int +agent_write_private_key (const unsigned char *grip, + const void *buffer, size_t length, int force) +{ + int i; + char *fname; + FILE *fp; + char hexgrip[40+4+1]; + + for (i=0; i < 20; i++) + sprintf (hexgrip+2*i, "%02X", grip[i]); + strcpy (hexgrip+40, ".key"); + + fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); + if (force) + fp = fopen (fname, "wb"); + else + { + int fd; + + if (!access (fname, F_OK)) + { + log_error ("secret key file `%s' already exists\n", fname); + xfree (fname); + return gpg_error (GPG_ERR_GENERAL); + } + + /* We would like to create FNAME but only if it does not already + exist. We cannot make this guarantee just using POSIX (GNU + provides the "x" opentype for fopen, however, this is not + portable). Thus, we use the more flexible open function and + then use fdopen to obtain a stream. + + The mode parameter to open is what fopen uses. It will be + combined with the process' umask automatically. */ + fd = open (fname, O_CREAT | O_EXCL | O_RDWR, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if (fd < 0) + fp = 0; + else + { + fp = fdopen (fd, "wb"); + if (!fp) + { + int save_e = errno; + close (fd); + errno = save_e; + } + } + } + + if (!fp) + { + gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("can't create `%s': %s\n", fname, strerror (errno)); + xfree (fname); + return tmperr; + } + + if (fwrite (buffer, length, 1, fp) != 1) + { + gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("error writing `%s': %s\n", fname, strerror (errno)); + fclose (fp); + remove (fname); + xfree (fname); + return tmperr; + } + if ( fclose (fp) ) + { + gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("error closing `%s': %s\n", fname, strerror (errno)); + remove (fname); + xfree (fname); + return tmperr; + } + + xfree (fname); + return 0; +} + + +/* Callback function to try the unprotection from the passpharse query + code. */ +static int +try_unprotect_cb (struct pin_entry_info_s *pi) +{ + struct try_unprotect_arg_s *arg = pi->check_cb_arg; + size_t dummy; + + assert (!arg->unprotected_key); + return agent_unprotect (arg->protected_key, pi->pin, + &arg->unprotected_key, &dummy); +} + + +/* Unprotect the canconical encoded S-expression key in KEYBUF. GRIP + should be the hex encoded keygrip of that key to be used with the + caching mechanism. */ +static int +unprotect (CTRL ctrl, + unsigned char **keybuf, const unsigned char *grip, int ignore_cache) +{ + struct pin_entry_info_s *pi; + struct try_unprotect_arg_s arg; + int rc, i; + unsigned char *result; + size_t resultlen; + char hexgrip[40+1]; + + for (i=0; i < 20; i++) + sprintf (hexgrip+2*i, "%02X", grip[i]); + hexgrip[40] = 0; + + /* first try to get it from the cache - if there is none or we can't + unprotect it, we fall back to ask the user */ + if (!ignore_cache) + { + void *cache_marker; + const char *pw = agent_get_cache (hexgrip, &cache_marker); + if (pw) + { + rc = agent_unprotect (*keybuf, pw, &result, &resultlen); + agent_unlock_cache_entry (&cache_marker); + if (!rc) + { + xfree (*keybuf); + *keybuf = result; + return 0; + } + rc = 0; + } + } + + pi = gcry_calloc_secure (1, sizeof (*pi) + 100); + pi->max_length = 100; + pi->min_digits = 0; /* we want a real passphrase */ + pi->max_digits = 8; + pi->max_tries = 3; + pi->check_cb = try_unprotect_cb; + arg.protected_key = *keybuf; + arg.unprotected_key = NULL; + pi->check_cb_arg = &arg; + + rc = agent_askpin (ctrl, NULL, pi); + if (!rc) + { + assert (arg.unprotected_key); + agent_put_cache (hexgrip, pi->pin, 0); + xfree (*keybuf); + *keybuf = arg.unprotected_key; + } + xfree (pi); + return rc; +} + + + +/* Return the secret key as an S-Exp after locating it using the grip. + Returns NULL if key is not available or the operation should be + diverted to a token. In the latter case shadow_info will point to + an allocated S-Expression with the shadow_info part from the file. + With IGNORE_CACHE passed as true the passphrase is not taken from + the cache.*/ +gcry_sexp_t +agent_key_from_file (CTRL ctrl, + const unsigned char *grip, unsigned char **shadow_info, + int ignore_cache) +{ + int i, rc; + char *fname; + FILE *fp; + struct stat st; + unsigned char *buf; + size_t len, buflen, erroff; + gcry_sexp_t s_skey; + char hexgrip[40+4+1]; + + if (shadow_info) + *shadow_info = NULL; + + for (i=0; i < 20; i++) + sprintf (hexgrip+2*i, "%02X", grip[i]); + strcpy (hexgrip+40, ".key"); + + fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); + fp = fopen (fname, "rb"); + if (!fp) + { + log_error ("can't open `%s': %s\n", fname, strerror (errno)); + xfree (fname); + return NULL; + } + + if (fstat (fileno(fp), &st)) + { + log_error ("can't stat `%s': %s\n", fname, strerror (errno)); + xfree (fname); + fclose (fp); + return NULL; + } + + buflen = st.st_size; + buf = xmalloc (buflen+1); + if (fread (buf, buflen, 1, fp) != 1) + { + log_error ("error reading `%s': %s\n", fname, strerror (errno)); + xfree (fname); + fclose (fp); + xfree (buf); + return NULL; + } + + rc = gcry_sexp_sscan (&s_skey, &erroff, buf, buflen); + xfree (fname); + fclose (fp); + xfree (buf); + if (rc) + { + log_error ("failed to build S-Exp (off=%u): %s\n", + (unsigned int)erroff, gpg_strerror (rc)); + return NULL; + } + len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); + assert (len); + buf = xtrymalloc (len); + if (!buf) + { + gcry_sexp_release (s_skey); + return NULL; + } + len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, buf, len); + assert (len); + gcry_sexp_release (s_skey); + + switch (agent_private_key_type (buf)) + { + case PRIVATE_KEY_CLEAR: + break; /* no unprotection needed */ + case PRIVATE_KEY_PROTECTED: + rc = unprotect (ctrl, &buf, grip, ignore_cache); + if (rc) + log_error ("failed to unprotect the secret key: %s\n", + gpg_strerror (rc)); + break; + case PRIVATE_KEY_SHADOWED: + if (shadow_info) + { + const unsigned char *s; + size_t n; + + rc = agent_get_shadow_info (buf, &s); + if (!rc) + { + n = gcry_sexp_canon_len (s, 0, NULL,NULL); + assert (n); + *shadow_info = xtrymalloc (n); + if (!*shadow_info) + rc = out_of_core (); + else + { + memcpy (*shadow_info, s, n); + rc = 0; + } + } + if (rc) + log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc)); + } + rc = -1; /* ugly interface: we return an error but keep a value + in shadow_info. */ + break; + default: + log_error ("invalid private key format\n"); + rc = gpg_error (GPG_ERR_BAD_SECKEY); + break; + } + if (rc) + { + xfree (buf); + return NULL; + } + + /* arggg FIXME: does scan support secure memory? */ + rc = gcry_sexp_sscan (&s_skey, &erroff, + buf, gcry_sexp_canon_len (buf, 0, NULL, NULL)); + xfree (buf); + if (rc) + { + log_error ("failed to build S-Exp (off=%u): %s\n", + (unsigned int)erroff, gpg_strerror (rc)); + return NULL; + } + + return s_skey; +} + +/* Return the secret key as an S-Exp after locating it using the grip. + Returns NULL if key is not available. 0 = key is available */ +int +agent_key_available (const unsigned char *grip) +{ + int i; + char *fname; + char hexgrip[40+4+1]; + + for (i=0; i < 20; i++) + sprintf (hexgrip+2*i, "%02X", grip[i]); + strcpy (hexgrip+40, ".key"); + + fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); + i = !access (fname, R_OK)? 0 : -1; + xfree (fname); + return i; +} + + + diff --git a/agent/genkey.c b/agent/genkey.c new file mode 100644 index 000000000..0a0577f17 --- /dev/null +++ b/agent/genkey.c @@ -0,0 +1,240 @@ +/* pksign.c - Generate a keypair + * Copyright (C) 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "agent.h" +#include "i18n.h" + +static int +store_key (gcry_sexp_t private, const char *passphrase, int force) +{ + int rc; + char *buf; + size_t len; + unsigned char grip[20]; + + if ( !gcry_pk_get_keygrip (private, grip) ) + { + log_error ("can't calculate keygrip\n"); + return gpg_error (GPG_ERR_GENERAL); + } + + len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, NULL, 0); + assert (len); + buf = gcry_malloc_secure (len); + if (!buf) + return out_of_core (); + len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, buf, len); + assert (len); + + if (passphrase) + { + unsigned char *p; + + rc = agent_protect (buf, passphrase, &p, &len); + if (rc) + { + xfree (buf); + return rc; + } + xfree (buf); + buf = p; + } + + rc = agent_write_private_key (grip, buf, len, force); + xfree (buf); + return rc; +} + +/* Callback function to compare the first entered PIN with the one + currently being entered. */ +static int +reenter_compare_cb (struct pin_entry_info_s *pi) +{ + const char *pin1 = pi->check_cb_arg; + + if (!strcmp (pin1, pi->pin)) + return 0; /* okay */ + pi->cb_errtext = _("does not match - try again"); + return -1; +} + + + +/* Generate a new keypair according to the parameters given in + KEYPARAM */ +int +agent_genkey (CTRL ctrl, const char *keyparam, size_t keyparamlen, + FILE *outfp) +{ + gcry_sexp_t s_keyparam, s_key, s_private, s_public; + struct pin_entry_info_s *pi, *pi2; + int rc; + size_t len; + char *buf; + + rc = gcry_sexp_sscan (&s_keyparam, NULL, keyparam, keyparamlen); + if (rc) + { + log_error ("failed to convert keyparam: %s\n", gpg_strerror (rc)); + return gpg_error (GPG_ERR_INV_DATA); + } + + /* Get the passphrase now, cause key generation may take a while. */ + { + const char *text1 = _("Please enter the passphrase to%0A" + "to protect your new key"); + const char *text2 = _("Please re-enter this passphrase"); + + pi = gcry_calloc_secure (2, sizeof (*pi) + 100); + pi2 = pi + (sizeof *pi + 100); + pi->max_length = 100; + pi->max_tries = 3; + pi2->max_length = 100; + pi2->max_tries = 3; + pi2->check_cb = reenter_compare_cb; + pi2->check_cb_arg = pi->pin; + + rc = agent_askpin (ctrl, text1, pi); + if (!rc) + rc = agent_askpin (ctrl, text2, pi2); + if (rc) + return rc; + if (!*pi->pin) + { + xfree (pi); + pi = NULL; /* User does not want a passphrase. */ + } + } + + rc = gcry_pk_genkey (&s_key, s_keyparam ); + gcry_sexp_release (s_keyparam); + if (rc) + { + log_error ("key generation failed: %s\n", gpg_strerror (rc)); + xfree (pi); + return map_gcry_err (rc); + } + + /* break out the parts */ + s_private = gcry_sexp_find_token (s_key, "private-key", 0); + if (!s_private) + { + log_error ("key generation failed: invalid return value\n"); + gcry_sexp_release (s_key); + xfree (pi); + return gpg_error (GPG_ERR_INV_DATA); + } + s_public = gcry_sexp_find_token (s_key, "public-key", 0); + if (!s_public) + { + log_error ("key generation failed: invalid return value\n"); + gcry_sexp_release (s_private); + gcry_sexp_release (s_key); + xfree (pi); + return gpg_error (GPG_ERR_INV_DATA); + } + gcry_sexp_release (s_key); s_key = NULL; + + /* store the secret key */ + log_debug ("storing private key\n"); + rc = store_key (s_private, pi? pi->pin:NULL, 0); + xfree (pi); pi = NULL; + gcry_sexp_release (s_private); + if (rc) + { + gcry_sexp_release (s_public); + return rc; + } + + /* return the public key */ + log_debug ("returning public key\n"); + len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, NULL, 0); + assert (len); + buf = xtrymalloc (len); + if (!buf) + { + gpg_error_t tmperr = out_of_core (); + gcry_sexp_release (s_private); + gcry_sexp_release (s_public); + return tmperr; + } + len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, buf, len); + assert (len); + if (fwrite (buf, len, 1, outfp) != 1) + { + gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("error writing public key: %s\n", strerror (errno)); + gcry_sexp_release (s_private); + gcry_sexp_release (s_public); + xfree (buf); + return tmperr; + } + gcry_sexp_release (s_public); + xfree (buf); + + return 0; +} + + + +/* Apply a new passpahrse to the key S_SKEY and store it. */ +int +agent_protect_and_store (CTRL ctrl, gcry_sexp_t s_skey) +{ + struct pin_entry_info_s *pi, *pi2; + int rc; + + { + const char *text1 = _("Please enter the new passphrase"); + const char *text2 = _("Please re-enter this passphrase"); + + pi = gcry_calloc_secure (2, sizeof (*pi) + 100); + pi2 = pi + (sizeof *pi + 100); + pi->max_length = 100; + pi->max_tries = 3; + pi2->max_length = 100; + pi2->max_tries = 3; + pi2->check_cb = reenter_compare_cb; + pi2->check_cb_arg = pi->pin; + + rc = agent_askpin (ctrl, text1, pi); + if (!rc) + rc = agent_askpin (ctrl, text2, pi2); + if (rc) + return rc; + if (!*pi->pin) + { + xfree (pi); + pi = NULL; /* User does not want a passphrase. */ + } + } + + rc = store_key (s_skey, pi? pi->pin:NULL, 1); + xfree (pi); + return 0; +} diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c new file mode 100644 index 000000000..675f2be3f --- /dev/null +++ b/agent/gpg-agent.c @@ -0,0 +1,1063 @@ +/* gpg-agent.c - The GnuPG Agent + * Copyright (C) 2000, 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef USE_GNU_PTH +# include +#endif + +#define JNLIB_NEED_LOG_LOGV +#include "agent.h" +#include /* malloc hooks */ + +#include "i18n.h" +#include "sysutils.h" + + +enum cmd_and_opt_values +{ aNull = 0, + oCsh = 'c', + oQuiet = 'q', + oSh = 's', + oVerbose = 'v', + + oNoVerbose = 500, + oOptions, + oDebug, + oDebugAll, + oDebugWait, + oNoGreeting, + oNoOptions, + oHomedir, + oNoDetach, + oNoGrab, + oLogFile, + oServer, + oDaemon, + oBatch, + + oPinentryProgram, + oDisplay, + oTTYname, + oTTYtype, + oLCctype, + oLCmessages, + oScdaemonProgram, + oDefCacheTTL, + oDisablePth, + + oIgnoreCacheForSigning, + oKeepTTY, + oKeepDISPLAY, + +aTest }; + + + +static ARGPARSE_OPTS opts[] = { + + { 301, NULL, 0, N_("@Options:\n ") }, + + { oServer, "server", 0, N_("run in server mode (foreground)") }, + { oDaemon, "daemon", 0, N_("run in daemon mode (background)") }, + { oVerbose, "verbose", 0, N_("verbose") }, + { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, + { oSh, "sh", 0, N_("sh-style command output") }, + { oCsh, "csh", 0, N_("csh-style command output") }, + { oOptions, "options" , 2, N_("read options from file")}, + { oDebug, "debug" ,4|16, N_("set debugging flags")}, + { oDebugAll, "debug-all" ,0, N_("enable full debugging")}, + { oDebugWait,"debug-wait",1, "@"}, + { oNoDetach, "no-detach" ,0, N_("do not detach from the console")}, + { oNoGrab, "no-grab" ,0, N_("do not grab keyboard and mouse")}, + { oLogFile, "log-file" ,2, N_("use a log file for the server")}, + { oDisablePth, "disable-pth", 0, N_("do not allow multiple connections")}, + + { oPinentryProgram, "pinentry-program", 2 , "path to PIN Entry program" }, + { oDisplay, "display", 2, "set the display" }, + { oTTYname, "ttyname", 2, "set the tty terminal node name" }, + { oTTYtype, "ttytype", 2, "set the tty terminal type" }, + { oLCctype, "lc-ctype", 2, "set the tty LC_CTYPE value" }, + { oLCmessages, "lc-messages", 2, "set the tty LC_MESSAGES value" }, + + { oScdaemonProgram, "scdaemon-program", 2 , "path to SCdaemon program" }, + { oDefCacheTTL, "default-cache-ttl", 4, + "|N|expire cached PINs after N seconds"}, + { oIgnoreCacheForSigning, "ignore-cache-for-signing", 0, + "do not use the PIN cache when signing"}, + { oKeepTTY, "keep-tty", 0, N_("ignore requests to change the TTY")}, + { oKeepDISPLAY, "keep-display", + 0, N_("ignore requests to change the X display")}, + {0} +}; + + +static volatile int caught_fatal_sig = 0; + +/* flag to indicate that a shutdown was requested */ +static int shutdown_pending; + + +/* It is possible that we are currently running under setuid permissions */ +static int maybe_setuid = 1; + +/* Name of the communication socket */ +static char socket_name[128]; + +/* Default values for options passed to the pinentry. */ +static char *default_display; +static char *default_ttyname; +static char *default_ttytype; +static char *default_lc_ctype; +static char *default_lc_messages; + +/* Name of a config file, which will be reread on a HUP if it is not NULL. */ +static char *config_filename; + + +/* Local prototypes. */ +static void create_directories (void); +#ifdef USE_GNU_PTH +static void handle_connections (int listen_fd); +#endif + + + +static const char * +my_strusage (int level) +{ + const char *p; + switch (level) + { + case 11: p = "gpg-agent (GnuPG)"; + break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n"); + break; + case 1: + case 40: p = _("Usage: gpg-agent [options] (-h for help)"); + break; + case 41: p = _("Syntax: gpg-agent [options] [command [args]]\n" + "Secret key management for GnuPG\n"); + break; + + default: p = NULL; + } + return p; +} + + + +static void +i18n_init (void) +{ +#ifdef USE_SIMPLE_GETTEXT + set_gettext_file( PACKAGE ); +#else +#ifdef ENABLE_NLS + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); +#endif +#endif +} + + + +/* Used by gcry for logging */ +static void +my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr) +{ + /* translate the log levels */ + switch (level) + { + case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break; + case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break; + case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break; + case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break; + case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break; + case GCRY_LOG_BUG: level = JNLIB_LOG_BUG; break; + case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break; + default: level = JNLIB_LOG_ERROR; break; + } + log_logv (level, fmt, arg_ptr); +} + + +static void +cleanup (void) +{ + if (*socket_name) + { + char *p; + + remove (socket_name); + p = strrchr (socket_name, '/'); + if (p) + { + *p = 0; + rmdir (socket_name); + *p = '/'; + } + *socket_name = 0; + } +} + + +static RETSIGTYPE +cleanup_sh (int sig) +{ + if (caught_fatal_sig) + raise (sig); + caught_fatal_sig = 1; + + /* gcry_control( GCRYCTL_TERM_SECMEM );*/ + cleanup (); + +#ifndef HAVE_DOSISH_SYSTEM + { /* reset action to default action and raise signal again */ + struct sigaction nact; + nact.sa_handler = SIG_DFL; + sigemptyset( &nact.sa_mask ); + nact.sa_flags = 0; + sigaction( sig, &nact, NULL); + } +#endif + raise( sig ); +} + + +/* Handle options which are allowed to be reset after program start. + Return true when the current option in PARGS could be handled and + false if not. As a special feature, passing a value of NULL for + PARGS, resets the options to the default. */ +static int +parse_rereadable_options (ARGPARSE_ARGS *pargs) +{ + if (!pargs) + { /* reset mode */ + opt.quiet = 0; + opt.verbose = 0; + opt.debug = 0; + opt.no_grab = 0; + opt.pinentry_program = NULL; + opt.scdaemon_program = NULL; + opt.def_cache_ttl = 10*60; /* default to 10 minutes */ + opt.ignore_cache_for_signing = 0; + return 1; + } + + switch (pargs->r_opt) + { + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + + case oDebug: opt.debug |= pargs->r.ret_ulong; break; + case oDebugAll: opt.debug = ~0; break; + + case oNoGrab: opt.no_grab = 1; break; + + case oPinentryProgram: opt.pinentry_program = pargs->r.ret_str; break; + case oScdaemonProgram: opt.scdaemon_program = pargs->r.ret_str; break; + + case oDefCacheTTL: opt.def_cache_ttl = pargs->r.ret_ulong; break; + + case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break; + + default: + return 0; /* not handled */ + } + return 1; /* handled */ +} + + +int +main (int argc, char **argv ) +{ + ARGPARSE_ARGS pargs; + int orig_argc; + int may_coredump; + char **orig_argv; + FILE *configfp = NULL; + char *configname = NULL; + const char *shell; + unsigned configlineno; + int parse_debug = 0; + int default_config =1; + int greeting = 0; + int nogreeting = 0; + int pipe_server = 0; + int is_daemon = 0; + int nodetach = 0; + int csh_style = 0; + char *logfile = NULL; + int debug_wait = 0; + int disable_pth = 0; + + set_strusage (my_strusage); + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + /* Please note that we may running SUID(ROOT), so be very CAREFUL + when adding any stuff between here and the call to INIT_SECMEM() + somewhere after the option parsing */ + log_set_prefix ("gpg-agent", 1|4); + i18n_init (); + + /* We need to initialize Pth before libgcrypt, because the libgcrypt + initialization done by gcry_check_version internally sets up its + mutex system. Note that one must not link against pth if + USE_GNU_PTH is not defined. */ +#ifdef USE_GNU_PTH + if (!pth_init ()) + { + log_error ("failed to initialize the Pth library\n"); + exit (1); + } +#endif /*USE_GNU_PTH*/ + + /* check that the libraries are suitable. Do it here because + the option parsing may need services of the library */ + if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) ) + { + log_fatal( _("libgcrypt is too old (need %s, have %s)\n"), + NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) ); + } + + assuan_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free); + + gcry_set_log_handler (my_gcry_logger, NULL); + gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); + + may_coredump = disable_core_dumps (); + + parse_rereadable_options (NULL); /* Reset them to default values. */ + + shell = getenv ("SHELL"); + if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") ) + csh_style = 1; + + opt.homedir = getenv("GNUPGHOME"); + if (!opt.homedir || !*opt.homedir) + opt.homedir = GNUPG_DEFAULT_HOMEDIR; + + + /* check whether we have a config file on the commandline */ + orig_argc = argc; + orig_argv = argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */ + while (arg_parse( &pargs, opts)) + { + if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll) + parse_debug++; + else if (pargs.r_opt == oOptions) + { /* yes there is one, so we do not try the default one, but + read the option file when it is encountered at the + commandline */ + default_config = 0; + } + else if (pargs.r_opt == oNoOptions) + default_config = 0; /* --no-options */ + else if (pargs.r_opt == oHomedir) + opt.homedir = pargs.r.ret_str; + } + + /* initialize the secure memory. */ + gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); + maybe_setuid = 0; + + /* + Now we are now working under our real uid + */ + + + if (default_config) + configname = make_filename (opt.homedir, "gpg-agent.conf", NULL ); + + argc = orig_argc; + argv = orig_argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1; /* do not remove the args */ + next_pass: + if (configname) + { + configlineno = 0; + configfp = fopen (configname, "r"); + if (!configfp) + { + if (default_config) + { + if( parse_debug ) + log_info (_("NOTE: no default option file `%s'\n"), + configname ); + } + else + { + log_error (_("option file `%s': %s\n"), + configname, strerror(errno) ); + exit(2); + } + xfree (configname); + configname = NULL; + } + if (parse_debug && configname ) + log_info (_("reading options from `%s'\n"), configname ); + default_config = 0; + } + + while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) ) + { + if (parse_rereadable_options (&pargs)) + continue; /* Already handled */ + switch (pargs.r_opt) + { + case oBatch: opt.batch=1; break; + + case oDebugWait: debug_wait = pargs.r.ret_int; break; + + case oOptions: + /* config files may not be nested (silently ignore them) */ + if (!configfp) + { + xfree(configname); + configname = xstrdup(pargs.r.ret_str); + goto next_pass; + } + break; + case oNoGreeting: nogreeting = 1; break; + case oNoVerbose: opt.verbose = 0; break; + case oNoOptions: break; /* no-options */ + case oHomedir: opt.homedir = pargs.r.ret_str; break; + case oNoDetach: nodetach = 1; break; + case oLogFile: logfile = pargs.r.ret_str; break; + case oCsh: csh_style = 1; break; + case oSh: csh_style = 0; break; + case oServer: pipe_server = 1; break; + case oDaemon: is_daemon = 1; break; + case oDisablePth: disable_pth = 1; break; + + case oDisplay: default_display = xstrdup (pargs.r.ret_str); break; + case oTTYname: default_ttyname = xstrdup (pargs.r.ret_str); break; + case oTTYtype: default_ttytype = xstrdup (pargs.r.ret_str); break; + case oLCctype: default_lc_ctype = xstrdup (pargs.r.ret_str); break; + case oLCmessages: default_lc_messages = xstrdup (pargs.r.ret_str); break; + + case oKeepTTY: opt.keep_tty = 1; break; + case oKeepDISPLAY: opt.keep_display = 1; break; + + default : pargs.err = configfp? 1:2; break; + } + } + if (configfp) + { + fclose( configfp ); + configfp = NULL; + /* Keep a copy of the name so that it can be read on SIGHUP. */ + config_filename = configname; + configname = NULL; + goto next_pass; + } + xfree (configname); + configname = NULL; + if (log_get_errorcount(0)) + exit(2); + if (nogreeting ) + greeting = 0; + + if (greeting) + { + fprintf (stderr, "%s %s; %s\n", + strusage(11), strusage(13), strusage(14) ); + fprintf (stderr, "%s\n", strusage(15) ); + } +#ifdef IS_DEVELOPMENT_VERSION + log_info ("NOTE: this is a development version!\n"); +#endif + + + if (atexit (cleanup)) + { + log_error ("atexit failed\n"); + cleanup (); + exit (1); + } + + create_directories (); + + if (debug_wait && pipe_server) + { + log_debug ("waiting for debugger - my pid is %u .....\n", + (unsigned int)getpid()); + sleep (debug_wait); + log_debug ("... okay\n"); + } + + if (!pipe_server && !is_daemon) + log_info (_("please use the option `--daemon'" + " to run the program in the background\n")); + +#ifdef ENABLE_NLS + /* gpg-agent usdually does not ooutput any messages becuase it runs + in the background. For log files it is acceptable to have + messages always encoded in utf-8. We switch here to utf-8, so + that commands like --help still give native messages. It is far + easier to swicthnonly once instead of for every message and it + actually helps when more then one thread is active (avoids + required an extra copy step). */ + bind_textdomain_codeset (PACKAGE, "UTF-8"); +#endif + + /* now start with logging to a file if this is desired */ + if (logfile) + { + log_set_file (logfile); + log_set_prefix (NULL, 1|2|4); + } + + /* Make sure that we have a default ttyname. */ + if (!default_ttyname && ttyname (1)) + default_ttyname = xstrdup (ttyname (1)); + if (!default_ttytype && getenv ("TERM")) + default_ttytype = xstrdup (getenv ("TERM")); + + if (pipe_server) + { /* this is the simple pipe based server */ + start_command_handler (-1, -1); + } + else if (!is_daemon) + ; + else + { /* regular server mode */ + int fd; + pid_t pid; + int len; + struct sockaddr_un serv_addr; + char *p; + + /* Remove the DISPLAY variable so that a pinentry does not + default to a specific display. There is still a default + display when gpg-agent weas started using --display or a + client requested this using an OPTION command. */ + if (!opt.keep_display) + unsetenv ("DISPLAY"); + + *socket_name = 0; + snprintf (socket_name, DIM(socket_name)-1, + "/tmp/gpg-XXXXXX/S.gpg-agent"); + socket_name[DIM(socket_name)-1] = 0; + p = strrchr (socket_name, '/'); + if (!p) + BUG (); + *p = 0;; + if (!mkdtemp(socket_name)) + { + log_error ("can't create directory `%s': %s\n", + socket_name, strerror(errno) ); + exit (1); + } + *p = '/'; + + if (strchr (socket_name, ':') ) + { + log_error ("colons are not allowed in the socket name\n"); + exit (1); + } + if (strlen (socket_name)+1 >= sizeof serv_addr.sun_path ) + { + log_error ("name of socket too long\n"); + exit (1); + } + + + fd = socket (AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + { + log_error ("can't create socket: %s\n", strerror(errno) ); + exit (1); + } + + memset (&serv_addr, 0, sizeof serv_addr); + serv_addr.sun_family = AF_UNIX; + strcpy (serv_addr.sun_path, socket_name); + len = (offsetof (struct sockaddr_un, sun_path) + + strlen(serv_addr.sun_path) + 1); + + if (bind (fd, (struct sockaddr*)&serv_addr, len) == -1) + { + log_error ("error binding socket to `%s': %s\n", + serv_addr.sun_path, strerror (errno) ); + close (fd); + exit (1); + } + + if (listen (fd, 5 ) == -1) + { + log_error ("listen() failed: %s\n", strerror (errno)); + close (fd); + exit (1); + } + + if (opt.verbose) + log_info ("listening on socket `%s'\n", socket_name ); + + + fflush (NULL); + pid = fork (); + if (pid == (pid_t)-1) + { + log_fatal ("fork failed: %s\n", strerror (errno) ); + exit (1); + } + else if (pid) + { /* we are the parent */ + char *infostr; + + close (fd); + + /* create the info string: :: */ + if (asprintf (&infostr, "GPG_AGENT_INFO=%s:%lu:1", + socket_name, (ulong)pid ) < 0) + { + log_error ("out of core\n"); + kill (pid, SIGTERM); + exit (1); + } + *socket_name = 0; /* don't let cleanup() remove the socket - + the child should do this from now on */ + if (argc) + { /* run the program given on the commandline */ + if (putenv (infostr)) + { + log_error ("failed to set environment: %s\n", + strerror (errno) ); + kill (pid, SIGTERM ); + exit (1); + } + execvp (argv[0], argv); + log_error ("failed to run the command: %s\n", strerror (errno)); + kill (pid, SIGTERM); + exit (1); + } + else + { + /* print the environment string, so that the caller can use + shell's eval to set it */ + if (csh_style) + { + *strchr (infostr, '=') = ' '; + printf ( "setenv %s\n", infostr); + } + else + { + printf ( "%s; export GPG_AGENT_INFO;\n", infostr); + } + free (infostr); + exit (0); + } + /*NEVER REACHED*/ + } /* end parent */ + + + /* this is the child */ + + /* detach from tty and put process into a new session */ + if (!nodetach ) + { + int i; + + /* close stdin, stdout and stderr unless it is the log stream */ + for (i=0; i <= 2; i++) + { + if ( log_get_fd () != i) + close (i); + } + if (setsid() == -1) + { + log_error ("setsid() failed: %s\n", strerror(errno) ); + cleanup (); + exit (1); + } + opt.running_detached = 1; + } + + if (chdir("/")) + { + log_error ("chdir to / failed: %s\n", strerror (errno)); + exit (1); + } + + +#ifdef USE_GNU_PTH + if (!disable_pth) + { + struct sigaction sa; + + sa.sa_handler = SIG_IGN; + sigemptyset (&sa.sa_mask); + sa.sa_flags = 0; + sigaction (SIGPIPE, &sa, NULL); + handle_connections (fd); + } + else +#endif /*!USE_GNU_PTH*/ + /* setup signals */ + { + struct sigaction oact, nact; + + nact.sa_handler = cleanup_sh; + sigemptyset (&nact.sa_mask); + nact.sa_flags = 0; + + sigaction (SIGHUP, NULL, &oact); + if (oact.sa_handler != SIG_IGN) + sigaction (SIGHUP, &nact, NULL); + sigaction( SIGTERM, NULL, &oact ); + if (oact.sa_handler != SIG_IGN) + sigaction (SIGTERM, &nact, NULL); + nact.sa_handler = SIG_IGN; + sigaction (SIGPIPE, &nact, NULL); + sigaction (SIGINT, &nact, NULL); + + start_command_handler (fd, -1); + } + close (fd); + } + + return 0; +} + +void +agent_exit (int rc) +{ + /*FIXME: update_random_seed_file();*/ +#if 1 + /* at this time a bit annoying */ + if (opt.debug & DBG_MEMSTAT_VALUE) + { + gcry_control( GCRYCTL_DUMP_MEMORY_STATS ); + gcry_control( GCRYCTL_DUMP_RANDOM_STATS ); + } + if (opt.debug) + gcry_control (GCRYCTL_DUMP_SECMEM_STATS ); +#endif + gcry_control (GCRYCTL_TERM_SECMEM ); + rc = rc? rc : log_get_errorcount(0)? 2 : 0; + exit (rc); +} + + +void +agent_init_default_ctrl (struct server_control_s *ctrl) +{ + /* Note we ignore malloc errors because we can't do much about it + and the request will fail anyway shortly after this + initialization. */ + if (ctrl->display) + free (ctrl->display); + ctrl->display = default_display? strdup (default_display) : NULL; + + if (ctrl->ttyname) + free (ctrl->ttyname); + ctrl->ttyname = default_ttyname? strdup (default_ttyname) : NULL; + + if (ctrl->ttytype) + free (ctrl->ttytype); + ctrl->ttytype = default_ttytype? strdup (default_ttytype) : NULL; + + if (ctrl->lc_ctype) + free (ctrl->lc_ctype); + ctrl->lc_ctype = default_lc_ctype? strdup (default_lc_ctype) : NULL; + + if (ctrl->lc_messages) + free (ctrl->lc_messages); + ctrl->lc_messages = default_lc_messages? strdup (default_lc_messages) : NULL; +} + + +/* Reread parts of the configuration. Note, that this function is + obviously not thread-safe and should only be called from the PTH + signal handler. + + Fixme: Due to the way the argument parsing works, we create a + memory leak here for all string type arguments. There is currently + no clean way to tell whether the memory for the argument has been + allocated or points into the process' original arguments. Unless + we have a mechanism to tell this, we need to live on with this. */ +static void +reread_configuration (void) +{ + ARGPARSE_ARGS pargs; + FILE *fp; + unsigned int configlineno = 0; + int dummy; + + if (!config_filename) + return; /* No config file. */ + + fp = fopen (config_filename, "r"); + if (!fp) + { + log_error (_("option file `%s': %s\n"), + config_filename, strerror(errno) ); + return; + } + + parse_rereadable_options (NULL); /* Start from the default values. */ + + memset (&pargs, 0, sizeof pargs); + dummy = 0; + pargs.argc = &dummy; + pargs.flags = 1; /* do not remove the args */ + while (optfile_parse (fp, config_filename, &configlineno, &pargs, opts) ) + { + if (pargs.r_opt < -1) + pargs.err = 1; /* Print a warning. */ + else /* Try to parse this option - ignore unchangeable ones. */ + parse_rereadable_options (&pargs); + } + fclose (fp); +} + + +static void +create_private_keys_directory (const char *home) +{ + char *fname; + struct stat statbuf; + + fname = make_filename (home, GNUPG_PRIVATE_KEYS_DIR, NULL); + if (stat (fname, &statbuf) && errno == ENOENT) + { + if (mkdir (fname, S_IRUSR|S_IWUSR|S_IXUSR )) + log_error (_("can't create directory `%s': %s\n"), + fname, strerror(errno) ); + else if (!opt.quiet) + log_info (_("directory `%s' created\n"), fname); + } + xfree (fname); +} + +/* Create the directory only if the supplied directory name is the + same as the default one. This way we avoid to create arbitrary + directories when a non-default home directory is used. To cope + with HOME, we compare only the suffix if we see that the default + homedir does start with a tilde. We don't stop here in case of + problems because other functions will throw an error anyway.*/ +static void +create_directories (void) +{ + struct stat statbuf; + const char *defhome = GNUPG_DEFAULT_HOMEDIR; + char *home; + + home = make_filename (opt.homedir, NULL); + if ( stat (home, &statbuf) ) + { + if (errno == ENOENT) + { + if ( (*defhome == '~' + && (strlen (home) >= strlen (defhome+1) + && !strcmp (home + strlen(home) + - strlen (defhome+1), defhome+1))) + || (*defhome != '~' && !strcmp (home, defhome) ) + ) + { + if (mkdir (home, S_IRUSR|S_IWUSR|S_IXUSR )) + log_error (_("can't create directory `%s': %s\n"), + home, strerror(errno) ); + else + { + if (!opt.quiet) + log_info (_("directory `%s' created\n"), home); + create_private_keys_directory (home); + } + } + } + else + log_error ("error stat-ing `%s': %s\n", home, strerror (errno)); + } + else if ( !S_ISDIR(statbuf.st_mode)) + { + log_error ("can't use `%s' as home directory\n", home); + } + else /* exists and is a directory. */ + { + create_private_keys_directory (home); + } + xfree (home); +} + + + +#ifdef USE_GNU_PTH +static void +handle_signal (int signo) +{ + switch (signo) + { + case SIGHUP: + log_info ("SIGHUP received - " + "re-reading configuration and flushing cache\n"); + agent_flush_cache (); + reread_configuration (); + break; + + case SIGUSR1: + if (opt.verbose < 5) + opt.verbose++; + log_info ("SIGUSR1 received - verbosity set to %d\n", opt.verbose); + break; + + case SIGUSR2: + if (opt.verbose) + opt.verbose--; + log_info ("SIGUSR2 received - verbosity set to %d\n", opt.verbose ); + break; + + case SIGTERM: + if (!shutdown_pending) + log_info ("SIGTERM received - shutting down ...\n"); + else + log_info ("SIGTERM received - still %ld running threads\n", + pth_ctrl( PTH_CTRL_GETTHREADS )); + shutdown_pending++; + if (shutdown_pending > 2) + { + log_info ("shutdown forced\n"); + log_info ("%s %s stopped\n", strusage(11), strusage(13) ); + cleanup (); + agent_exit (0); + } + break; + + case SIGINT: + log_info ("SIGINT received - immediate shutdown\n"); + log_info( "%s %s stopped\n", strusage(11), strusage(13)); + cleanup (); + agent_exit (0); + break; + + default: + log_info ("signal %d received - no action defined\n", signo); + } +} + + +static void * +start_connection_thread (void *arg) +{ + int fd = (int)arg; + + if (opt.verbose) + log_info ("handler for fd %d started\n", fd); + start_command_handler (-1, fd); + if (opt.verbose) + log_info ("handler for fd %d terminated\n", fd); + + return NULL; +} + + +static void +handle_connections (int listen_fd) +{ + pth_attr_t tattr; + pth_event_t ev; + sigset_t sigs; + int signo; + struct sockaddr_un paddr; + socklen_t plen = sizeof( paddr ); + int fd; + + tattr = pth_attr_new(); + pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0); + pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 32*1024); + pth_attr_set (tattr, PTH_ATTR_NAME, "gpg-agent"); + + sigemptyset (&sigs ); + sigaddset (&sigs, SIGHUP); + sigaddset (&sigs, SIGUSR1); + sigaddset (&sigs, SIGUSR2); + sigaddset (&sigs, SIGINT); + sigaddset (&sigs, SIGTERM); + ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); + + for (;;) + { + if (shutdown_pending) + { + if (pth_ctrl (PTH_CTRL_GETTHREADS) == 1) + break; /* ready */ + + /* Do not accept anymore connections and wait for existing + connections to terminate */ + signo = 0; + pth_wait (ev); + if (pth_event_occurred (ev) && signo) + handle_signal (signo); + continue; + } + + fd = pth_accept_ev (listen_fd, (struct sockaddr *)&paddr, &plen, ev); + if (fd == -1) + { +#ifdef PTH_STATUS_OCCURRED /* This is Pth 2 */ + if (pth_event_status (ev) == PTH_STATUS_OCCURRED) +#else + if (pth_event_occurred (ev)) +#endif + { + handle_signal (signo); + continue; + } + log_error ("accept failed: %s - waiting 1s\n", strerror (errno)); + pth_sleep(1); + continue; + } + + if (!pth_spawn (tattr, start_connection_thread, (void*)fd)) + { + log_error ("error spawning connection handler: %s\n", + strerror (errno) ); + close (fd); + } + } + + pth_event_free (ev, PTH_FREE_ALL); + cleanup (); + log_info ("%s %s stopped\n", strusage(11), strusage(13)); +} +#endif /*USE_GNU_PTH*/ diff --git a/agent/learncard.c b/agent/learncard.c new file mode 100644 index 000000000..28a74f972 --- /dev/null +++ b/agent/learncard.c @@ -0,0 +1,448 @@ +/* learncard.c - Handle the LEARN command + * Copyright (C) 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "agent.h" +#include + +struct keypair_info_s { + struct keypair_info_s *next; + int no_cert; + char *id; /* points into grip */ + char hexgrip[1]; +}; +typedef struct keypair_info_s *KEYPAIR_INFO; + +struct kpinfo_cb_parm_s { + int error; + KEYPAIR_INFO info; +}; + + +struct certinfo_s { + struct certinfo_s *next; + int type; + int done; + char id[1]; +}; +typedef struct certinfo_s *CERTINFO; + +struct certinfo_cb_parm_s { + int error; + CERTINFO info; +}; + + +struct sinfo_s { + struct sinfo_s *next; + char *data; /* Points into keyword. */ + char keyword[1]; +}; +typedef struct sinfo_s *SINFO; + +struct sinfo_cb_parm_s { + int error;; + SINFO info; +}; + + + +static void +release_keypair_info (KEYPAIR_INFO info) +{ + while (info) + { + KEYPAIR_INFO tmp = info->next; + xfree (info); + info = tmp; + } +} + +static void +release_certinfo (CERTINFO info) +{ + while (info) + { + CERTINFO tmp = info->next; + xfree (info); + info = tmp; + } +} + +static void +release_sinfo (SINFO info) +{ + while (info) + { + SINFO tmp = info->next; + xfree (info); + info = tmp; + } +} + + + +/* This callback is used by agent_card_learn and passed the content of + all KEYPAIRINFO lines. It merely stores this data away */ +static void +kpinfo_cb (void *opaque, const char *line) +{ + struct kpinfo_cb_parm_s *parm = opaque; + KEYPAIR_INFO item; + char *p; + + if (parm->error) + return; /* no need to gather data after an error coccured */ + item = xtrycalloc (1, sizeof *item + strlen (line)); + if (!item) + { + parm->error = out_of_core (); + return; + } + strcpy (item->hexgrip, line); + for (p = item->hexgrip; hexdigitp (p); p++) + ; + if (p == item->hexgrip && *p == 'X' && spacep (p+1)) + { + item->no_cert = 1; + p++; + } + else if ((p - item->hexgrip) != 40 || !spacep (p)) + { /* not a 20 byte hex keygrip or not followed by a space */ + parm->error = gpg_error (GPG_ERR_INV_RESPONSE); + xfree (item); + return; + } + *p++ = 0; + while (spacep (p)) + p++; + item->id = p; + while (*p && !spacep (p)) + p++; + if (p == item->id) + { /* invalid ID string */ + parm->error = gpg_error (GPG_ERR_INV_RESPONSE); + xfree (item); + return; + } + *p = 0; /* ignore trailing stuff */ + + /* store it */ + item->next = parm->info; + parm->info = item; +} + + +/* This callback is used by agent_card_learn and passed the content of + all CERTINFO lines. It merely stores this data away */ +static void +certinfo_cb (void *opaque, const char *line) +{ + struct certinfo_cb_parm_s *parm = opaque; + CERTINFO item; + int type; + char *p, *pend; + + if (parm->error) + return; /* no need to gather data after an error coccured */ + + type = strtol (line, &p, 10); + while (spacep (p)) + p++; + for (pend = p; *pend && !spacep (pend); pend++) + ; + if (p == pend || !*p) + { + parm->error = gpg_error (GPG_ERR_INV_RESPONSE); + return; + } + *pend = 0; /* ignore trailing stuff */ + + item = xtrycalloc (1, sizeof *item + strlen (p)); + if (!item) + { + parm->error = out_of_core (); + return; + } + item->type = type; + strcpy (item->id, p); + /* store it */ + item->next = parm->info; + parm->info = item; +} + + +/* This callback is used by agent_card_learn and passed the content of + all SINFO lines. It merely stores this data away */ +static void +sinfo_cb (void *opaque, const char *keyword, size_t keywordlen, + const char *data) +{ + struct sinfo_cb_parm_s *sparm = opaque; + SINFO item; + + if (sparm->error) + return; /* no need to gather data after an error coccured */ + + item = xtrycalloc (1, sizeof *item + keywordlen + 1 + strlen (data)); + if (!item) + { + sparm->error = out_of_core (); + return; + } + memcpy (item->keyword, keyword, keywordlen); + item->data = item->keyword + keywordlen; + *item->data = 0; + item->data++; + strcpy (item->data, data); + /* store it */ + item->next = sparm->info; + sparm->info = item; +} + + +/* Create an S-expression with the shadow info. */ +static unsigned char * +make_shadow_info (const char *serialno, const char *idstring) +{ + const char *s; + unsigned char *info, *p; + char numbuf[21]; + int n; + + for (s=serialno, n=0; *s && s[1]; s += 2) + n++; + + info = p = xtrymalloc (1 + 21 + n + + 21 + strlen (idstring) + 1 + 1); + *p++ = '('; + sprintf (numbuf, "%d:", n); + p = stpcpy (p, numbuf); + for (s=serialno; *s && s[1]; s += 2) + *p++ = xtoi_2 (s); + sprintf (numbuf, "%d:", strlen (idstring)); + p = stpcpy (p, numbuf); + p = stpcpy (p, idstring); + *p++ = ')'; + *p = 0; + return info; +} + +static int +send_cert_back (const char *id, void *assuan_context) +{ + int rc; + char *derbuf; + size_t derbuflen; + + rc = agent_card_readcert (id, &derbuf, &derbuflen); + if (rc) + { + log_error ("error reading certificate: %s\n", + gpg_strerror (rc)); + return rc; + } + + rc = assuan_send_data (assuan_context, derbuf, derbuflen); + xfree (derbuf); + if (!rc) + rc = assuan_send_data (assuan_context, NULL, 0); + if (!rc) + rc = assuan_write_line (assuan_context, "END"); + if (rc) + { + log_error ("sending certificate failed: %s\n", + assuan_strerror (rc)); + return map_assuan_err (rc); + } + return 0; +} + +/* Perform the learn operation. If ASSUAN_CONTEXT is not NULL all new + certificates are send via Assuan */ +int +agent_handle_learn (void *assuan_context) +{ + int rc; + struct kpinfo_cb_parm_s parm; + struct certinfo_cb_parm_s cparm; + struct sinfo_cb_parm_s sparm; + char *serialno = NULL; + KEYPAIR_INFO item; + SINFO sitem; + unsigned char grip[20]; + char *p; + int i; + static int certtype_list[] = { + 101, /* trusted */ + 102, /* useful */ + 100, /* regular */ + -1 /* end of list */ + }; + + + memset (&parm, 0, sizeof parm); + memset (&cparm, 0, sizeof cparm); + memset (&sparm, 0, sizeof sparm); + + /* Check whether a card is present and get the serial number */ + rc = agent_card_serialno (&serialno); + if (rc) + goto leave; + + /* now gather all the available info */ + rc = agent_card_learn (kpinfo_cb, &parm, certinfo_cb, &cparm, + sinfo_cb, &sparm); + if (!rc && (parm.error || cparm.error || sparm.error)) + rc = parm.error? parm.error : cparm.error? cparm.error : sparm.error; + if (rc) + { + log_debug ("agent_card_learn failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + log_info ("card has S/N: %s\n", serialno); + + /* Pass on all the collected status information. */ + if (assuan_context) + { + for (sitem = sparm.info; sitem; sitem = sitem->next) + { + assuan_write_status (assuan_context, sitem->keyword, sitem->data); + } + } + + /* Write out the certificates in a standard order. */ + for (i=0; certtype_list[i] != -1; i++) + { + CERTINFO citem; + for (citem = cparm.info; citem; citem = citem->next) + { + if (certtype_list[i] != citem->type) + continue; + + if (opt.verbose) + log_info (" id: %s (type=%d)\n", + citem->id, citem->type); + + if (assuan_context) + { + rc = send_cert_back (citem->id, assuan_context); + if (rc) + goto leave; + citem->done = 1; + } + } + } + + for (item = parm.info; item; item = item->next) + { + unsigned char *pubkey, *shdkey; + size_t n; + + if (opt.verbose) + log_info (" id: %s (grip=%s)\n", item->id, item->hexgrip); + + if (item->no_cert) + continue; /* no public key yet available */ + + for (p=item->hexgrip, i=0; i < 20; p += 2, i++) + grip[i] = xtoi_2 (p); + + if (!agent_key_available (grip)) + continue; + + /* unknown - store it */ + rc = agent_card_readkey (item->id, &pubkey); + if (rc) + { + log_debug ("agent_card_readkey failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + { + unsigned char *shadow_info = make_shadow_info (serialno, item->id); + if (!shadow_info) + { + rc = gpg_error (GPG_ERR_ENOMEM); + xfree (pubkey); + goto leave; + } + rc = agent_shadow_key (pubkey, shadow_info, &shdkey); + xfree (shadow_info); + } + xfree (pubkey); + if (rc) + { + log_error ("shadowing the key failed: %s\n", gpg_strerror (rc)); + goto leave; + } + n = gcry_sexp_canon_len (shdkey, 0, NULL, NULL); + assert (n); + + rc = agent_write_private_key (grip, shdkey, n, 0); + xfree (shdkey); + if (rc) + { + log_error ("error writing key: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (opt.verbose) + log_info ("stored\n"); + + if (assuan_context) + { + CERTINFO citem; + + /* only send the certificate if we have not done so before */ + for (citem = cparm.info; citem; citem = citem->next) + { + if (!strcmp (citem->id, item->id)) + break; + } + if (!citem) + { + rc = send_cert_back (item->id, assuan_context); + if (rc) + goto leave; + } + } + } + + + leave: + xfree (serialno); + release_keypair_info (parm.info); + release_certinfo (cparm.info); + release_sinfo (sparm.info); + return rc; +} + + diff --git a/agent/minip12.c b/agent/minip12.c new file mode 100644 index 000000000..255fef096 --- /dev/null +++ b/agent/minip12.c @@ -0,0 +1,1140 @@ +/* minip12.c - A minimal pkcs-12 implementation. + * Copyright (C) 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include + +#undef TEST + +#ifdef TEST +#include +#include +#include +#endif + +#include "../jnlib/logging.h" +#include "minip12.h" + +#ifndef DIM +#define DIM(v) (sizeof(v)/sizeof((v)[0])) +#endif + +enum +{ + UNIVERSAL = 0, + APPLICATION = 1, + CONTEXT = 2, + PRIVATE = 3 +}; + + +enum +{ + TAG_NONE = 0, + TAG_BOOLEAN = 1, + TAG_INTEGER = 2, + TAG_BIT_STRING = 3, + TAG_OCTET_STRING = 4, + TAG_NULL = 5, + TAG_OBJECT_ID = 6, + TAG_OBJECT_DESCRIPTOR = 7, + TAG_EXTERNAL = 8, + TAG_REAL = 9, + TAG_ENUMERATED = 10, + TAG_EMBEDDED_PDV = 11, + TAG_UTF8_STRING = 12, + TAG_REALTIVE_OID = 13, + TAG_SEQUENCE = 16, + TAG_SET = 17, + TAG_NUMERIC_STRING = 18, + TAG_PRINTABLE_STRING = 19, + TAG_TELETEX_STRING = 20, + TAG_VIDEOTEX_STRING = 21, + TAG_IA5_STRING = 22, + TAG_UTC_TIME = 23, + TAG_GENERALIZED_TIME = 24, + TAG_GRAPHIC_STRING = 25, + TAG_VISIBLE_STRING = 26, + TAG_GENERAL_STRING = 27, + TAG_UNIVERSAL_STRING = 28, + TAG_CHARACTER_STRING = 29, + TAG_BMP_STRING = 30 +}; + + +static unsigned char const oid_data[9] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01 }; +static unsigned char const oid_encryptedData[9] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x06 }; +static unsigned char const oid_pkcs_12_pkcs_8ShroudedKeyBag[11] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x02 }; +static unsigned char const oid_pbeWithSHAAnd3_KeyTripleDES_CBC[10] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03 }; + +static unsigned char const oid_rsaEncryption[9] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }; + + +static unsigned char const data_3desiter1024[30] = { + 0x30, 0x1C, 0x06, 0x0A, 0x2A, 0x86, 0x48, 0x86, + 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03, 0x30, 0x0E, + 0x04, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x02, 0x02, 0x04, 0x00 }; +#define DATA_3DESITER1024_SALT_OFF 18 + + +struct buffer_s +{ + unsigned char *buffer; + size_t length; +}; + + +struct tag_info +{ + int class; + int is_constructed; + unsigned long tag; + unsigned long length; /* length part of the TLV */ + int nhdr; + int ndef; /* It is an indefinite length */ +}; + + +/* Parse the buffer at the address BUFFER which is of SIZE and return + the tag and the length part from the TLV triplet. Update BUFFER + and SIZE on success. */ +static int +parse_tag (unsigned char const **buffer, size_t *size, struct tag_info *ti) +{ + int c; + unsigned long tag; + const unsigned char *buf = *buffer; + size_t length = *size; + + ti->length = 0; + ti->ndef = 0; + ti->nhdr = 0; + + /* Get the tag */ + if (!length) + return -1; /* premature eof */ + c = *buf++; length--; + ti->nhdr++; + + ti->class = (c & 0xc0) >> 6; + ti->is_constructed = !!(c & 0x20); + tag = c & 0x1f; + + if (tag == 0x1f) + { + tag = 0; + do + { + tag <<= 7; + if (!length) + return -1; /* premature eof */ + c = *buf++; length--; + ti->nhdr++; + tag |= c & 0x7f; + } + while (c & 0x80); + } + ti->tag = tag; + + /* Get the length */ + if (!length) + return -1; /* prematureeof */ + c = *buf++; length--; + ti->nhdr++; + + if ( !(c & 0x80) ) + ti->length = c; + else if (c == 0x80) + ti->ndef = 1; + else if (c == 0xff) + return -1; /* forbidden length value */ + else + { + unsigned long len = 0; + int count = c & 0x7f; + + for (; count; count--) + { + len <<= 8; + if (!length) + return -1; /* premature_eof */ + c = *buf++; length--; + ti->nhdr++; + len |= c & 0xff; + } + ti->length = len; + } + + if (ti->class == UNIVERSAL && !ti->tag) + ti->length = 0; + + if (ti->length > length) + return -1; /* data larger than buffer. */ + + *buffer = buf; + *size = length; + return 0; +} + + +static int +string_to_key (int id, char *salt, int iter, const char *pw, + int req_keylen, unsigned char *keybuf) +{ + int rc, i, j; + gcry_md_hd_t md; + gcry_mpi_t num_b1 = NULL; + int pwlen; + unsigned char hash[20], buf_b[64], buf_i[128], *p; + size_t cur_keylen; + size_t n; + + cur_keylen = 0; + pwlen = strlen (pw); + if (pwlen > 63/2) + { + log_error ("password too long\n"); + return -1; + } + + /* Store salt and password in BUF_I */ + p = buf_i; + for(i=0; i < 64; i++) + *p++ = salt [i%8]; + for(i=j=0; i < 64; i += 2) + { + *p++ = 0; + *p++ = pw[j]; + if (++j > pwlen) /* Note, that we include the trailing zero */ + j = 0; + } + + for (;;) + { + rc = gcry_md_open (&md, GCRY_MD_SHA1, 0); + if (rc) + { + log_error ( "gcry_md_open failed: %s\n", gpg_strerror (rc)); + return rc; + } + for(i=0; i < 64; i++) + gcry_md_putc (md, id); + gcry_md_write (md, buf_i, 128); + memcpy (hash, gcry_md_read (md, 0), 20); + gcry_md_close (md); + for (i=1; i < iter; i++) + gcry_md_hash_buffer (GCRY_MD_SHA1, hash, hash, 20); + + for (i=0; i < 20 && cur_keylen < req_keylen; i++) + keybuf[cur_keylen++] = hash[i]; + if (cur_keylen == req_keylen) + { + gcry_mpi_release (num_b1); + return 0; /* ready */ + } + + /* need more bytes. */ + for(i=0; i < 64; i++) + buf_b[i] = hash[i % 20]; + rc = gcry_mpi_scan (&num_b1, GCRYMPI_FMT_USG, buf_b, 64, &n); + if (rc) + { + log_error ( "gcry_mpi_scan failed: %s\n", gpg_strerror (rc)); + return -1; + } + gcry_mpi_add_ui (num_b1, num_b1, 1); + for (i=0; i < 128; i += 64) + { + gcry_mpi_t num_ij; + + rc = gcry_mpi_scan (&num_ij, GCRYMPI_FMT_USG, buf_i + i, 64, &n); + if (rc) + { + log_error ( "gcry_mpi_scan failed: %s\n", + gpg_strerror (rc)); + return -1; + } + gcry_mpi_add (num_ij, num_ij, num_b1); + gcry_mpi_clear_highbit (num_ij, 64*8); + rc = gcry_mpi_print (GCRYMPI_FMT_USG, buf_i + i, 64, &n, num_ij); + if (rc) + { + log_error ( "gcry_mpi_print failed: %s\n", + gpg_strerror (rc)); + return -1; + } + gcry_mpi_release (num_ij); + } + } +} + + +static int +set_key_iv (gcry_cipher_hd_t chd, char *salt, int iter, const char *pw) +{ + unsigned char keybuf[24]; + int rc; + + if (string_to_key (1, salt, iter, pw, 24, keybuf)) + return -1; + rc = gcry_cipher_setkey (chd, keybuf, 24); + if (rc) + { + log_error ( "gcry_cipher_setkey failed: %s\n", gpg_strerror (rc)); + return -1; + } + + if (string_to_key (2, salt, iter, pw, 8, keybuf)) + return -1; + rc = gcry_cipher_setiv (chd, keybuf, 8); + if (rc) + { + log_error ("gcry_cipher_setiv failed: %s\n", gpg_strerror (rc)); + return -1; + } + return 0; +} + + +static void +crypt_block (unsigned char *buffer, size_t length, char *salt, int iter, + const char *pw, int encrypt) +{ + gcry_cipher_hd_t chd; + int rc; + + rc = gcry_cipher_open (&chd, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_CBC, 0); + if (rc) + { + log_error ( "gcry_cipher_open failed: %s\n", gpg_strerror(-1)); + return; + } + if (set_key_iv (chd, salt, iter, pw)) + goto leave; + + rc = encrypt? gcry_cipher_encrypt (chd, buffer, length, NULL, 0) + : gcry_cipher_decrypt (chd, buffer, length, NULL, 0); + + if (rc) + { + log_error ( "en/de-crytion failed: %s\n", gpg_strerror (rc)); + goto leave; + } + +/* { */ +/* FILE *fp = fopen("inner.der", "wb"); */ +/* fwrite (buffer, 1, length, fp); */ +/* fclose (fp); */ +/* } */ + + leave: + gcry_cipher_close (chd); +} + + + + +static int +parse_bag_encrypted_data (const unsigned char *buffer, size_t length, + int startoffset) +{ + struct tag_info ti; + const unsigned char *p = buffer; + size_t n = length; + const char *where; + + where = "start"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != CONTEXT || ti.tag) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_SEQUENCE) + goto bailout; + + where = "bag.encryptedData.version"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_INTEGER || ti.length != 1 || *p != 0) + goto bailout; + p++; n--; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_SEQUENCE) + goto bailout; + + where = "bag.encryptedData.data"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_OBJECT_ID || ti.length != DIM(oid_data) + || memcmp (p, oid_data, DIM(oid_data))) + goto bailout; + p += DIM(oid_data); + n -= DIM(oid_data); + + /* fixme: continue parsing */ + + return 0; + bailout: + log_error ("encrptedData error at \"%s\", offset %u\n", + where, (p - buffer)+startoffset); + return -1; +} + +static gcry_mpi_t * +parse_bag_data (const unsigned char *buffer, size_t length, int startoffset, + const char *pw) +{ + int rc; + struct tag_info ti; + const unsigned char *p = buffer; + size_t n = length; + const char *where; + char salt[8]; + unsigned int iter; + int len; + unsigned char *plain = NULL; + gcry_mpi_t *result = NULL; + int result_count, i; + + where = "start"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != CONTEXT || ti.tag) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OCTET_STRING) + goto bailout; + + where = "data.outerseqs"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + + where = "data.objectidentifier"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OBJECT_ID + || ti.length != DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag) + || memcmp (p, oid_pkcs_12_pkcs_8ShroudedKeyBag, + DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag))) + goto bailout; + p += DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag); + n -= DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag); + + where = "shrouded,outerseqs"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != CONTEXT || ti.tag) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OBJECT_ID + || ti.length != DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC) + || memcmp (p, oid_pbeWithSHAAnd3_KeyTripleDES_CBC, + DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC))) + goto bailout; + p += DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC); + n -= DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC); + + where = "3des-params"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OCTET_STRING || ti.length != 8 ) + goto bailout; + memcpy (salt, p, 8); + p += 8; + n -= 8; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_INTEGER || !ti.length ) + goto bailout; + for (iter=0; ti.length; ti.length--) + { + iter <<= 8; + iter |= (*p++) & 0xff; + n--; + } + + where = "3des-ciphertext"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OCTET_STRING || !ti.length ) + goto bailout; + + log_info ("%lu bytes of 3DES encrypted text\n", ti.length); + + plain = gcry_malloc_secure (ti.length); + if (!plain) + { + log_error ("error allocating decryption buffer\n"); + goto bailout; + } + memcpy (plain, p, ti.length); + crypt_block (plain, ti.length, salt, iter, pw, 0); + n = ti.length; + startoffset = 0; + buffer = p = plain; + + where = "decrypted-text"; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER + || ti.length != 1 || *p) + goto bailout; + p++; n--; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + len = ti.length; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (len < ti.nhdr) + goto bailout; + len -= ti.nhdr; + if (ti.class || ti.tag != TAG_OBJECT_ID + || ti.length != DIM(oid_rsaEncryption) + || memcmp (p, oid_rsaEncryption, + DIM(oid_rsaEncryption))) + goto bailout; + p += DIM (oid_rsaEncryption); + n -= DIM (oid_rsaEncryption); + if (len < ti.length) + goto bailout; + len -= ti.length; + if (n < len) + goto bailout; + p += len; + n -= len; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_OCTET_STRING) + goto bailout; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + len = ti.length; + + result = gcry_calloc (10, sizeof *result); + if (!result) + { + log_error ( "error allocating result array\n"); + goto bailout; + } + result_count = 0; + + where = "reading.key-parameters"; + for (result_count=0; len && result_count < 9;) + { + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER) + goto bailout; + if (len < ti.nhdr) + goto bailout; + len -= ti.nhdr; + if (len < ti.length) + goto bailout; + len -= ti.length; + if (!result_count && ti.length == 1 && !*p) + ; /* ignore the very first one if it is a 0 */ + else + { + rc = gcry_mpi_scan (result+result_count, GCRYMPI_FMT_USG, p, + ti.length, NULL); + if (rc) + { + log_error ("error parsing key parameter: %s\n", + gpg_strerror (rc)); + goto bailout; + } + result_count++; + } + p += ti.length; + n -= ti.length; + } + if (len) + goto bailout; + + return result; + + bailout: + gcry_free (plain); + if (result) + { + for (i=0; result[i]; i++) + gcry_mpi_release (result[i]); + gcry_free (result); + } + log_error ( "data error at \"%s\", offset %u\n", + where, (p - buffer) + startoffset); + return NULL; +} + + +/* Parse a PKCS12 object and return an array of MPI representing the + secret key parameters. This is a very limited inplementation in + that it is only able to look for 3DES encoded enctyptedData and + tries to extract the first private key object it finds. In case of + an error NULL is returned. */ +gcry_mpi_t * +p12_parse (const unsigned char *buffer, size_t length, const char *pw) +{ + struct tag_info ti; + const unsigned char *p = buffer; + size_t n = length; + const char *where; + int bagseqlength, len; + + where = "pfx"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_SEQUENCE) + goto bailout; + + where = "pfxVersion"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_INTEGER || ti.length != 1 || *p != 3) + goto bailout; + p++; n--; + + where = "authSave"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_OBJECT_ID || ti.length != DIM(oid_data) + || memcmp (p, oid_data, DIM(oid_data))) + goto bailout; + p += DIM(oid_data); + n -= DIM(oid_data); + + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != CONTEXT || ti.tag) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != UNIVERSAL || ti.tag != TAG_OCTET_STRING) + goto bailout; + + where = "bags"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != UNIVERSAL || ti.tag != TAG_SEQUENCE) + goto bailout; + bagseqlength = ti.length; + while (bagseqlength) + { + /*log_debug ( "at offset %u\n", (p - buffer));*/ + where = "bag-sequence"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != UNIVERSAL || ti.tag != TAG_SEQUENCE) + goto bailout; + + if (bagseqlength < ti.nhdr) + goto bailout; + bagseqlength -= ti.nhdr; + if (bagseqlength < ti.length) + goto bailout; + bagseqlength -= ti.length; + len = ti.length; + + if (parse_tag (&p, &n, &ti)) + goto bailout; + len -= ti.nhdr; + if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_encryptedData) + && !memcmp (p, oid_encryptedData, DIM(oid_encryptedData))) + { + p += DIM(oid_encryptedData); + n -= DIM(oid_encryptedData); + len -= DIM(oid_encryptedData); + where = "bag.encryptedData"; + if (parse_bag_encrypted_data (p, n, (p - buffer))) + goto bailout; + } + else if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_data) + && !memcmp (p, oid_data, DIM(oid_data))) + { + p += DIM(oid_data); + n -= DIM(oid_data); + len -= DIM(oid_data); + return parse_bag_data (p, n, (p-buffer), pw); + } + else + log_info ( "unknown bag type - skipped\n"); + + if (len < 0 || len > n) + goto bailout; + p += len; + n -= len; + } + + return NULL; + bailout: + log_error ("error at \"%s\", offset %u\n", where, (p - buffer)); + return NULL; +} + + + +static size_t +compute_tag_length (size_t n) +{ + int needed = 0; + + if (n < 128) + needed += 2; /* tag and one length byte */ + else if (n < 256) + needed += 3; /* tag, number of length bytes, 1 length byte */ + else if (n < 65536) + needed += 4; /* tag, number of length bytes, 2 length bytes */ + else + { + log_error ("object too larger to encode\n"); + return 0; + } + return needed; +} + +static unsigned char * +store_tag_length (unsigned char *p, int tag, size_t n) +{ + if (tag == TAG_SEQUENCE) + tag |= 0x20; /* constructed */ + + *p++ = tag; + if (n < 128) + *p++ = n; + else if (n < 256) + { + *p++ = 0x81; + *p++ = n; + } + else if (n < 65536) + { + *p++ = 0x82; + *p++ = n >> 8; + *p++ = n; + } + + return p; +} + + +/* Create the final PKCS-12 object from the sequences contained in + SEQLIST. That array is terminated with an NULL object */ +static unsigned char * +create_final (struct buffer_s *sequences, size_t *r_length) +{ + int i; + size_t needed = 0; + size_t n, outseqlen, notsooutseqlen, out0taglen, octstrlen, inseqlen; + unsigned char *result, *p; + size_t resultlen; + + for (i=0; sequences[i].buffer; i++) + needed += sequences[i].length; + /* This goes into a sequences. */ + inseqlen = needed; + n = compute_tag_length (needed); + needed += n; + /* And encapsulate all in an octet string. */ + octstrlen = needed; + n = compute_tag_length (needed); + needed += n; + /* And tag it with [0]. */ + out0taglen = needed; + n = compute_tag_length (needed); + needed += n; + /* Prepend an data OID. */ + needed += 2 + DIM (oid_data); + /* This all into a sequences. */ + notsooutseqlen = needed; + n = compute_tag_length (needed); + needed += n; + /* Prepend the version integer 3. */ + needed += 3; + /* And the final sequence. */ + outseqlen = needed; + n = compute_tag_length (needed); + needed += n; + + result = gcry_malloc (needed); + if (!result) + { + log_error ("error allocating buffer\n"); + return NULL; + } + p = result; + + /* Store the very outer sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, outseqlen); + /* Store the version integer 3. */ + *p++ = TAG_INTEGER; + *p++ = 1; + *p++ = 3; + /* Store another sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, notsooutseqlen); + /* Store the data OID. */ + p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data)); + memcpy (p, oid_data, DIM (oid_data)); + p += DIM (oid_data); + /* Next comes a context tag. */ + p = store_tag_length (p, 0xa0, out0taglen); + /* And an octet string. */ + p = store_tag_length (p, TAG_OCTET_STRING, octstrlen); + /* And the inner sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, inseqlen); + /* And append all the buffers. */ + for (i=0; sequences[i].buffer; i++) + { + memcpy (p, sequences[i].buffer, sequences[i].length); + p += sequences[i].length; + } + + /* Ready. */ + resultlen = p - result; + if (needed != resultlen) + log_debug ("length mismatch: %u, %u\n", needed, resultlen); + + *r_length = resultlen; + return result; +} + + +/* Expect the RSA key parameters in KPARMS and a password in + PW. Create a PKCS structure from it and return it as well as the + length in R_LENGTH; return NULL in case of an error. */ +unsigned char * +p12_build (gcry_mpi_t *kparms, const char *pw, size_t *r_length) +{ + int rc, i; + size_t needed, n; + unsigned char *plain, *p, *cipher; + size_t plainlen, cipherlen; + size_t outseqlen, oidseqlen, octstrlen, inseqlen; + size_t out0taglen, in0taglen, outoctstrlen; + size_t aseq1len, aseq2len, aseq3len; + char salt[8]; + + needed = 3; /* The version(?) integer of value 0. */ + for (i=0; kparms[i]; i++) + { + n = 0; + rc = gcry_mpi_print (GCRYMPI_FMT_STD, NULL, 0, &n, kparms[i]); + if (rc) + { + log_error ("error formatting parameter: %s\n", gpg_strerror (rc)); + return NULL; + } + needed += n; + n = compute_tag_length (n); + if (!n) + return NULL; + needed += n; + } + if (i != 8) + { + log_error ("invalid paramters for p12_build\n"); + return NULL; + } + /* Now this all goes into a sequence. */ + inseqlen = needed; + n = compute_tag_length (needed); + if (!n) + return NULL; + needed += n; + /* Encapsulate all into an octet string. */ + octstrlen = needed; + n = compute_tag_length (needed); + if (!n) + return NULL; + needed += n; + /* Prepend the object identifier sequence. */ + oidseqlen = 2 + DIM (oid_rsaEncryption) + 2; + needed += 2 + oidseqlen; + /* The version number. */ + needed += 3; + /* And finally put the whole thing into a sequence. */ + outseqlen = needed; + n = compute_tag_length (needed); + if (!n) + return NULL; + needed += n; + + /* allocate 8 extra bytes for padding */ + plain = gcry_malloc_secure (needed+8); + if (!plain) + { + log_error ("error allocating encryption buffer\n"); + return NULL; + } + + /* And now fill the plaintext buffer. */ + p = plain; + p = store_tag_length (p, TAG_SEQUENCE, outseqlen); + /* Store version. */ + *p++ = TAG_INTEGER; + *p++ = 1; + *p++ = 0; + /* Store object identifier sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, oidseqlen); + p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_rsaEncryption)); + memcpy (p, oid_rsaEncryption, DIM (oid_rsaEncryption)); + p += DIM (oid_rsaEncryption); + *p++ = TAG_NULL; + *p++ = 0; + /* Start with the octet string. */ + p = store_tag_length (p, TAG_OCTET_STRING, octstrlen); + p = store_tag_length (p, TAG_SEQUENCE, inseqlen); + /* Store the key parameters. */ + *p++ = TAG_INTEGER; + *p++ = 1; + *p++ = 0; + for (i=0; kparms[i]; i++) + { + n = 0; + rc = gcry_mpi_print (GCRYMPI_FMT_STD, NULL, 0, &n, kparms[i]); + if (rc) + { + log_error ("oops: error formatting parameter: %s\n", + gpg_strerror (rc)); + gcry_free (plain); + return NULL; + } + p = store_tag_length (p, TAG_INTEGER, n); + + n = plain + needed - p; + rc = gcry_mpi_print (GCRYMPI_FMT_STD, p, n, &n, kparms[i]); + if (rc) + { + log_error ("oops: error storing parameter: %s\n", + gpg_strerror (rc)); + gcry_free (plain); + return NULL; + } + p += n; + } + + plainlen = p - plain; + assert (needed == plainlen); + /* Append some pad characters; we already allocated extra space. */ + n = 8 - plainlen % 8; + for (;(plainlen % 8); plainlen++) + *p++ = n; + + { + FILE *fp = fopen("inner-out.der", "wb"); + fwrite (plain, 1, plainlen, fp); + fclose (fp); + } + + + /* Encrypt it and prepend a lot of stupid things. */ + gcry_randomize (salt, 8, GCRY_STRONG_RANDOM); + crypt_block (plain, plainlen, salt, 1024, pw, 1); + /* the data goes into an octet string. */ + needed = compute_tag_length (plainlen); + needed += plainlen; + /* we prepend the the algorithm identifier (we use a pre-encoded one)*/ + needed += DIM (data_3desiter1024); + /* we put a sequence around. */ + aseq3len = needed; + needed += compute_tag_length (needed); + /* Prepend it with a [0] tag. */ + in0taglen = needed; + needed += compute_tag_length (needed); + /* Prepend that shroudedKeyBag OID. */ + needed += 2 + DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag); + /* Put it all into two sequence. */ + aseq2len = needed; + needed += compute_tag_length ( needed); + aseq1len = needed; + needed += compute_tag_length (needed); + /* This all goes into an octet string. */ + outoctstrlen = needed; + needed += compute_tag_length (needed); + /* Prepend it with a [0] tag. */ + out0taglen = needed; + needed += compute_tag_length (needed); + /* Prepend the data OID. */ + needed += 2 + DIM (oid_data); + /* And a sequence. */ + outseqlen = needed; + needed += compute_tag_length (needed); + + cipher = gcry_malloc (needed); + if (!cipher) + { + log_error ("error allocating buffer\n"); + gcry_free (plain); + return NULL; + } + p = cipher; + /* Store the first sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, outseqlen); + /* Store the data OID. */ + p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data)); + memcpy (p, oid_data, DIM (oid_data)); + p += DIM (oid_data); + /* Next comes a context tag. */ + p = store_tag_length (p, 0xa0, out0taglen); + /* And an octet string. */ + p = store_tag_length (p, TAG_OCTET_STRING, outoctstrlen); + /* Two sequences. */ + p = store_tag_length (p, TAG_SEQUENCE, aseq1len); + p = store_tag_length (p, TAG_SEQUENCE, aseq2len); + /* Store the shroudedKeyBag OID. */ + p = store_tag_length (p, TAG_OBJECT_ID, + DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag)); + memcpy (p, oid_pkcs_12_pkcs_8ShroudedKeyBag, + DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag)); + p += DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag); + /* Next comes a context tag. */ + p = store_tag_length (p, 0xa0, in0taglen); + /* And a sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, aseq3len); + /* Now for the pre-encoded algorithm indentifier and the salt. */ + memcpy (p, data_3desiter1024, DIM (data_3desiter1024)); + memcpy (p + DATA_3DESITER1024_SALT_OFF, salt, 8); + p += DIM (data_3desiter1024); + /* And finally the octet string with the encrypted data. */ + p = store_tag_length (p, TAG_OCTET_STRING, plainlen); + memcpy (p, plain, plainlen); + p += plainlen; + cipherlen = p - cipher; + + if (needed != cipherlen) + log_debug ("length mismatch: %u, %u\n", needed, cipherlen); + gcry_free (plain); + + { + struct buffer_s seqlist[2]; + + seqlist[0].buffer = cipher; + seqlist[0].length = cipherlen; + seqlist[1].buffer = NULL; + seqlist[1].length = 0; + + cipher = create_final (seqlist, &cipherlen); + gcry_free (seqlist[0].buffer); + } + + *r_length = cipherlen; + return cipher; +} + + +#ifdef TEST +int +main (int argc, char **argv) +{ + FILE *fp; + struct stat st; + char *buf; + size_t buflen; + GcryMPI *result; + + if (argc != 3) + { + fprintf (stderr, "usage: testp12 file passphrase\n"); + return 1; + } + + gcry_control (GCRYCTL_DISABLE_SECMEM, NULL); + gcry_control (GCRYCTL_INITIALIZATION_FINISHED, NULL); + + fp = fopen (argv[1], "rb"); + if (!fp) + { + fprintf (stderr, "can't open `%s': %s\n", argv[1], strerror (errno)); + return 1; + } + + if (fstat (fileno(fp), &st)) + { + fprintf (stderr, "can't stat `%s': %s\n", argv[1], strerror (errno)); + return 1; + } + + buflen = st.st_size; + buf = gcry_malloc (buflen+1); + if (!buf || fread (buf, buflen, 1, fp) != 1) + { + fprintf (stderr, "error reading `%s': %s\n", argv[1], strerror (errno)); + return 1; + } + fclose (fp); + + result = p12_parse (buf, buflen, argv[2]); + if (result) + { + int i, rc; + char *buf; + + for (i=0; result[i]; i++) + { + rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, (void**)&buf, + NULL, result[i]); + if (rc) + printf ("%d: [error printing number: %s]\n", + i, gpg_strerror (rc)); + else + { + printf ("%d: %s\n", i, buf); + gcry_free (buf); + } + } + } + + return 0; + +} +#endif /* TEST */ diff --git a/agent/minip12.h b/agent/minip12.h new file mode 100644 index 000000000..122215549 --- /dev/null +++ b/agent/minip12.h @@ -0,0 +1,33 @@ +/* minip12.h - Global definitions for the minimal pkcs-12 implementation. + * Copyright (C) 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef MINIP12_H +#define MINIP12_H + +#include + +gcry_mpi_t *p12_parse (const unsigned char *buffer, size_t length, + const char *pw); + +unsigned char *p12_build (gcry_mpi_t *kparms, const char *pw, + size_t *r_length); + + +#endif /*MINIP12_H*/ diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c new file mode 100644 index 000000000..543a82737 --- /dev/null +++ b/agent/pkdecrypt.c @@ -0,0 +1,138 @@ +/* pkdecrypt.c - public key decryption (well, acually using a secret key) + * Copyright (C) 2001, 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "agent.h" + + +/* DECRYPT the stuff in ciphertext which is expected to be a S-Exp. + Try to get the key from CTRL and write the decoded stuff back to + OUTFP. */ +int +agent_pkdecrypt (CTRL ctrl, const char *ciphertext, size_t ciphertextlen, + FILE *outfp) +{ + gcry_sexp_t s_skey = NULL, s_cipher = NULL, s_plain = NULL; + unsigned char *shadow_info = NULL; + int rc; + char *buf = NULL; + size_t len; + + if (!ctrl->have_keygrip) + { + log_error ("speculative decryption not yet supported\n"); + rc = gpg_error (GPG_ERR_NO_SECKEY); + goto leave; + } + + rc = gcry_sexp_sscan (&s_cipher, NULL, ciphertext, ciphertextlen); + if (rc) + { + log_error ("failed to convert ciphertext: %s\n", gpg_strerror (rc)); + rc = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + + if (DBG_CRYPTO) + { + log_printhex ("keygrip:", ctrl->keygrip, 20); + log_printhex ("cipher: ", ciphertext, ciphertextlen); + } + s_skey = agent_key_from_file (ctrl, ctrl->keygrip, &shadow_info, 0); + if (!s_skey && !shadow_info) + { + log_error ("failed to read the secret key\n"); + rc = gpg_error (GPG_ERR_NO_SECKEY); + goto leave; + } + + if (!s_skey) + { /* divert operation to the smartcard */ + + if (!gcry_sexp_canon_len (ciphertext, ciphertextlen, NULL, NULL)) + { + rc = gpg_error (GPG_ERR_INV_SEXP); + goto leave; + } + + rc = divert_pkdecrypt (ctrl, ciphertext, shadow_info, &buf, &len ); + if (rc) + { + log_error ("smartcard decryption failed: %s\n", gpg_strerror (rc)); + goto leave; + } + /* FIXME: don't use buffering and change the protocol to return + a complete S-expression and not just a part. */ + fprintf (outfp, "%u:", (unsigned int)len); + fwrite (buf, 1, len, outfp); + putc (0, outfp); + } + else + { /* no smartcard, but a private key */ + if (DBG_CRYPTO) + { + log_debug ("skey: "); + gcry_sexp_dump (s_skey); + } + + rc = gcry_pk_decrypt (&s_plain, s_cipher, s_skey); + if (rc) + { + log_error ("decryption failed: %s\n", gpg_strerror (rc)); + rc = map_gcry_err (rc); + goto leave; + } + + if (DBG_CRYPTO) + { + log_debug ("plain: "); + gcry_sexp_dump (s_plain); + } + len = gcry_sexp_sprint (s_plain, GCRYSEXP_FMT_CANON, NULL, 0); + assert (len); + buf = xmalloc (len); + len = gcry_sexp_sprint (s_plain, GCRYSEXP_FMT_CANON, buf, len); + assert (len); + /* FIXME: we must make sure that no buffering takes place or we are + in full control of the buffer memory (easy to do) - should go + into assuan. */ + fwrite (buf, 1, len, outfp); + } + + + leave: + gcry_sexp_release (s_skey); + gcry_sexp_release (s_plain); + gcry_sexp_release (s_cipher); + xfree (buf); + xfree (shadow_info); + return rc; +} + + diff --git a/agent/pksign.c b/agent/pksign.c new file mode 100644 index 000000000..fba2c652c --- /dev/null +++ b/agent/pksign.c @@ -0,0 +1,185 @@ +/* pksign.c - public key signing (well, acually using a secret key) + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "agent.h" + + +static int +do_encode_md (const unsigned char *digest, size_t digestlen, int algo, + unsigned int nbits, gcry_mpi_t *r_val) +{ + int nframe = (nbits+7) / 8; + byte *frame; + int i, n; + byte asn[100]; + size_t asnlen; + + asnlen = DIM(asn); + if (gcry_md_algo_info (algo, GCRYCTL_GET_ASNOID, asn, &asnlen)) + { + log_error ("no object identifier for algo %d\n", algo); + return gpg_error (GPG_ERR_INTERNAL); + } + + if (digestlen + asnlen + 4 > nframe ) + { + log_error ("can't encode a %d bit MD into a %d bits frame\n", + (int)(digestlen*8), (int)nbits); + return gpg_error (GPG_ERR_INTERNAL); + } + + /* We encode the MD in this way: + * + * 0 1 PAD(n bytes) 0 ASN(asnlen bytes) MD(len bytes) + * + * PAD consists of FF bytes. + */ + frame = xtrymalloc (nframe); + if (!frame) + return out_of_core (); + n = 0; + frame[n++] = 0; + frame[n++] = 1; /* block type */ + i = nframe - digestlen - asnlen -3 ; + assert ( i > 1 ); + memset ( frame+n, 0xff, i ); n += i; + frame[n++] = 0; + memcpy ( frame+n, asn, asnlen ); n += asnlen; + memcpy ( frame+n, digest, digestlen ); n += digestlen; + assert ( n == nframe ); + if (DBG_CRYPTO) + log_printhex ("encoded hash:", frame, nframe); + + gcry_mpi_scan (r_val, GCRYMPI_FMT_USG, frame, n, &nframe); + xfree (frame); + return 0; +} + + +/* SIGN whatever information we have accumulated in CTRL and write it + back to OUTFP. */ +int +agent_pksign (CTRL ctrl, FILE *outfp, int ignore_cache) +{ + gcry_sexp_t s_skey = NULL, s_hash = NULL, s_sig = NULL; + gcry_mpi_t frame = NULL; + unsigned char *shadow_info = NULL; + int rc; + char *buf = NULL; + size_t len; + + if (!ctrl->have_keygrip) + return gpg_error (GPG_ERR_NO_SECKEY); + + s_skey = agent_key_from_file (ctrl, + ctrl->keygrip, &shadow_info, ignore_cache); + if (!s_skey && !shadow_info) + { + log_error ("failed to read the secret key\n"); + rc = gpg_error (GPG_ERR_NO_SECKEY); + goto leave; + } + + if (!s_skey) + { /* divert operation to the smartcard */ + unsigned char *sigbuf; + + rc = divert_pksign (ctrl, + ctrl->digest.value, + ctrl->digest.valuelen, + ctrl->digest.algo, + shadow_info, &sigbuf); + if (rc) + { + log_error ("smartcard signing failed: %s\n", gpg_strerror (rc)); + goto leave; + } + len = gcry_sexp_canon_len (sigbuf, 0, NULL, NULL); + assert (len); + buf = sigbuf; + } + else + { /* no smartcard, but a private key */ + + /* put the hash into a sexp */ + rc = do_encode_md (ctrl->digest.value, + ctrl->digest.valuelen, + ctrl->digest.algo, + gcry_pk_get_nbits (s_skey), + &frame); + if (rc) + goto leave; + if ( gcry_sexp_build (&s_hash, NULL, "%m", frame) ) + BUG (); + + if (DBG_CRYPTO) + { + log_debug ("skey: "); + gcry_sexp_dump (s_skey); + } + + /* sign */ + rc = gcry_pk_sign (&s_sig, s_hash, s_skey); + if (rc) + { + log_error ("signing failed: %s\n", gpg_strerror (rc)); + rc = map_gcry_err (rc); + goto leave; + } + + if (DBG_CRYPTO) + { + log_debug ("result: "); + gcry_sexp_dump (s_sig); + } + + len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, NULL, 0); + assert (len); + buf = xmalloc (len); + len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, buf, len); + assert (len); + } + + /* FIXME: we must make sure that no buffering takes place or we are + in full control of the buffer memory (easy to do) - should go + into assuan. */ + fwrite (buf, 1, len, outfp); + + leave: + gcry_sexp_release (s_skey); + gcry_sexp_release (s_hash); + gcry_sexp_release (s_sig); + gcry_mpi_release (frame); + xfree (buf); + xfree (shadow_info); + return rc; +} + + diff --git a/agent/protect-tool.c b/agent/protect-tool.c new file mode 100644 index 000000000..e518c5672 --- /dev/null +++ b/agent/protect-tool.c @@ -0,0 +1,977 @@ +/* protect-tool.c - A tool to test the secret key protection + * Copyright (C) 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define JNLIB_NEED_LOG_LOGV +#include "agent.h" +#include "minip12.h" +#include "simple-pwquery.h" +#include "i18n.h" + +enum cmd_and_opt_values +{ aNull = 0, + oVerbose = 'v', + oArmor = 'a', + oPassphrase = 'P', + + oProtect = 'p', + oUnprotect = 'u', + + oNoVerbose = 500, + oShadow, + oShowShadowInfo, + oShowKeygrip, + + oP12Import, + oP12Export, + oStore, + oForce, + +aTest }; + +struct rsa_secret_key_s + { + gcry_mpi_t n; /* public modulus */ + gcry_mpi_t e; /* public exponent */ + gcry_mpi_t d; /* exponent */ + gcry_mpi_t p; /* prime p. */ + gcry_mpi_t q; /* prime q. */ + gcry_mpi_t u; /* inverse of p mod q. */ + }; + + +static int opt_armor; +static int opt_store; +static int opt_force; +static const char *passphrase; + +static const char *get_passphrase (void); +static int store_private_key (const unsigned char *grip, + const void *buffer, size_t length, int force); + + +static ARGPARSE_OPTS opts[] = { + + { 301, NULL, 0, N_("@Options:\n ") }, + + { oVerbose, "verbose", 0, "verbose" }, + { oArmor, "armor", 0, "write output in advanced format" }, + { oPassphrase, "passphrase", 2, "|STRING|use passphrase STRING" }, + { oProtect, "protect", 256, "protect a private key"}, + { oUnprotect, "unprotect", 256, "unprotect a private key"}, + { oShadow, "shadow", 256, "create a shadow entry for a priblic key"}, + { oShowShadowInfo, "show-shadow-info", 256, "return the shadow info"}, + { oShowKeygrip, "show-keygrip", 256, "show the \"keygrip\""}, + + { oP12Import, "p12-import", 256, "import a PKCS-12 encoded private key"}, + { oP12Export, "p12-export", 256, "export a private key PKCS-12 encoded"}, + { oStore, "store", 0, "store the created key in the appropriate place"}, + { oForce, "force", 0, "force overwriting"}, + {0} +}; + +static const char * +my_strusage (int level) +{ + const char *p; + switch (level) + { + case 11: p = "gpg-protect-tool (GnuPG)"; + break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n"); + break; + case 1: + case 40: p = _("Usage: gpg-protect-tool [options] (-h for help)\n"); + break; + case 41: p = _("Syntax: gpg-protect-tool [options] [args]]\n" + "Secret key maintenance tool\n"); + break; + + default: p = NULL; + } + return p; +} + + + +static void +i18n_init (void) +{ +#ifdef USE_SIMPLE_GETTEXT + set_gettext_file( PACKAGE ); +#else +#ifdef ENABLE_NLS + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); +#endif +#endif +} + + + +/* Used by gcry for logging */ +static void +my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr) +{ + /* translate the log levels */ + switch (level) + { + case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break; + case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break; + case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break; + case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break; + case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break; + case GCRY_LOG_BUG: level = JNLIB_LOG_BUG; break; + case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break; + default: level = JNLIB_LOG_ERROR; break; } + log_logv (level, fmt, arg_ptr); +} + + +/* static void */ +/* print_mpi (const char *text, gcry_mpi_t a) */ +/* { */ +/* char *buf; */ +/* void *bufaddr = &buf; */ +/* int rc; */ + +/* rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, bufaddr, NULL, a); */ +/* if (rc) */ +/* log_info ("%s: [error printing number: %s]\n", text, gpg_strerror (rc)); */ +/* else */ +/* { */ +/* log_info ("%s: %s\n", text, buf); */ +/* gcry_free (buf); */ +/* } */ +/* } */ + + + +static unsigned char * +make_canonical (const char *fname, const char *buf, size_t buflen) +{ + int rc; + size_t erroff, len; + gcry_sexp_t sexp; + unsigned char *result; + + rc = gcry_sexp_sscan (&sexp, &erroff, buf, buflen); + if (rc) + { + log_error ("invalid S-Expression in `%s' (off=%u): %s\n", + fname, (unsigned int)erroff, gpg_strerror (rc)); + return NULL; + } + len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0); + assert (len); + result = xmalloc (len); + len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, result, len); + assert (len); + gcry_sexp_release (sexp); + return result; +} + +static char * +make_advanced (const unsigned char *buf, size_t buflen) +{ + int rc; + size_t erroff, len; + gcry_sexp_t sexp; + unsigned char *result; + + rc = gcry_sexp_sscan (&sexp, &erroff, buf, buflen); + if (rc) + { + log_error ("invalid canonical S-Expression (off=%u): %s\n", + (unsigned int)erroff, gpg_strerror (rc)); + return NULL; + } + len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0); + assert (len); + result = xmalloc (len); + len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len); + assert (len); + gcry_sexp_release (sexp); + return result; +} + + +static char * +read_file (const char *fname, size_t *r_length) +{ + FILE *fp; + struct stat st; + char *buf; + size_t buflen; + + fp = fopen (fname, "rb"); + if (!fp) + { + log_error ("can't open `%s': %s\n", fname, strerror (errno)); + return NULL; + } + + if (fstat (fileno(fp), &st)) + { + log_error ("can't stat `%s': %s\n", fname, strerror (errno)); + fclose (fp); + return NULL; + } + + buflen = st.st_size; + buf = xmalloc (buflen+1); + if (fread (buf, buflen, 1, fp) != 1) + { + log_error ("error reading `%s': %s\n", fname, strerror (errno)); + fclose (fp); + xfree (buf); + return NULL; + } + fclose (fp); + + *r_length = buflen; + return buf; +} + + +static unsigned char * +read_key (const char *fname) +{ + char *buf; + size_t buflen; + unsigned char *key; + + buf = read_file (fname, &buflen); + if (!buf) + return NULL; + key = make_canonical (fname, buf, buflen); + xfree (buf); + return key; +} + + + +static void +read_and_protect (const char *fname) +{ + int rc; + unsigned char *key; + unsigned char *result; + size_t resultlen; + + key = read_key (fname); + if (!key) + return; + + rc = agent_protect (key, get_passphrase (), &result, &resultlen); + xfree (key); + if (rc) + { + log_error ("protecting the key failed: %s\n", gpg_strerror (rc)); + return; + } + + if (opt_armor) + { + char *p = make_advanced (result, resultlen); + xfree (result); + if (!p) + return; + result = p; + resultlen = strlen (p); + } + + fwrite (result, resultlen, 1, stdout); + xfree (result); +} + + +static void +read_and_unprotect (const char *fname) +{ + int rc; + unsigned char *key; + unsigned char *result; + size_t resultlen; + + key = read_key (fname); + if (!key) + return; + + rc = agent_unprotect (key, get_passphrase (), &result, &resultlen); + xfree (key); + if (rc) + { + log_error ("unprotecting the key failed: %s\n", gpg_strerror (rc)); + return; + } + + if (opt_armor) + { + char *p = make_advanced (result, resultlen); + xfree (result); + if (!p) + return; + result = p; + resultlen = strlen (p); + } + + fwrite (result, resultlen, 1, stdout); + xfree (result); +} + + + +static void +read_and_shadow (const char *fname) +{ + int rc; + unsigned char *key; + unsigned char *result; + size_t resultlen; + + key = read_key (fname); + if (!key) + return; + + rc = agent_shadow_key (key, "(8:313233342:43)", &result); + xfree (key); + if (rc) + { + log_error ("shadowing the key failed: %s\n", gpg_strerror (rc)); + return; + } + resultlen = gcry_sexp_canon_len (result, 0, NULL,NULL); + assert (resultlen); + + if (opt_armor) + { + char *p = make_advanced (result, resultlen); + xfree (result); + if (!p) + return; + result = p; + resultlen = strlen (p); + } + + fwrite (result, resultlen, 1, stdout); + xfree (result); +} + +static void +show_shadow_info (const char *fname) +{ + int rc; + unsigned char *key; + const unsigned char *info; + size_t infolen; + + key = read_key (fname); + if (!key) + return; + + rc = agent_get_shadow_info (key, &info); + xfree (key); + if (rc) + { + log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc)); + return; + } + infolen = gcry_sexp_canon_len (info, 0, NULL,NULL); + assert (infolen); + + if (opt_armor) + { + char *p = make_advanced (info, infolen); + if (!p) + return; + fwrite (p, strlen (p), 1, stdout); + xfree (p); + } + else + fwrite (info, infolen, 1, stdout); +} + + +static void +show_file (const char *fname) +{ + unsigned char *key; + size_t keylen; + char *p; + + key = read_key (fname); + if (!key) + return; + + keylen = gcry_sexp_canon_len (key, 0, NULL,NULL); + assert (keylen); + + p = make_advanced (key, keylen); + xfree (key); + if (p) + { + fwrite (p, strlen (p), 1, stdout); + xfree (p); + } +} + +static void +show_keygrip (const char *fname) +{ + unsigned char *key; + gcry_sexp_t private; + unsigned char grip[20]; + int i; + + key = read_key (fname); + if (!key) + return; + + if (gcry_sexp_new (&private, key, 0, 0)) + { + log_error ("gcry_sexp_new failed\n"); + return; + } + xfree (key); + + if (!gcry_pk_get_keygrip (private, grip)) + { + log_error ("can't calculate keygrip\n"); + return; + } + gcry_sexp_release (private); + + for (i=0; i < 20; i++) + printf ("%02X", grip[i]); + putchar ('\n'); +} + + +static int +rsa_key_check (struct rsa_secret_key_s *skey) +{ + int err = 0; + gcry_mpi_t t = gcry_mpi_snew (0); + gcry_mpi_t t1 = gcry_mpi_snew (0); + gcry_mpi_t t2 = gcry_mpi_snew (0); + gcry_mpi_t phi = gcry_mpi_snew (0); + + /* check that n == p * q */ + gcry_mpi_mul (t, skey->p, skey->q); + if (gcry_mpi_cmp( t, skey->n) ) + { + log_error ("RSA oops: n != p * q\n"); + err++; + } + + /* check that p is less than q */ + if (gcry_mpi_cmp (skey->p, skey->q) > 0) + { + gcry_mpi_t tmp; + + log_info ("swapping secret primes\n"); + tmp = gcry_mpi_copy (skey->p); + gcry_mpi_set (skey->p, skey->q); + gcry_mpi_set (skey->q, tmp); + gcry_mpi_release (tmp); + /* and must recompute u of course */ + gcry_mpi_invm (skey->u, skey->p, skey->q); + } + + /* check that e divides neither p-1 nor q-1 */ + gcry_mpi_sub_ui (t, skey->p, 1 ); + gcry_mpi_div (NULL, t, t, skey->e, 0); + if (!gcry_mpi_cmp_ui( t, 0) ) + { + log_error ("RSA oops: e divides p-1\n"); + err++; + } + gcry_mpi_sub_ui (t, skey->q, 1); + gcry_mpi_div (NULL, t, t, skey->e, 0); + if (!gcry_mpi_cmp_ui( t, 0)) + { + log_info ( "RSA oops: e divides q-1\n" ); + err++; + } + + /* check that d is correct. */ + gcry_mpi_sub_ui (t1, skey->p, 1); + gcry_mpi_sub_ui (t2, skey->q, 1); + gcry_mpi_mul (phi, t1, t2); + gcry_mpi_invm (t, skey->e, phi); + if (gcry_mpi_cmp (t, skey->d)) + { /* no: try universal exponent. */ + gcry_mpi_gcd (t, t1, t2); + gcry_mpi_div (t, NULL, phi, t, 0); + gcry_mpi_invm (t, skey->e, t); + if (gcry_mpi_cmp (t, skey->d)) + { + log_error ("RSA oops: bad secret exponent\n"); + err++; + } + } + + /* check for correctness of u */ + gcry_mpi_invm (t, skey->p, skey->q); + if (gcry_mpi_cmp (t, skey->u)) + { + log_info ( "RSA oops: bad u parameter\n"); + err++; + } + + if (err) + log_info ("RSA secret key check failed\n"); + + gcry_mpi_release (t); + gcry_mpi_release (t1); + gcry_mpi_release (t2); + gcry_mpi_release (phi); + + return err? -1:0; +} + + +static void +import_p12_file (const char *fname) +{ + char *buf; + unsigned char *result; + size_t buflen, resultlen; + int i; + int rc; + gcry_mpi_t *kparms; + struct rsa_secret_key_s sk; + gcry_sexp_t s_key; + unsigned char *key; + unsigned char grip[20]; + + /* fixme: we should release some stuff on error */ + + buf = read_file (fname, &buflen); + if (!buf) + return; + + kparms = p12_parse (buf, buflen, get_passphrase ()); + xfree (buf); + if (!kparms) + { + log_error ("error parsing or decrypting the PKCS-1 file\n"); + return; + } + for (i=0; kparms[i]; i++) + ; + if (i != 8) + { + log_error ("invalid structure of private key\n"); + return; + } + + +/* print_mpi (" n", kparms[0]); */ +/* print_mpi (" e", kparms[1]); */ +/* print_mpi (" d", kparms[2]); */ +/* print_mpi (" p", kparms[3]); */ +/* print_mpi (" q", kparms[4]); */ +/* print_mpi ("dmp1", kparms[5]); */ +/* print_mpi ("dmq1", kparms[6]); */ +/* print_mpi (" u", kparms[7]); */ + + sk.n = kparms[0]; + sk.e = kparms[1]; + sk.d = kparms[2]; + sk.q = kparms[3]; + sk.p = kparms[4]; + sk.u = kparms[7]; + if (rsa_key_check (&sk)) + return; +/* print_mpi (" n", sk.n); */ +/* print_mpi (" e", sk.e); */ +/* print_mpi (" d", sk.d); */ +/* print_mpi (" p", sk.p); */ +/* print_mpi (" q", sk.q); */ +/* print_mpi (" u", sk.u); */ + + /* Create an S-expresion from the parameters. */ + rc = gcry_sexp_build (&s_key, NULL, + "(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))", + sk.n, sk.e, sk.d, sk.p, sk.q, sk.u, NULL); + for (i=0; i < 8; i++) + gcry_mpi_release (kparms[i]); + gcry_free (kparms); + if (rc) + { + log_error ("failed to created S-expression from key: %s\n", + gpg_strerror (rc)); + return; + } + + /* Compute the keygrip. */ + if (!gcry_pk_get_keygrip (s_key, grip)) + { + log_error ("can't calculate keygrip\n"); + return; + } + log_info ("keygrip: "); + for (i=0; i < 20; i++) + log_printf ("%02X", grip[i]); + log_printf ("\n"); + + /* convert to canonical encoding */ + buflen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_CANON, NULL, 0); + assert (buflen); + key = gcry_xmalloc_secure (buflen); + buflen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_CANON, key, buflen); + assert (buflen); + gcry_sexp_release (s_key); + + + rc = agent_protect (key, get_passphrase (), &result, &resultlen); + xfree (key); + if (rc) + { + log_error ("protecting the key failed: %s\n", gpg_strerror (rc)); + return; + } + + if (opt_armor) + { + char *p = make_advanced (result, resultlen); + xfree (result); + if (!p) + return; + result = p; + resultlen = strlen (p); + } + + if (opt_store) + store_private_key (grip, result, resultlen, opt_force); + else + fwrite (result, resultlen, 1, stdout); + + xfree (result); +} + + + +static gcry_mpi_t * +sexp_to_kparms (gcry_sexp_t sexp) +{ + gcry_sexp_t list, l2; + const char *name; + const char *s; + size_t n; + int i, idx; + const char *elems; + gcry_mpi_t *array; + + list = gcry_sexp_find_token (sexp, "private-key", 0 ); + if(!list) + return NULL; + l2 = gcry_sexp_cadr (list); + gcry_sexp_release (list); + list = l2; + name = gcry_sexp_nth_data (list, 0, &n); + if(!name || n != 3 || memcmp (name, "rsa", 3)) + { + gcry_sexp_release (list); + return NULL; + } + + /* Parameter names used with RSA. */ + elems = "nedpqu"; + array = xcalloc (strlen(elems) + 1, sizeof *array); + for (idx=0, s=elems; *s; s++, idx++ ) + { + l2 = gcry_sexp_find_token (list, s, 1); + if (!l2) + { + for (i=0; i +#include +#include +#include +#include +#include +#include +#include +#include + +#include "agent.h" + +#include "sexp-parse.h" + +#define PROT_CIPHER GCRY_CIPHER_AES +#define PROT_CIPHER_STRING "aes" +#define PROT_CIPHER_KEYLEN (128/8) + + +/* A table containing the information needed to create a protected + private key */ +static struct { + const char *algo; + const char *parmlist; + int prot_from, prot_to; +} protect_info[] = { + { "rsa", "nedpqu", 2, 5 }, + { NULL } +}; + + +static int +hash_passphrase (const char *passphrase, int hashalgo, + int s2kmode, + const unsigned char *s2ksalt, unsigned long s2kcount, + unsigned char *key, size_t keylen); + + + +/* Calculate the MIC for a private key S-Exp. SHA1HASH should pint to + a 20 byte buffer. This function is suitable for any algorithms. */ +static int +calculate_mic (const unsigned char *plainkey, unsigned char *sha1hash) +{ + const unsigned char *hash_begin, *hash_end; + const unsigned char *s; + size_t n; + + s = plainkey; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "private-key")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + hash_begin = s; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s += n; /* skip over the algorithm name */ + + while (*s == '(') + { + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s += n; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s += n; + if ( *s != ')' ) + return gpg_error (GPG_ERR_INV_SEXP); + s++; + } + if (*s != ')') + return gpg_error (GPG_ERR_INV_SEXP); + s++; + hash_end = s; + + gcry_md_hash_buffer (GCRY_MD_SHA1, sha1hash, + hash_begin, hash_end - hash_begin); + + return 0; +} + + + +/* Encrypt the parameter block starting at PROTBEGIN with length + PROTLEN using the utf8 encoded key PASSPHRASE and return the entire + encrypted block in RESULT or ereturn with an error code. SHA1HASH + is the 20 byte SHA-1 hash required for the integrity code. + + The parameter block is expected to be an incomplete S-Expression of + the form (example in advanced format): + + (d #046129F..[some bytes not shown]..81#) + (p #00e861b..[some bytes not shown]..f1#) + (q #00f7a7c..[some bytes not shown]..61#) + (u #304559a..[some bytes not shown]..9b#) + + the returned block is the S-Expression: + + (protected mode (parms) encrypted_octet_string) + +*/ +static int +do_encryption (const char *protbegin, size_t protlen, + const char *passphrase, const unsigned char *sha1hash, + unsigned char **result, size_t *resultlen) +{ + gcry_cipher_hd_t hd; + const char *modestr = "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc"; + int blklen, enclen, outlen; + char *iv = NULL; + int rc; + char *outbuf = NULL; + char *p; + int saltpos, ivpos, encpos; + + rc = gcry_cipher_open (&hd, PROT_CIPHER, GCRY_CIPHER_MODE_CBC, + GCRY_CIPHER_SECURE); + if (rc) + return rc; + + + /* We need to work on a copy of the data because this makes it + easier to add the trailer and the padding and more important we + have to prefix the text with 2 parenthesis, so we have to + allocate enough space for: + + (()(4:hash4:sha120:)) + padding + + We always append a full block of random bytes as padding but + encrypt only what is needed for a full blocksize */ + blklen = gcry_cipher_get_algo_blklen (PROT_CIPHER); + outlen = 2 + protlen + 2 + 6 + 6 + 23 + 2 + blklen; + enclen = outlen/blklen * blklen; + outbuf = gcry_malloc_secure (outlen); + if (!outbuf) + rc = out_of_core (); + if (!rc) + { + /* allocate random bytes to be used as IV, padding and s2k salt*/ + iv = gcry_random_bytes (blklen*2+8, GCRY_WEAK_RANDOM); + if (!iv) + rc = gpg_error (GPG_ERR_ENOMEM); + else + rc = gcry_cipher_setiv (hd, iv, blklen); + } + if (!rc) + { + unsigned char *key; + size_t keylen = PROT_CIPHER_KEYLEN; + + key = gcry_malloc_secure (keylen); + if (!key) + rc = out_of_core (); + else + { + rc = hash_passphrase (passphrase, GCRY_MD_SHA1, + 3, iv+2*blklen, 96, key, keylen); + if (!rc) + rc = gcry_cipher_setkey (hd, key, keylen); + xfree (key); + } + } + if (!rc) + { + p = outbuf; + *p++ = '('; + *p++ = '('; + memcpy (p, protbegin, protlen); + p += protlen; + memcpy (p, ")(4:hash4:sha120:", 17); + p += 17; + memcpy (p, sha1hash, 20); + p += 20; + *p++ = ')'; + *p++ = ')'; + memcpy (p, iv+blklen, blklen); + p += blklen; + assert ( p - outbuf == outlen); + rc = gcry_cipher_encrypt (hd, outbuf, enclen, NULL, 0); + } + gcry_cipher_close (hd); + if (rc) + { + xfree (iv); + xfree (outbuf); + return rc; + } + + /* Now allocate the buffer we want to return. This is + + (protected openpgp-s2k3-sha1-aes-cbc + ((sha1 salt no_of_iterations) 16byte_iv) + encrypted_octet_string) + + in canoncical format of course. We use asprintf and %n modifier + and spaces as palceholders. */ + asprintf (&p, + "(9:protected%d:%s((4:sha18:%n_8bytes_2:96)%d:%n%*s)%d:%n%*s)", + (int)strlen (modestr), modestr, + &saltpos, + blklen, &ivpos, blklen, "", + enclen, &encpos, enclen, ""); + if (p) + { /* asprintf does not use our malloc system */ + char *psave = p; + p = xtrymalloc (strlen (psave)+1); + if (p) + strcpy (p, psave); + free (psave); + } + if (!p) + { + gpg_error_t tmperr = out_of_core (); + xfree (iv); + xfree (outbuf); + return tmperr; + } + *resultlen = strlen (p); + *result = p; + memcpy (p+saltpos, iv+2*blklen, 8); + memcpy (p+ivpos, iv, blklen); + memcpy (p+encpos, outbuf, enclen); + xfree (iv); + xfree (outbuf); + return 0; +} + + + +/* Protect the key encoded in canonical format in plainkey. We assume + a valid S-Exp here. */ +int +agent_protect (const unsigned char *plainkey, const char *passphrase, + unsigned char **result, size_t *resultlen) +{ + int rc; + const unsigned char *s; + const unsigned char *hash_begin, *hash_end; + const unsigned char *prot_begin, *prot_end, *real_end; + size_t n; + int c, infidx, i; + unsigned char hashvalue[20]; + unsigned char *protected; + size_t protectedlen; + int depth = 0; + unsigned char *p; + + s = plainkey; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + depth++; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "private-key")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + depth++; + hash_begin = s; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + + for (infidx=0; protect_info[infidx].algo + && !smatch (&s, n, protect_info[infidx].algo); infidx++) + ; + if (!protect_info[infidx].algo) + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + + prot_begin = prot_end = NULL; + for (i=0; (c=protect_info[infidx].parmlist[i]); i++) + { + if (i == protect_info[infidx].prot_from) + prot_begin = s; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + depth++; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (n != 1 || c != *s) + return gpg_error (GPG_ERR_INV_SEXP); + s += n; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s +=n; /* skip value */ + if (*s != ')') + return gpg_error (GPG_ERR_INV_SEXP); + depth--; + if (i == protect_info[infidx].prot_to) + prot_end = s; + s++; + } + if (*s != ')' || !prot_begin || !prot_end ) + return gpg_error (GPG_ERR_INV_SEXP); + depth--; + hash_end = s; + s++; + /* skip to the end of the S-exp */ + assert (depth == 1); + rc = sskip (&s, &depth); + if (rc) + return rc; + assert (!depth); + real_end = s-1; + + gcry_md_hash_buffer (GCRY_MD_SHA1, hashvalue, + hash_begin, hash_end - hash_begin + 1); + + rc = do_encryption (prot_begin, prot_end - prot_begin + 1, + passphrase, hashvalue, + &protected, &protectedlen); + if (rc) + return rc; + + /* Now create the protected version of the key. Note that the 10 + extra bytes are for for the inserted "protected-" string (the + beginning of the plaintext reads: "((11:private-key(" ). */ + *resultlen = (10 + + (prot_begin-plainkey) + + protectedlen + + (real_end-prot_end)); + *result = p = xtrymalloc (*resultlen); + if (!p) + { + gpg_error_t tmperr = out_of_core (); + xfree (protected); + return tmperr; + } + memcpy (p, "(21:protected-", 14); + p += 14; + memcpy (p, plainkey+4, prot_begin - plainkey - 4); + p += prot_begin - plainkey - 4; + memcpy (p, protected, protectedlen); + p += protectedlen; + memcpy (p, prot_end+1, real_end - prot_end); + p += real_end - prot_end; + assert ( p - *result == *resultlen); + xfree (protected); + return 0; +} + + +/* Do the actual decryption and check the return list for consistency. */ +static int +do_decryption (const unsigned char *protected, size_t protectedlen, + const char *passphrase, + const unsigned char *s2ksalt, unsigned long s2kcount, + const unsigned char *iv, size_t ivlen, + unsigned char **result) +{ + int rc = 0; + int blklen; + gcry_cipher_hd_t hd; + unsigned char *outbuf; + size_t reallen; + + blklen = gcry_cipher_get_algo_blklen (PROT_CIPHER); + if (protectedlen < 4 || (protectedlen%blklen)) + return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); + + rc = gcry_cipher_open (&hd, PROT_CIPHER, GCRY_CIPHER_MODE_CBC, + GCRY_CIPHER_SECURE); + if (rc) + return rc; + + outbuf = gcry_malloc_secure (protectedlen); + if (!outbuf) + rc = out_of_core (); + if (!rc) + rc = gcry_cipher_setiv (hd, iv, ivlen); + if (!rc) + { + unsigned char *key; + size_t keylen = PROT_CIPHER_KEYLEN; + + key = gcry_malloc_secure (keylen); + if (!key) + rc = out_of_core (); + else + { + rc = hash_passphrase (passphrase, GCRY_MD_SHA1, + 3, s2ksalt, s2kcount, key, keylen); + if (!rc) + rc = gcry_cipher_setkey (hd, key, keylen); + xfree (key); + } + } + if (!rc) + rc = gcry_cipher_decrypt (hd, outbuf, protectedlen, + protected, protectedlen); + gcry_cipher_close (hd); + if (rc) + { + xfree (outbuf); + return rc; + } + /* do a quick check first */ + if (*outbuf != '(' && outbuf[1] != '(') + { + xfree (outbuf); + return gpg_error (GPG_ERR_BAD_PASSPHRASE); + } + /* check that we have a consistent S-Exp */ + reallen = gcry_sexp_canon_len (outbuf, protectedlen, NULL, NULL); + if (!reallen || (reallen + blklen < protectedlen) ) + { + xfree (outbuf); + return gpg_error (GPG_ERR_BAD_PASSPHRASE); + } + *result = outbuf; + return 0; +} + + +/* Merge the parameter list contained in CLEARTEXT with the original + protect lists PROTECTEDKEY by replacing the list at REPLACEPOS. + Return the new list in RESULT and the MIC value in the 20 byte + buffer SHA1HASH. */ +static int +merge_lists (const unsigned char *protectedkey, + size_t replacepos, + const unsigned char *cleartext, + unsigned char *sha1hash, unsigned char **result) +{ + size_t n, newlistlen; + unsigned char *newlist, *p; + const unsigned char *s; + const unsigned char *startpos, *endpos; + int i, rc; + + if (replacepos < 26) + return gpg_error (GPG_ERR_BUG); + + /* Estimate the required size of the resulting list. We have a large + safety margin of >20 bytes (MIC hash from CLEARTEXT and the + removed "protected-" */ + newlistlen = gcry_sexp_canon_len (protectedkey, 0, NULL, NULL); + if (!newlistlen) + return gpg_error (GPG_ERR_BUG); + n = gcry_sexp_canon_len (cleartext, 0, NULL, NULL); + if (!n) + return gpg_error (GPG_ERR_BUG); + newlistlen += n; + newlist = gcry_malloc_secure (newlistlen); + if (!newlist) + return out_of_core (); + + /* Copy the initial segment */ + strcpy (newlist, "(11:private-key"); + p = newlist + 15; + memcpy (p, protectedkey+15+10, replacepos-15-10); + p += replacepos-15-10; + + /* copy the cleartext */ + s = cleartext; + if (*s != '(' && s[1] != '(') + return gpg_error (GPG_ERR_BUG); /*we already checked this */ + s += 2; + startpos = s; + while ( *s == '(' ) + { + s++; + n = snext (&s); + if (!n) + goto invalid_sexp; + s += n; + n = snext (&s); + if (!n) + goto invalid_sexp; + s += n; + if ( *s != ')' ) + goto invalid_sexp; + s++; + } + if ( *s != ')' ) + goto invalid_sexp; + endpos = s; + s++; + /* short intermezzo: Get the MIC */ + if (*s != '(') + goto invalid_sexp; + s++; + n = snext (&s); + if (!smatch (&s, n, "hash")) + goto invalid_sexp; + n = snext (&s); + if (!smatch (&s, n, "sha1")) + goto invalid_sexp; + n = snext (&s); + if (n != 20) + goto invalid_sexp; + memcpy (sha1hash, s, 20); + s += n; + if (*s != ')') + goto invalid_sexp; + /* end intermezzo */ + + /* append the parameter list */ + memcpy (p, startpos, endpos - startpos); + p += endpos - startpos; + + /* skip overt the protected list element in the original list */ + s = protectedkey + replacepos; + assert (*s == '('); + s++; + i = 1; + rc = sskip (&s, &i); + if (rc) + goto failure; + startpos = s; + i = 2; /* we are inside this level */ + rc = sskip (&s, &i); + if (rc) + goto failure; + assert (s[-1] == ')'); + endpos = s; /* one behind the end of the list */ + + /* append the rest */ + memcpy (p, startpos, endpos - startpos); + p += endpos - startpos; + + /* ready */ + *result = newlist; + return 0; + + failure: + xfree (newlist); + return rc; + + invalid_sexp: + xfree (newlist); + return gpg_error (GPG_ERR_INV_SEXP); +} + + + +/* Unprotect the key encoded in canonical format. We assume a valid + S-Exp here. */ +int +agent_unprotect (const unsigned char *protectedkey, const char *passphrase, + unsigned char **result, size_t *resultlen) +{ + int rc; + const unsigned char *s; + size_t n; + int infidx, i; + unsigned char sha1hash[20], sha1hash2[20]; + const unsigned char *s2ksalt; + unsigned long s2kcount; + const unsigned char *iv; + const unsigned char *prot_begin; + unsigned char *cleartext; + unsigned char *final; + + s = protectedkey; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "protected-private-key")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + + for (infidx=0; protect_info[infidx].algo + && !smatch (&s, n, protect_info[infidx].algo); infidx++) + ; + if (!protect_info[infidx].algo) + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + + /* now find the list with the protected information. Here is an + example for such a list: + (protected openpgp-s2k3-sha1-aes-cbc + ((sha1 ) ) + ) + */ + for (;;) + { + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + prot_begin = s; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (smatch (&s, n, "protected")) + break; + s += n; + i = 1; + rc = sskip (&s, &i); + if (rc) + return rc; + } + /* found */ + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc")) + return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION); + if (*s != '(' || s[1] != '(') + return gpg_error (GPG_ERR_INV_SEXP); + s += 2; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "sha1")) + return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION); + n = snext (&s); + if (n != 8) + return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); + s2ksalt = s; + s += n; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); + /* We expect a list close as next, so we can simply use strtoul() + here. We might want to check that we only have digits - but this + is nothing we should worry about */ + if (s[n] != ')' ) + return gpg_error (GPG_ERR_INV_SEXP); + s2kcount = strtoul (s, NULL, 10); + if (!s2kcount) + return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); + s += n; + s++; /* skip list end */ + + n = snext (&s); + if (n != 16) /* Wrong blocksize for IV (we support ony aes-128) */ + return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); + iv = s; + s += n; + if (*s != ')' ) + return gpg_error (GPG_ERR_INV_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + + rc = do_decryption (s, n, + passphrase, s2ksalt, s2kcount, + iv, 16, + &cleartext); + if (rc) + return rc; + + rc = merge_lists (protectedkey, prot_begin-protectedkey, cleartext, + sha1hash, &final); + xfree (cleartext); + if (rc) + return rc; + + rc = calculate_mic (final, sha1hash2); + if (!rc && memcmp (sha1hash, sha1hash2, 20)) + rc = gpg_error (GPG_ERR_CORRUPTED_PROTECTION); + if (rc) + { + xfree (final); + return rc; + } + + *result = final; + *resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL); + return 0; +} + +/* Check the type of the private key, this is one of the constants: + PRIVATE_KEY_UNKNOWN if we can't figure out the type (this is the + value 0), PRIVATE_KEY_CLEAR for an unprotected private key. + PRIVATE_KEY_PROTECTED for an protected private key or + PRIVATE_KEY_SHADOWED for a sub key where the secret parts are stored + elsewhere. */ +int +agent_private_key_type (const unsigned char *privatekey) +{ + const unsigned char *s; + size_t n; + + s = privatekey; + if (*s != '(') + return PRIVATE_KEY_UNKNOWN; + s++; + n = snext (&s); + if (!n) + return PRIVATE_KEY_UNKNOWN; + if (smatch (&s, n, "protected-private-key")) + return PRIVATE_KEY_PROTECTED; + if (smatch (&s, n, "shadowed-private-key")) + return PRIVATE_KEY_SHADOWED; + if (smatch (&s, n, "private-key")) + return PRIVATE_KEY_CLEAR; + return PRIVATE_KEY_UNKNOWN; +} + + + +/* Transform a passphrase into a suitable key of length KEYLEN and + store this key in the caller provided buffer KEY. The caller must + provide an HASHALGO, a valid S2KMODE (see rfc-2440) and depending on + that mode an S2KSALT of 8 random bytes and an S2KCOUNT (a suitable + value is 96). + + Returns an error code on failure. */ +static int +hash_passphrase (const char *passphrase, int hashalgo, + int s2kmode, + const unsigned char *s2ksalt, + unsigned long s2kcount, + unsigned char *key, size_t keylen) +{ + int rc; + gcry_md_hd_t md; + int pass, i; + int used = 0; + int pwlen = strlen (passphrase); + + if ( (s2kmode != 0 && s2kmode != 1 && s2kmode != 3) + || !hashalgo || !keylen || !key || !passphrase) + return gpg_error (GPG_ERR_INV_VALUE); + if ((s2kmode == 1 ||s2kmode == 3) && !s2ksalt) + return gpg_error (GPG_ERR_INV_VALUE); + + rc = gcry_md_open (&md, hashalgo, GCRY_MD_FLAG_SECURE); + if (rc) + return rc; + + for (pass=0; used < keylen; pass++) + { + if (pass) + { + gcry_md_reset (md); + for (i=0; i < pass; i++) /* preset the hash context */ + gcry_md_putc (md, 0); + } + + if (s2kmode == 1 || s2kmode == 3) + { + int len2 = pwlen + 8; + unsigned long count = len2; + + if (s2kmode == 3) + { + count = (16ul + (s2kcount & 15)) << ((s2kcount >> 4) + 6); + if (count < len2) + count = len2; + } + + while (count > len2) + { + gcry_md_write (md, s2ksalt, 8); + gcry_md_write (md, passphrase, pwlen); + count -= len2; + } + if (count < 8) + gcry_md_write (md, s2ksalt, count); + else + { + gcry_md_write (md, s2ksalt, 8); + count -= 8; + gcry_md_write (md, passphrase, count); + } + } + else + gcry_md_write (md, passphrase, pwlen); + + gcry_md_final (md); + i = gcry_md_get_algo_dlen (hashalgo); + if (i > keylen - used) + i = keylen - used; + memcpy (key+used, gcry_md_read (md, hashalgo), i); + used += i; + } + gcry_md_close(md); + return 0; +} + + + +/* Create a shadow key from a public key. We use the shadow protocol + "ti-v1" and insert the S-expressionn SHADOW_INFO. The resulting + S-expression is returned in an allocated buffer RESULT will point + to. The input parameters are expected to be valid canonilized + S-expressions */ +int +agent_shadow_key (const unsigned char *pubkey, + const unsigned char *shadow_info, + unsigned char **result) +{ + const unsigned char *s; + const unsigned char *point; + size_t n; + int depth = 0; + unsigned char *p; + size_t pubkey_len = gcry_sexp_canon_len (pubkey, 0, NULL,NULL); + size_t shadow_info_len = gcry_sexp_canon_len (shadow_info, 0, NULL,NULL); + + if (!pubkey_len || !shadow_info_len) + return gpg_error (GPG_ERR_INV_VALUE); + s = pubkey; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + depth++; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "public-key")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + depth++; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s += n; /* skip over the algorithm name */ + + while (*s != ')') + { + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + depth++; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s += n; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s +=n; /* skip value */ + if (*s != ')') + return gpg_error (GPG_ERR_INV_SEXP); + depth--; + s++; + } + point = s; /* insert right before the point */ + depth--; + s++; + assert (depth == 1); + + /* calculate required length by taking in account: the "shadowed-" + prefix, the "shadowed", "t1-v1" as well as some parenthesis */ + n = 12 + pubkey_len + 1 + 3+8 + 2+5 + shadow_info_len + 1; + *result = p = xtrymalloc (n); + if (!p) + return out_of_core (); + p = stpcpy (p, "(20:shadowed-private-key"); + /* (10:public-key ...)*/ + memcpy (p, pubkey+14, point - (pubkey+14)); + p += point - (pubkey+14); + p = stpcpy (p, "(8:shadowed5:t1-v1"); + memcpy (p, shadow_info, shadow_info_len); + p += shadow_info_len; + *p++ = ')'; + memcpy (p, point, pubkey_len - (point - pubkey)); + p += pubkey_len - (point - pubkey); + + return 0; +} + +/* Parse a canonical encoded shadowed key and return a pointer to the + inner list with the shadow_info */ +int +agent_get_shadow_info (const unsigned char *shadowkey, + unsigned char const **shadow_info) +{ + const unsigned char *s; + size_t n; + int depth = 0; + + s = shadowkey; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + depth++; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "shadowed-private-key")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + depth++; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s += n; /* skip over the algorithm name */ + + for (;;) + { + if (*s == ')') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + depth++; + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (smatch (&s, n, "shadowed")) + break; + s += n; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s +=n; /* skip value */ + if (*s != ')') + return gpg_error (GPG_ERR_INV_SEXP); + depth--; + s++; + } + /* found the shadowed list, s points to the protocol */ + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (smatch (&s, n, "t1-v1")) + { + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + *shadow_info = s; + } + else + return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL); + return 0; +} + diff --git a/agent/simple-pwquery.c b/agent/simple-pwquery.c new file mode 100644 index 000000000..e870122cb --- /dev/null +++ b/agent/simple-pwquery.c @@ -0,0 +1,486 @@ +/* simple-pwquery.c - A simple password query client for gpg-agent + * Copyright (C) 2002 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* This module is intended as a standalone client implementation to + gpg-agent's GET_PASSPHRASE command. In particular it does not use + the Assuan library and can only cope with an already running + gpg-agent. Some stuff is configurable in the header file. */ + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LOCALE_H +#include +#endif + +#define SIMPLE_PWQUERY_IMPLEMENTATION 1 +#include "simple-pwquery.h" + +#if defined(SPWQ_USE_LOGGING) && !defined(HAVE_JNLIB_LOGGING) +# undef SPWQ_USE_LOGGING +#endif + +#ifndef _ +#define _(a) (a) +#endif + +#if !defined (hexdigitp) && !defined (xtoi_2) +#define digitp(p) (*(p) >= '0' && *(p) <= '9') +#define hexdigitp(a) (digitp (a) \ + || (*(a) >= 'A' && *(a) <= 'F') \ + || (*(a) >= 'a' && *(a) <= 'f')) +#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ + *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) +#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) +#endif + + +/* Write NBYTES of BUF to file descriptor FD. */ +static int +writen (int fd, const void *buf, size_t nbytes) +{ + size_t nleft = nbytes; + int nwritten; + + while (nleft > 0) + { + nwritten = write( fd, buf, nleft ); + if (nwritten < 0) + { + if (errno == EINTR) + nwritten = 0; + else { +#ifdef SPWQ_USE_LOGGING + log_error ("write failed: %s\n", strerror (errno)); +#endif + return SPWQ_IO_ERROR; + } + } + nleft -= nwritten; + buf = (const char*)buf + nwritten; + } + + return 0; +} + + +/* Read an entire line and return number of bytes read. */ +static int +readline (int fd, char *buf, size_t buflen) +{ + size_t nleft = buflen; + char *p; + int nread = 0; + + while (nleft > 0) + { + int n = read (fd, buf, nleft); + if (n < 0) + { + if (errno == EINTR) + continue; + return -(SPWQ_IO_ERROR); + } + else if (!n) + { + return -(SPWQ_PROTOCOL_ERROR); /* incomplete line */ + } + p = buf; + nleft -= n; + buf += n; + nread += n; + + for (; n && *p != '\n'; n--, p++) + ; + if (n) + { + break; /* at least one full line available - that's enough. + This function is just a simple implementation, so + it is okay to forget about pending bytes */ + } + } + + return nread; +} + + +/* Send an option to the agent */ +static int +agent_send_option (int fd, const char *name, const char *value) +{ + char buf[200]; + int nread; + char *line; + int i; + + line = spwq_malloc (7 + strlen (name) + 1 + strlen (value) + 2); + if (!line) + return SPWQ_OUT_OF_CORE; + strcpy (stpcpy (stpcpy (stpcpy ( + stpcpy (line, "OPTION "), name), "="), value), "\n"); + i = writen (fd, line, strlen (line)); + spwq_free (line); + if (i) + return i; + + /* get response */ + nread = readline (fd, buf, DIM(buf)-1); + if (nread < 0) + return -nread; + if (nread < 3) + return SPWQ_PROTOCOL_ERROR; + + if (buf[0] == 'O' && buf[1] == 'K' && (buf[2] == ' ' || buf[2] == '\n')) + return 0; /* okay */ + + return SPWQ_ERR_RESPONSE; +} + + +/* Send all available options to the agent. */ +static int +agent_send_all_options (int fd) +{ + char *dft_display = NULL; + char *dft_ttyname = NULL; + char *dft_ttytype = NULL; + int rc = 0; + + dft_display = getenv ("DISPLAY"); + if (dft_display) + { + if ((rc = agent_send_option (fd, "display", dft_display))) + return rc; + } + + dft_ttyname = getenv ("GPG_TTY"); + if ((!dft_ttyname || !*dft_ttyname) && ttyname (0)) + dft_ttyname = ttyname (0); + if (dft_ttyname && *dft_ttyname) + { + if ((rc=agent_send_option (fd, "ttyname", dft_ttyname))) + return rc; + } + + dft_ttytype = getenv ("TERM"); + if (dft_ttyname && dft_ttytype) + { + if ((rc = agent_send_option (fd, "ttytype", dft_ttytype))) + return rc; + } + +#if defined(HAVE_SETLOCALE) + { + char *old_lc = NULL; + char *dft_lc = NULL; + +#if defined(LC_CTYPE) + old_lc = setlocale (LC_CTYPE, NULL); + if (old_lc) + { + char *p = spwq_malloc (strlen (old_lc)+1); + if (!p) + return SPWQ_OUT_OF_CORE; + strcpy (p, old_lc); + old_lc = p; + } + dft_lc = setlocale (LC_CTYPE, ""); + if (dft_ttyname && dft_lc) + rc = agent_send_option (fd, "lc-ctype", dft_lc); + if (old_lc) + { + setlocale (LC_CTYPE, old_lc); + spwq_free (old_lc); + } + if (rc) + return rc; +#endif + +#if defined(LC_MESSAGES) + old_lc = setlocale (LC_MESSAGES, NULL); + if (old_lc) + { + char *p = spwq_malloc (strlen (old_lc)+1); + if (!p) + return SPWQ_OUT_OF_CORE; + strcpy (p, old_lc); + old_lc = p; + } + dft_lc = setlocale (LC_MESSAGES, ""); + if (dft_ttyname && dft_lc) + rc = agent_send_option (fd, "lc-messages", dft_lc); + if (old_lc) + { + setlocale (LC_MESSAGES, old_lc); + spwq_free (old_lc); + } + if (rc) + return rc; +#endif + } +#endif /*HAVE_SETLOCALE*/ + + return 0; +} + + + +/* Try to open a connection to the agent, send all options and return + the file descriptor for the connection. Return -1 in case of + error. */ +static int +agent_open (int *rfd) +{ + int rc; + int fd; + char *infostr, *p; + struct sockaddr_un client_addr; + size_t len; + int prot; + char line[200]; + int nread; + + *rfd = -1; + infostr = getenv ( "GPG_AGENT_INFO" ); + if ( !infostr ) + { +#ifdef SPWQ_USE_LOGGING + log_error (_("gpg-agent is not available in this session\n")); +#endif + return SPWQ_NO_AGENT; + } + + if ( !(p = strchr ( infostr, ':')) || p == infostr + || (p-infostr)+1 >= sizeof client_addr.sun_path ) + { +#ifdef SPWQ_USE_LOGGING + log_error ( _("malformed GPG_AGENT_INFO environment variable\n")); +#endif + return SPWQ_NO_AGENT; + } + *p++ = 0; + + while (*p && *p != ':') + p++; + prot = *p? atoi (p+1) : 0; + if ( prot != 1) + { +#ifdef SPWQ_USE_LOGGING + log_error (_("gpg-agent protocol version %d is not supported\n"),prot); +#endif + return SPWQ_PROTOCOL_ERROR; + } + + if( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ) + { +#ifdef SPWQ_USE_LOGGING + log_error ("can't create socket: %s\n", strerror(errno) ); +#endif + return SPWQ_SYS_ERROR; + } + + memset (&client_addr, 0, sizeof client_addr); + client_addr.sun_family = AF_UNIX; + strcpy (client_addr.sun_path, infostr); + len = (offsetof (struct sockaddr_un, sun_path) + + strlen(client_addr.sun_path) + 1); + + if (connect (fd, (struct sockaddr*)&client_addr, len ) == -1) + { +#ifdef SPWQ_USE_LOGGING + log_error ( _("can't connect to `%s': %s\n"), infostr, strerror (errno)); +#endif + close (fd ); + return SPWQ_IO_ERROR; + } + + nread = readline (fd, line, DIM(line)); + if (nread < 3 || !(line[0] == 'O' && line[1] == 'K' + && (line[2] == '\n' || line[2] == ' ')) ) + { +#ifdef SPWQ_USE_LOGGING + log_error ( _("communication problem with gpg-agent\n")); +#endif + close (fd ); + return SPWQ_PROTOCOL_ERROR; + } + + rc = agent_send_all_options (fd); + if (rc) + { +#ifdef SPWQ_USE_LOGGING + log_error (_("problem setting the gpg-agent options\n")); +#endif + close (fd); + return rc; + } + + *rfd = fd; + return 0; +} + + +/* Copy text to BUFFER and escape as required. Return a poiinter to + the end of the new buffer. NOte that BUFFER must be large enough + to keep the entire text; allocataing it 3 times the size of TEXT + is sufficient. */ +static char * +copy_and_escape (char *buffer, const char *text) +{ + int i; + char *p = buffer; + + for (i=0; text[i]; i++) + { + if (text[i] < ' ' || text[i] == '+') + { + sprintf (p, "%%%02X", text[i]); + p += 3; + } + else if (text[i] == ' ') + *p++ = '+'; + else + *p++ = text[i]; + } + return p; +} + + +/* Ask the gpg-agent for a passphrase and present the user with a + DESCRIPTION, a PROMPT and optiaonlly with a TRYAGAIN extra text. + If a CACHEID is not NULL it is used to locate the passphrase in in + the cache and store it under this ID. If ERRORCODE is not NULL it + should point a variable receiving an errorcode; thsi errocode might + be 0 if the user canceled the operation. The function returns NULL + to indicate an error. */ +char * +simple_pwquery (const char *cacheid, + const char *tryagain, + const char *prompt, + const char *description, + int *errorcode) +{ + int fd = -1; + int nread; + char *result = NULL; + char *pw = NULL; + char *p; + int rc, i; + + rc = agent_open (&fd); + if (rc) + goto leave; + + if (!cacheid) + cacheid = "X"; + if (!tryagain) + tryagain = "X"; + if (!prompt) + prompt = "X"; + if (!description) + description = "X"; + + { + char *line; + /* We allocate 3 times the needed space so that there is enough + space for escaping. */ + line = spwq_malloc (15 + + 3*strlen (cacheid) + 1 + + 3*strlen (tryagain) + 1 + + 3*strlen (prompt) + 1 + + 3*strlen (description) + 1 + + 2); + if (!line) + { + rc = SPWQ_OUT_OF_CORE; + goto leave; + } + strcpy (line, "GET_PASSPHRASE "); + p = line+15; + p = copy_and_escape (p, cacheid); + *p++ = ' '; + p = copy_and_escape (p, tryagain); + *p++ = ' '; + p = copy_and_escape (p, prompt); + *p++ = ' '; + p = copy_and_escape (p, description); + *p++ = '\n'; + rc = writen (fd, line, p - line); + spwq_free (line); + if (rc) + goto leave; + } + + /* get response */ + pw = spwq_secure_malloc (500); + nread = readline (fd, pw, 499); + if (nread < 0) + { + rc = -nread; + goto leave; + } + if (nread < 3) + { + rc = SPWQ_PROTOCOL_ERROR; + goto leave; + } + + if (pw[0] == 'O' && pw[1] == 'K' && pw[2] == ' ') + { /* we got a passphrase - convert it back from hex */ + size_t pwlen = 0; + + for (i=3; i < nread && hexdigitp (pw+i); i+=2) + pw[pwlen++] = xtoi_2 (pw+i); + pw[pwlen] = 0; /* make a C String */ + result = pw; + pw = NULL; + } + else if (nread > 7 && !memcmp (pw, "ERR 111", 7) + && (pw[7] == ' ' || pw[7] == '\n') ) + { +#ifdef SPWQ_USE_LOGGING + log_info (_("canceled by user\n") ); +#endif + *errorcode = 0; /* canceled */ + } + else + { +#ifdef SPWQ_USE_LOGGING + log_error (_("problem with the agent\n")); +#endif + rc = SPWQ_ERR_RESPONSE; + } + + leave: + if (errorcode) + *errorcode = rc; + if (fd != -1) + close (fd); + if (pw) + spwq_free (pw); + return result; +} diff --git a/common/ChangeLog b/common/ChangeLog new file mode 100644 index 000000000..4870a4a5d --- /dev/null +++ b/common/ChangeLog @@ -0,0 +1,219 @@ +2003-07-15 Werner Koch + + * simple-pwquery.c, simple-pwquery.h: New; moved from ../agent. + * Makefile.am (libsimple_pwquery_a_LIBADD): New. + +2003-06-25 Werner Koch + + * maperror.c (map_to_assuan_status): Directly map 0 to 0. + +2003-06-17 Werner Koch + + * gettime.c (scan_isodatestr,add_days_to_timestamp,strtimevalue) + (strtimestamp,asctimestamp): New. Code taken from gnupg 1.3.2 + mischelp.c. + + * yesno.c: New. Code taken from gnupg 1.3.2 mischelp.c + + * miscellaneous.c: New. + + * util.h: Include utf8conf.h + +2003-06-16 Werner Koch + + * gettime.c (make_timestamp): New. + + * ttyio.c: New. Taken from gnupg 1.2. + * ttyio.h: Move from ../include. + +2003-06-13 Werner Koch + + * util.h (seterr): Removed macro. + (xmalloc_secure,xcalloc_secure): New. + +2003-06-11 Werner Koch + + * iobuf.c (iobuf_writebyte,iobuf_write): Return error code from + iobuf_flush. + (iobuf_writestr): Ditto. + +2003-06-10 Werner Koch + + * iobuf.c, iobuf.h: New. Taken from current gnupg 1.3 CVS. Run + indent on it and adjusted error handling to libgpg-error style. + Replaced IOBUF by iobuf_t. Renamed malloc functions. + +2003-06-04 Werner Koch + + * errors.h: Removed all error codes. We keep the status codes for + now. + * Makefile.am: Do not create errors.c anymore; remove it from the + sources. + + * maperror.c: Don't include error.h. Change all error codes to + libgpg-error style. + (map_assuan_err): Changed to new Assuan error code convention. + (map_to_assuan_status): Likewise. + (map_gcry_err,map_kbx_err): Not needed. For now dummy functions. + + * membuf.c, membuf.h: New. Code taken from ../sm/call-agent.h. + * Makefile.am: Added above. + +2003-04-29 Werner Koch + + * util.h (fopencokokie): Removed prototype and struct. + + * fopencookie.c: Removed. + + * maperror.c: Use system assuan.h + +2002-10-31 Neal H. Walfield + + * isascii.c: New file. + * putc_unlocked.c: Likewise. + +2002-10-28 Neal H. Walfield + + * signal.c (caught_fatal_sig): Remove superfluous zero + initializer. + (caught_sigusr1): Likewise. + +2002-09-04 Neal H. Walfield + + * vasprintf.c (vasprintf) [va_copy]: Use va_copy. + [!va_copy && __va_copy]: Use __va_copy. + [!va_copy && !__va_copy]: Only now fall back to using memcpy. + +2002-08-21 Werner Koch + + * errors.h: Added STATUS_IMPORT_PROBLEM. + +2002-08-20 Werner Koch + + * vasprintf.c: Hack to handle NULL for %s. + +2002-08-09 Werner Koch + + * signal.c: New. Taken from GnuPG 1.1.91. + +2002-07-23 Werner Koch + + * util.h (_IO_cookie_io_functions_t): Fixed typo. Noted by + Richard Lefebvre. + +2002-07-22 Werner Koch + + * fseeko.c, ftello.c: New. + +2002-06-28 Werner Koch + + * maperror.c (map_to_assuan_status): Map more errorcodes to Bad + Certificate. + +2002-06-26 Werner Koch + + * maperror.c (map_to_assuan_status): Map EOF to No_Data_Available. + +2002-06-10 Werner Koch + + * errors.h (gnupg_error_token): Add new prototype. + (STATUS_ERROR): New. + + * mkerrtok: New. + * Makefile.am: Use it to create the new error token function. + +2002-06-04 Werner Koch + + * maperror.c (map_to_assuan_status): Map Bad_CA_Certificate. + +2002-05-23 Werner Koch + + * no-pth.c, Makefile.am: Removed. + +2002-05-22 Werner Koch + + * mkdtemp.c: Replaced byte by unsigned char because it is no longer + defined in gcrypt.h. + +2002-05-21 Werner Koch + + * maperror.c (map_gcry_err): Add libgcrypt's new S-expression errors. + (map_ksba_err): Add a few mappings. + +2002-05-14 Werner Koch + + * gettime.c: New. + +2002-05-03 Werner Koch + + * errors.h: Added STARUS_EXPSIG and STATUS_EXPKEYSIG. + +2002-04-15 Werner Koch + + * cryptmiss.c: New. + +2002-02-14 Werner Koch + + * maperror.c: Add more assuan<->gnupg mappings. + +2002-02-12 Werner Koch + + * fopencookie.c: Dummy function. + + * vasprintf.c: New. Taken from binutils-2.9.1 and dropped all non + ANSI-C stuff. Merged with asprintf version. + + * no-pth.c: New. + +2002-01-23 Werner Koch + + * mkdtemp.c: Copied from gnupg-1.0.6c and changed to use libgcrypt. + +2002-01-19 Werner Koch + + * sysutils.c: New. This is the misc.c file from gnupg 1.0.6 with + the OpenPGP stuff removed. + * sysutils.h: New. + +2002-01-15 Werner Koch + + * maperror.c: Add mapping for Not_Trusted. + +2002-01-11 Werner Koch + + * maperror.c (map_assuan_err): Codes for CRL + +2002-01-08 Werner Koch + + * util.h (spacep): New. + +2002-01-02 Werner Koch + + * maperror.c (map_to_assuan_status): New. Merged from ../agent + and ../sm. + +2001-12-20 Werner Koch + + * maperror.c (map_gcry_err): Add some mappings. + +2001-12-18 Werner Koch + + * Makefile.am (AM_CPPFLAGS): Include flags for gcrypt and ksba + +2001-12-14 Werner Koch + + * util.h (digitp, hexdigitp): New ctype like macros. + (atoi_1,atoi_2,atoi_4,xtoi_1,xtoi_2): New. + + + Copyright 2001, 2002 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. + + diff --git a/common/Makefile.am b/common/Makefile.am new file mode 100644 index 000000000..2b99a19eb --- /dev/null +++ b/common/Makefile.am @@ -0,0 +1,58 @@ +# Makefile for common gnupg modules +# Copyright (C) 2001, 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 2 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +## Process this file with automake to produce Makefile.in + +EXTRA_DIST = mkerrors mkerrtok +#INCLUDES = + +noinst_LIBRARIES = libcommon.a libsimple-pwquery.a + +AM_CPPFLAGS = $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) + +libcommon_a_SOURCES = \ + util.h i18n.h \ + errors.h \ + maperror.c \ + sysutils.c sysutils.h \ + cryptmiss.c \ + gettime.c \ + yesno.c \ + miscellaneous.c \ + membuf.c membuf.h \ + iobuf.c iobuf.h \ + ttyio.c ttyio.h \ + signal.c + + +libcommon_a_LIBADD = @LIBOBJS@ + +libsimple_pwquery_a_SOURCES = \ + simple-pwquery.c simple-pwquery.h + +libsimple_pwquery_a_LIBADD = @LIBOBJS@ + + + + + + + + + diff --git a/common/README b/common/README new file mode 100644 index 000000000..a90224bab --- /dev/null +++ b/common/README @@ -0,0 +1,11 @@ +Stuff used by several modules of GnuPG. + +These directories use it: + +gpg +sm +agent + +These directories don't use it: + +kbx \ No newline at end of file diff --git a/common/errors.h b/common/errors.h new file mode 100644 index 000000000..a5643f08a --- /dev/null +++ b/common/errors.h @@ -0,0 +1,110 @@ +/* errors.h - Globally used error codes + * Copyright (C) 2001 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef GNUPG_COMMON_ERRORS_H +#define GNUPG_COMMON_ERRORS_H + +#include "util.h" + +/* Status codes - fixme: should go into another file */ +enum { + STATUS_ENTER, + STATUS_LEAVE, + STATUS_ABORT, + STATUS_GOODSIG, + STATUS_BADSIG, + STATUS_ERRSIG, + STATUS_BADARMOR, + STATUS_RSA_OR_IDEA, + STATUS_SIGEXPIRED, + STATUS_KEYREVOKED, + STATUS_TRUST_UNDEFINED, + STATUS_TRUST_NEVER, + STATUS_TRUST_MARGINAL, + STATUS_TRUST_FULLY, + STATUS_TRUST_ULTIMATE, + + STATUS_SHM_INFO, + STATUS_SHM_GET, + STATUS_SHM_GET_BOOL, + STATUS_SHM_GET_HIDDEN, + + STATUS_NEED_PASSPHRASE, + STATUS_VALIDSIG, + STATUS_SIG_ID, + STATUS_ENC_TO, + STATUS_NODATA, + STATUS_BAD_PASSPHRASE, + STATUS_NO_PUBKEY, + STATUS_NO_SECKEY, + STATUS_NEED_PASSPHRASE_SYM, + STATUS_DECRYPTION_FAILED, + STATUS_DECRYPTION_OKAY, + STATUS_MISSING_PASSPHRASE, + STATUS_GOOD_PASSPHRASE, + STATUS_GOODMDC, + STATUS_BADMDC, + STATUS_ERRMDC, + STATUS_IMPORTED, + STATUS_IMPORT_PROBLEM, + STATUS_IMPORT_RES, + STATUS_FILE_START, + STATUS_FILE_DONE, + STATUS_FILE_ERROR, + + STATUS_BEGIN_DECRYPTION, + STATUS_END_DECRYPTION, + STATUS_BEGIN_ENCRYPTION, + STATUS_END_ENCRYPTION, + + STATUS_DELETE_PROBLEM, + STATUS_GET_BOOL, + STATUS_GET_LINE, + STATUS_GET_HIDDEN, + STATUS_GOT_IT, + STATUS_PROGRESS, + STATUS_SIG_CREATED, + STATUS_SESSION_KEY, + STATUS_NOTATION_NAME, + STATUS_NOTATION_DATA, + STATUS_POLICY_URL, + STATUS_BEGIN_STREAM, + STATUS_END_STREAM, + STATUS_KEY_CREATED, + STATUS_USERID_HIN, + STATUS_UNEXPECTED, + STATUS_INV_RECP, + STATUS_NO_RECP, + STATUS_ALREADY_SIGNED, + + STATUS_EXPSIG, + STATUS_EXPKEYSIG, + + STATUS_TRUNCATED, + STATUS_ERROR +}; + + +/*-- errors.c (build by mkerror and mkerrtok) --*/ +const char *gnupg_strerror (int err); +const char *gnupg_error_token (int err); + + +#endif /*GNUPG_COMMON_ERRORS_H*/ diff --git a/common/gettime.c b/common/gettime.c new file mode 100644 index 000000000..a7914d348 --- /dev/null +++ b/common/gettime.c @@ -0,0 +1,250 @@ +/* gettime.c - Wrapper for time functions + * Copyright (C) 1998, 2002 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#ifdef HAVE_LANGINFO_H +#include +#endif + +#include "util.h" + +static unsigned long timewarp; +static enum { NORMAL = 0, FROZEN, FUTURE, PAST } timemode; + +/* Wrapper for the time(3). We use this here so we can fake the time + for tests */ +time_t +gnupg_get_time () +{ + time_t current = time (NULL); + if (timemode == NORMAL) + return current; + else if (timemode == FROZEN) + return timewarp; + else if (timemode == FUTURE) + return current + timewarp; + else + return current - timewarp; +} + +/* set the time to NEWTIME so that gnupg_get_time returns a time + starting with this one. With FREEZE set to 1 the returned time + will never change. Just for completeness, a value of (time_t)-1 + for NEWTIME gets you back to rality. Note that this is obviously + not thread-safe but this is not required. */ +void +gnupg_set_time (time_t newtime, int freeze) +{ + time_t current = time (NULL); + + if ( newtime == (time_t)-1 || current == newtime) + { + timemode = NORMAL; + timewarp = 0; + } + else if (freeze) + { + timemode = FROZEN; + timewarp = current; + } + else if (newtime > current) + { + timemode = FUTURE; + timewarp = newtime - current; + } + else + { + timemode = PAST; + timewarp = current - newtime; + } +} + +/* Returns true when we are in timewarp mode */ +int +gnupg_faked_time_p (void) +{ + return timemode; +} + + +/* This function is used by gpg because OpenPGP defines the timestamp + as an unsigned 32 bit value. */ +u32 +make_timestamp (void) +{ + time_t t = gnupg_get_time (); + + if (t == (time_t)-1) + log_fatal ("gnupg_get_time() failed\n"); + return (u32)t; +} + + + +/**************** + * Scan a date string and return a timestamp. + * The only supported format is "yyyy-mm-dd" + * Returns 0 for an invalid date. + */ +u32 +scan_isodatestr( const char *string ) +{ + int year, month, day; + struct tm tmbuf; + time_t stamp; + int i; + + if( strlen(string) != 10 || string[4] != '-' || string[7] != '-' ) + return 0; + for( i=0; i < 4; i++ ) + if( !digitp (string+i) ) + return 0; + if( !digitp (string+5) || !digitp(string+6) ) + return 0; + if( !digitp(string+8) || !digitp(string+9) ) + return 0; + year = atoi(string); + month = atoi(string+5); + day = atoi(string+8); + /* some basic checks */ + if( year < 1970 || month < 1 || month > 12 || day < 1 || day > 31 ) + return 0; + memset( &tmbuf, 0, sizeof tmbuf ); + tmbuf.tm_mday = day; + tmbuf.tm_mon = month-1; + tmbuf.tm_year = year - 1900; + tmbuf.tm_isdst = -1; + stamp = mktime( &tmbuf ); + if( stamp == (time_t)-1 ) + return 0; + return stamp; +} + + +u32 +add_days_to_timestamp( u32 stamp, u16 days ) +{ + return stamp + days*86400L; +} + + +/**************** + * Return a string with a time value in the form: x Y, n D, n H + */ + +const char * +strtimevalue( u32 value ) +{ + static char buffer[30]; + unsigned int years, days, hours, minutes; + + value /= 60; + minutes = value % 60; + value /= 60; + hours = value % 24; + value /= 24; + days = value % 365; + value /= 365; + years = value; + + sprintf(buffer,"%uy%ud%uh%um", years, days, hours, minutes ); + if( years ) + return buffer; + if( days ) + return strchr( buffer, 'y' ) + 1; + return strchr( buffer, 'd' ) + 1; +} + + +/**************** + * Note: this function returns GMT + */ +const char * +strtimestamp( u32 stamp ) +{ + static char buffer[11+5]; + struct tm *tp; + time_t atime = stamp; + + if (atime < 0) { + strcpy (buffer, "????" "-??" "-??"); + } + else { + tp = gmtime( &atime ); + sprintf(buffer,"%04d-%02d-%02d", + 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday ); + } + return buffer; +} + +/**************** + * Note: this function returns local time + */ +const char * +asctimestamp( u32 stamp ) +{ + static char buffer[50]; +#if defined (HAVE_STRFTIME) && defined (HAVE_NL_LANGINFO) + static char fmt[50]; +#endif + struct tm *tp; + time_t atime = stamp; + + if (atime < 0) { + strcpy (buffer, "????" "-??" "-??"); + return buffer; + } + + tp = localtime( &atime ); +#ifdef HAVE_STRFTIME +#if defined(HAVE_NL_LANGINFO) + mem2str( fmt, nl_langinfo(D_T_FMT), DIM(fmt)-3 ); + if( strstr( fmt, "%Z" ) == NULL ) + strcat( fmt, " %Z"); + strftime( buffer, DIM(buffer)-1, fmt, tp ); +#else + /* fixme: we should check whether the locale appends a " %Z" + * These locales from glibc don't put the " %Z": + * fi_FI hr_HR ja_JP lt_LT lv_LV POSIX ru_RU ru_SU sv_FI sv_SE zh_CN + */ + strftime( buffer, DIM(buffer)-1, "%c %Z", tp ); +#endif + buffer[DIM(buffer)-1] = 0; +#else + mem2str( buffer, asctime(tp), DIM(buffer) ); +#endif + return buffer; +} + + + + + + + + + + + + + + diff --git a/common/iobuf.c b/common/iobuf.c new file mode 100644 index 000000000..773e2993b --- /dev/null +++ b/common/iobuf.c @@ -0,0 +1,2415 @@ +/* iobuf.c - file handling + * Copyright (C) 1998, 1999, 2000, 2001, 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_DOSISH_SYSTEM +#include +#endif +#ifdef __riscos__ +#include +#include +#endif /* __riscos__ */ + +#include "memory.h" +#include "util.h" +#include "iobuf.h" + +#undef FILE_FILTER_USES_STDIO + +#ifdef HAVE_DOSISH_SYSTEM +#define USE_SETMODE 1 +#endif + +#ifdef FILE_FILTER_USES_STDIO +#define my_fileno(a) fileno ((a)) +#define my_fopen_ro(a,b) fopen ((a),(b)) +#define my_fopen(a,b) fopen ((a),(b)) +typedef FILE *FILEP_OR_FD; +#define INVALID_FP NULL +#define FILEP_OR_FD_FOR_STDIN (stdin) +#define FILEP_OR_FD_FOR_STDOUT (stdout) +typedef struct +{ + FILE *fp; /* open file handle */ + int keep_open; + int no_cache; + int print_only_name; /* flags indicating that fname is not a real file */ + char fname[1]; /* name of the file */ +} +file_filter_ctx_t; +#else +#define my_fileno(a) (a) +#define my_fopen_ro(a,b) fd_cache_open ((a),(b)) +#define my_fopen(a,b) direct_open ((a),(b)) +#ifdef HAVE_DOSISH_SYSTEM +typedef HANDLE FILEP_OR_FD; +#define INVALID_FP ((HANDLE)-1) +#define FILEP_OR_FD_FOR_STDIN (GetStdHandle (STD_INPUT_HANDLE)) +#define FILEP_OR_FD_FOR_STDOUT (GetStdHandle (STD_OUTPUT_HANDLE)) +#undef USE_SETMODE +#else +typedef int FILEP_OR_FD; +#define INVALID_FP (-1) +#define FILEP_OR_FD_FOR_STDIN (0) +#define FILEP_OR_FD_FOR_STDOUT (1) +#endif +typedef struct +{ + FILEP_OR_FD fp; /* open file handle */ + int keep_open; + int no_cache; + int eof_seen; + int print_only_name; /* flags indicating that fname is not a real file */ + char fname[1]; /* name of the file */ +} +file_filter_ctx_t; + +struct close_cache_s +{ + struct close_cache_s *next; + FILEP_OR_FD fp; + char fname[1]; +}; +typedef struct close_cache_s *CLOSE_CACHE; +static CLOSE_CACHE close_cache; +#endif + +#ifdef __MINGW32__ +typedef struct +{ + int sock; + int keep_open; + int no_cache; + int eof_seen; + int print_only_name; /* flags indicating that fname is not a real file */ + char fname[1]; /* name of the file */ +} +sock_filter_ctx_t; +#endif /*__MINGW32__*/ + +/* The first partial length header block must be of size 512 + * to make it easier (and efficienter) we use a min. block size of 512 + * for all chunks (but the last one) */ +#define OP_MIN_PARTIAL_CHUNK 512 +#define OP_MIN_PARTIAL_CHUNK_2POW 9 + +typedef struct +{ + int use; + size_t size; + size_t count; + int partial; /* 1 = partial header, 2 in last partial packet */ + char *buffer; /* used for partial header */ + size_t buflen; /* used size of buffer */ + int first_c; /* of partial header (which is > 0) */ + int eof; +} +block_filter_ctx_t; + +static int special_names_enabled; + +static int underflow (iobuf_t a); +static int translate_file_handle (int fd, int for_write); + +#ifndef FILE_FILTER_USES_STDIO + +/* + * Invalidate (i.e. close) a cached iobuf + */ +static void +fd_cache_invalidate (const char *fname) +{ + CLOSE_CACHE cc; + + assert (fname); + if (DBG_IOBUF) + log_debug ("fd_cache_invalidate (%s)\n", fname); + + for (cc = close_cache; cc; cc = cc->next) + { + if (cc->fp != INVALID_FP && !strcmp (cc->fname, fname)) + { + if (DBG_IOBUF) + log_debug (" did (%s)\n", cc->fname); +#ifdef HAVE_DOSISH_SYSTEM + CloseHandle (cc->fp); +#else + close (cc->fp); +#endif + cc->fp = INVALID_FP; + } + } +} + + + +static FILEP_OR_FD +direct_open (const char *fname, const char *mode) +{ +#ifdef HAVE_DOSISH_SYSTEM + unsigned long da, cd, sm; + HANDLE hfile; + + /* Note, that we do not handle all mode combinations */ + + /* According to the ReactOS source it seems that open() of the + * standard MSW32 crt does open the file in share mode which is + * something new for MS applications ;-) + */ + if (strchr (mode, '+')) + { + fd_cache_invalidate (fname); + da = GENERIC_READ | GENERIC_WRITE; + cd = OPEN_EXISTING; + sm = FILE_SHARE_READ | FILE_SHARE_WRITE; + } + else if (strchr (mode, 'w')) + { + fd_cache_invalidate (fname); + da = GENERIC_WRITE; + cd = CREATE_ALWAYS; + sm = FILE_SHARE_WRITE; + } + else + { + da = GENERIC_READ; + cd = OPEN_EXISTING; + sm = FILE_SHARE_READ; + } + + hfile = CreateFile (fname, da, sm, NULL, cd, FILE_ATTRIBUTE_NORMAL, NULL); + return hfile; +#else + int oflag; + int cflag = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + + /* Note, that we do not handle all mode combinations */ + if (strchr (mode, '+')) + { + fd_cache_invalidate (fname); + oflag = O_RDWR; + } + else if (strchr (mode, 'w')) + { + fd_cache_invalidate (fname); + oflag = O_WRONLY | O_CREAT | O_TRUNC; + } + else + { + oflag = O_RDONLY; + } +#ifdef O_BINARY + if (strchr (mode, 'b')) + oflag |= O_BINARY; +#endif +#ifndef __riscos__ + return open (fname, oflag, cflag); +#else + { + struct stat buf; + int rc = stat (fname, &buf); + + /* Don't allow iobufs on directories */ + if (!rc && S_ISDIR (buf.st_mode) && !S_ISREG (buf.st_mode)) + return __set_errno (EISDIR); + else + return open (fname, oflag, cflag); + } +#endif +#endif +} + + +/* + * Instead of closing an FD we keep it open and cache it for later reuse + * Note that this caching strategy only works if the process does not chdir. + */ +static void +fd_cache_close (const char *fname, FILEP_OR_FD fp) +{ + CLOSE_CACHE cc; + + assert (fp); + if (!fname || !*fname) + { +#ifdef HAVE_DOSISH_SYSTEM + CloseHandle (fp); +#else + close (fp); +#endif + if (DBG_IOBUF) + log_debug ("fd_cache_close (%p) real\n", (void *) fp); + return; + } + /* try to reuse a slot */ + for (cc = close_cache; cc; cc = cc->next) + { + if (cc->fp == INVALID_FP && !strcmp (cc->fname, fname)) + { + cc->fp = fp; + if (DBG_IOBUF) + log_debug ("fd_cache_close (%s) used existing slot\n", fname); + return; + } + } + /* add a new one */ + if (DBG_IOBUF) + log_debug ("fd_cache_close (%s) new slot created\n", fname); + cc = xcalloc (1, sizeof *cc + strlen (fname)); + strcpy (cc->fname, fname); + cc->fp = fp; + cc->next = close_cache; + close_cache = cc; +} + +/* + * Do an direct_open on FNAME but first try to reuse one from the fd_cache + */ +static FILEP_OR_FD +fd_cache_open (const char *fname, const char *mode) +{ + CLOSE_CACHE cc; + + assert (fname); + for (cc = close_cache; cc; cc = cc->next) + { + if (cc->fp != INVALID_FP && !strcmp (cc->fname, fname)) + { + FILEP_OR_FD fp = cc->fp; + cc->fp = INVALID_FP; + if (DBG_IOBUF) + log_debug ("fd_cache_open (%s) using cached fp\n", fname); +#ifdef HAVE_DOSISH_SYSTEM + if (SetFilePointer (fp, 0, NULL, FILE_BEGIN) == 0xffffffff) + { + log_error ("rewind file failed on handle %p: ec=%d\n", + fp, (int) GetLastError ()); + fp = INVALID_FP; + } +#else + if (lseek (fp, 0, SEEK_SET) == (off_t) - 1) + { + log_error ("can't rewind fd %d: %s\n", fp, strerror (errno)); + fp = INVALID_FP; + } +#endif + return fp; + } + } + if (DBG_IOBUF) + log_debug ("fd_cache_open (%s) not cached\n", fname); + return direct_open (fname, mode); +} + + +#endif /*FILE_FILTER_USES_STDIO */ + + +/**************** + * Read data from a file into buf which has an allocated length of *LEN. + * return the number of read bytes in *LEN. OPAQUE is the FILE * of + * the stream. A is not used. + * control may be: + * IOBUFCTRL_INIT: called just before the function is linked into the + * list of function. This can be used to prepare internal + * data structures of the function. + * IOBUFCTRL_FREE: called just before the function is removed from the + * list of functions and can be used to release internal + * data structures or close a file etc. + * IOBUFCTRL_UNDERFLOW: called by iobuf_underflow to fill the buffer + * with new stuff. *RET_LEN is the available size of the + * buffer, and should be set to the number of bytes + * which were put into the buffer. The function + * returns 0 to indicate success, -1 on EOF and + * GPG_ERR_xxxxx for other errors. + * + * IOBUFCTRL_FLUSH: called by iobuf_flush() to write out the collected stuff. + * *RET_LAN is the number of bytes in BUF. + * + * IOBUFCTRL_CANCEL: send to all filters on behalf of iobuf_cancel. The + * filter may take appropriate action on this message. + */ +static int +file_filter (void *opaque, int control, iobuf_t chain, byte * buf, + size_t * ret_len) +{ + file_filter_ctx_t *a = opaque; + FILEP_OR_FD f = a->fp; + size_t size = *ret_len; + size_t nbytes = 0; + int rc = 0; + +#ifdef FILE_FILTER_USES_STDIO + if (control == IOBUFCTRL_UNDERFLOW) + { + assert (size); /* need a buffer */ + if (feof (f)) + { /* On terminals you could easiely read as many EOFs as you call */ + rc = -1; /* fread() or fgetc() repeatly. Every call will block until you press */ + *ret_len = 0; /* CTRL-D. So we catch this case before we call fread() again. */ + } + else + { + clearerr (f); + nbytes = fread (buf, 1, size, f); + if (feof (f) && !nbytes) + { + rc = -1; /* okay: we can return EOF now. */ + } + else if (ferror (f) && errno != EPIPE) + { + rc = gpg_error_from_errno (errno); + log_error ("%s: read error: %s\n", a->fname, strerror (errno)); + } + *ret_len = nbytes; + } + } + else if (control == IOBUFCTRL_FLUSH) + { + if (size) + { + clearerr (f); + nbytes = fwrite (buf, 1, size, f); + if (ferror (f)) + { + rc = gpg_error_from_errno (errno); + log_error ("%s: write error: %s\n", a->fname, strerror (errno)); + } + } + *ret_len = nbytes; + } + else if (control == IOBUFCTRL_INIT) + { + a->keep_open = a->no_cache = 0; + } + else if (control == IOBUFCTRL_DESC) + { + *(char **) buf = "file_filter"; + } + else if (control == IOBUFCTRL_FREE) + { + if (f != stdin && f != stdout) + { + if (DBG_IOBUF) + log_debug ("%s: close fd %d\n", a->fname, fileno (f)); + if (!a->keep_open) + fclose (f); + } + f = NULL; + xfree (a); /* we can free our context now */ + } +#else /* !stdio implementation */ + + if (control == IOBUFCTRL_UNDERFLOW) + { + assert (size); /* need a buffer */ + if (a->eof_seen) + { + rc = -1; + *ret_len = 0; + } + else + { +#ifdef HAVE_DOSISH_SYSTEM + unsigned long nread; + + nbytes = 0; + if (!ReadFile (f, buf, size, &nread, NULL)) + { + int ec = (int) GetLastError (); + if (ec != ERROR_BROKEN_PIPE) + { + rc = gpg_error_from_errno (ec); + log_error ("%s: read error: ec=%d\n", a->fname, ec); + } + } + else if (!nread) + { + a->eof_seen = 1; + rc = -1; + } + else + { + nbytes = nread; + } + +#else + + int n; + + nbytes = 0; + do + { + n = read (f, buf, size); + } + while (n == -1 && errno == EINTR); + if (n == -1) + { /* error */ + if (errno != EPIPE) + { + rc = gpg_error_from_errno (errno); + log_error ("%s: read error: %s\n", + a->fname, strerror (errno)); + } + } + else if (!n) + { /* eof */ + a->eof_seen = 1; + rc = -1; + } + else + { + nbytes = n; + } +#endif + *ret_len = nbytes; + } + } + else if (control == IOBUFCTRL_FLUSH) + { + if (size) + { +#ifdef HAVE_DOSISH_SYSTEM + byte *p = buf; + unsigned long n; + + nbytes = size; + do + { + if (size && !WriteFile (f, p, nbytes, &n, NULL)) + { + int ec = (int) GetLastError (); + rc = gpg_error_from_errno (ec); + log_error ("%s: write error: ec=%d\n", a->fname, ec); + break; + } + p += n; + nbytes -= n; + } + while (nbytes); + nbytes = p - buf; +#else + byte *p = buf; + int n; + + nbytes = size; + do + { + do + { + n = write (f, p, nbytes); + } + while (n == -1 && errno == EINTR); + if (n > 0) + { + p += n; + nbytes -= n; + } + } + while (n != -1 && nbytes); + if (n == -1) + { + rc = gpg_error_from_errno (errno); + log_error ("%s: write error: %s\n", a->fname, strerror (errno)); + } + nbytes = p - buf; +#endif + } + *ret_len = nbytes; + } + else if (control == IOBUFCTRL_INIT) + { + a->eof_seen = 0; + a->keep_open = 0; + a->no_cache = 0; + } + else if (control == IOBUFCTRL_DESC) + { + *(char **) buf = "file_filter(fd)"; + } + else if (control == IOBUFCTRL_FREE) + { +#ifdef HAVE_DOSISH_SYSTEM + if (f != FILEP_OR_FD_FOR_STDIN && f != FILEP_OR_FD_FOR_STDOUT) + { + if (DBG_IOBUF) + log_debug ("%s: close handle %p\n", a->fname, f); + if (!a->keep_open) + fd_cache_close (a->no_cache ? NULL : a->fname, f); + } +#else + if ((int) f != 0 && (int) f != 1) + { + if (DBG_IOBUF) + log_debug ("%s: close fd %d\n", a->fname, f); + if (!a->keep_open) + fd_cache_close (a->no_cache ? NULL : a->fname, f); + } + f = INVALID_FP; +#endif + xfree (a); /* we can free our context now */ + } +#endif /* !stdio implementation */ + return rc; +} + +#ifdef __MINGW32__ +/* Becuase sockets are an special object under Lose32 we have to + * use a special filter */ +static int +sock_filter (void *opaque, int control, iobuf_t chain, byte * buf, + size_t * ret_len) +{ + sock_filter_ctx_t *a = opaque; + size_t size = *ret_len; + size_t nbytes = 0; + int rc = 0; + + if (control == IOBUFCTRL_UNDERFLOW) + { + assert (size); /* need a buffer */ + if (a->eof_seen) + { + rc = -1; + *ret_len = 0; + } + else + { + int nread; + + nread = recv (a->sock, buf, size, 0); + if (nread == SOCKET_ERROR) + { + int ec = (int) WSAGetLastError (); + rc = gpg_error_from_errno (ec); + log_error ("socket read error: ec=%d\n", ec); + } + else if (!nread) + { + a->eof_seen = 1; + rc = -1; + } + else + { + nbytes = nread; + } + *ret_len = nbytes; + } + } + else if (control == IOBUFCTRL_FLUSH) + { + if (size) + { + byte *p = buf; + int n; + + nbytes = size; + do + { + n = send (a->sock, p, nbytes, 0); + if (n == SOCKET_ERROR) + { + int ec = (int) WSAGetLastError (); + rc = gpg_error_from_errno (ec); + log_error ("socket write error: ec=%d\n", ec); + break; + } + p += n; + nbytes -= n; + } + while (nbytes); + nbytes = p - buf; + } + *ret_len = nbytes; + } + else if (control == IOBUFCTRL_INIT) + { + a->eof_seen = 0; + a->keep_open = 0; + a->no_cache = 0; + } + else if (control == IOBUFCTRL_DESC) + { + *(char **) buf = "sock_filter"; + } + else if (control == IOBUFCTRL_FREE) + { + if (!a->keep_open) + closesocket (a->sock); + xfree (a); /* we can free our context now */ + } + return rc; +} +#endif /*__MINGW32__*/ + +/**************** + * This is used to implement the block write mode. + * Block reading is done on a byte by byte basis in readbyte(), + * without a filter + */ +static int +block_filter (void *opaque, int control, iobuf_t chain, byte * buf, + size_t * ret_len) +{ + block_filter_ctx_t *a = opaque; + size_t size = *ret_len; + int c, needed, rc = 0; + char *p; + + if (control == IOBUFCTRL_UNDERFLOW) + { + size_t n = 0; + + p = buf; + assert (size); /* need a buffer */ + if (a->eof) /* don't read any further */ + rc = -1; + while (!rc && size) + { + if (!a->size) + { /* get the length bytes */ + if (a->partial == 2) + { + a->eof = 1; + if (!n) + rc = -1; + break; + } + else if (a->partial) + { + /* These OpenPGP introduced huffman like encoded length + * bytes are really a mess :-( */ + if (a->first_c) + { + c = a->first_c; + a->first_c = 0; + } + else if ((c = iobuf_get (chain)) == -1) + { + log_error ("block_filter: 1st length byte missing\n"); + rc = GPG_ERR_BAD_DATA; + break; + } + if (c < 192) + { + a->size = c; + a->partial = 2; + if (!a->size) + { + a->eof = 1; + if (!n) + rc = -1; + break; + } + } + else if (c < 224) + { + a->size = (c - 192) * 256; + if ((c = iobuf_get (chain)) == -1) + { + log_error + ("block_filter: 2nd length byte missing\n"); + rc = GPG_ERR_BAD_DATA; + break; + } + a->size += c + 192; + a->partial = 2; + if (!a->size) + { + a->eof = 1; + if (!n) + rc = -1; + break; + } + } + else if (c == 255) + { + a->size = iobuf_get (chain) << 24; + a->size |= iobuf_get (chain) << 16; + a->size |= iobuf_get (chain) << 8; + if ((c = iobuf_get (chain)) == -1) + { + log_error ("block_filter: invalid 4 byte length\n"); + rc = GPG_ERR_BAD_DATA; + break; + } + a->size |= c; + } + else + { /* next partial body length */ + a->size = 1 << (c & 0x1f); + } + /* log_debug("partial: ctx=%p c=%02x size=%u\n", a, c, a->size); */ + } + else + { /* the gnupg partial length scheme - much better :-) */ + c = iobuf_get (chain); + a->size = c << 8; + c = iobuf_get (chain); + a->size |= c; + if (c == -1) + { + log_error ("block_filter: error reading length info\n"); + rc = GPG_ERR_BAD_DATA; + } + if (!a->size) + { + a->eof = 1; + if (!n) + rc = -1; + break; + } + } + } + + while (!rc && size && a->size) + { + needed = size < a->size ? size : a->size; + c = iobuf_read (chain, p, needed); + if (c < needed) + { + if (c == -1) + c = 0; + log_error + ("block_filter %p: read error (size=%lu,a->size=%lu)\n", + a, (ulong) size + c, (ulong) a->size + c); + rc = GPG_ERR_BAD_DATA; + } + else + { + size -= c; + a->size -= c; + p += c; + n += c; + } + } + } + *ret_len = n; + } + else if (control == IOBUFCTRL_FLUSH) + { + if (a->partial) + { /* the complicated openpgp scheme */ + size_t blen, n, nbytes = size + a->buflen; + + assert (a->buflen <= OP_MIN_PARTIAL_CHUNK); + if (nbytes < OP_MIN_PARTIAL_CHUNK) + { + /* not enough to write a partial block out; so we store it */ + if (!a->buffer) + a->buffer = xmalloc (OP_MIN_PARTIAL_CHUNK); + memcpy (a->buffer + a->buflen, buf, size); + a->buflen += size; + } + else + { /* okay, we can write out something */ + /* do this in a loop to use the most efficient block lengths */ + p = buf; + do + { + /* find the best matching block length - this is limited + * by the size of the internal buffering */ + for (blen = OP_MIN_PARTIAL_CHUNK * 2, + c = OP_MIN_PARTIAL_CHUNK_2POW + 1; blen <= nbytes; + blen *= 2, c++) + ; + blen /= 2; + c--; + /* write the partial length header */ + assert (c <= 0x1f); /*;-) */ + c |= 0xe0; + iobuf_put (chain, c); + if ((n = a->buflen)) + { /* write stuff from the buffer */ + assert (n == OP_MIN_PARTIAL_CHUNK); + if (iobuf_write (chain, a->buffer, n)) + rc = gpg_error_from_errno (errno); + a->buflen = 0; + nbytes -= n; + } + if ((n = nbytes) > blen) + n = blen; + if (n && iobuf_write (chain, p, n)) + rc = gpg_error_from_errno (errno); + p += n; + nbytes -= n; + } + while (!rc && nbytes >= OP_MIN_PARTIAL_CHUNK); + /* store the rest in the buffer */ + if (!rc && nbytes) + { + assert (!a->buflen); + assert (nbytes < OP_MIN_PARTIAL_CHUNK); + if (!a->buffer) + a->buffer = xmalloc (OP_MIN_PARTIAL_CHUNK); + memcpy (a->buffer, p, nbytes); + a->buflen = nbytes; + } + } + } + else + { /* the gnupg scheme (which is not openpgp compliant) */ + size_t avail, n; + + for (p = buf; !rc && size;) + { + n = size; + avail = a->size - a->count; + if (!avail) + { + if (n > a->size) + { + iobuf_put (chain, (a->size >> 8) & 0xff); + iobuf_put (chain, a->size & 0xff); + avail = a->size; + a->count = 0; + } + else + { + iobuf_put (chain, (n >> 8) & 0xff); + iobuf_put (chain, n & 0xff); + avail = n; + a->count = a->size - n; + } + } + if (n > avail) + n = avail; + if (iobuf_write (chain, p, n)) + rc = gpg_error_from_errno (errno); + a->count += n; + p += n; + size -= n; + } + } + } + else if (control == IOBUFCTRL_INIT) + { + if (DBG_IOBUF) + log_debug ("init block_filter %p\n", a); + if (a->partial) + a->count = 0; + else if (a->use == 1) + a->count = a->size = 0; + else + a->count = a->size; /* force first length bytes */ + a->eof = 0; + a->buffer = NULL; + a->buflen = 0; + } + else if (control == IOBUFCTRL_DESC) + { + *(char **) buf = "block_filter"; + } + else if (control == IOBUFCTRL_FREE) + { + if (a->use == 2) + { /* write the end markers */ + if (a->partial) + { + u32 len; + /* write out the remaining bytes without a partial header + * the length of this header may be 0 - but if it is + * the first block we are not allowed to use a partial header + * and frankly we can't do so, because this length must be + * a power of 2. This is _really_ complicated because we + * have to check the possible length of a packet prior + * to it's creation: a chain of filters becomes complicated + * and we need a lot of code to handle compressed packets etc. + * :-((((((( + */ + /* construct header */ + len = a->buflen; + /*log_debug("partial: remaining length=%u\n", len ); */ + if (len < 192) + rc = iobuf_put (chain, len); + else if (len < 8384) + { + if (!(rc = iobuf_put (chain, ((len - 192) / 256) + 192))) + rc = iobuf_put (chain, ((len - 192) % 256)); + } + else + { /* use a 4 byte header */ + if (!(rc = iobuf_put (chain, 0xff))) + if (!(rc = iobuf_put (chain, (len >> 24) & 0xff))) + if (!(rc = iobuf_put (chain, (len >> 16) & 0xff))) + if (!(rc = iobuf_put (chain, (len >> 8) & 0xff))) + rc = iobuf_put (chain, len & 0xff); + } + if (!rc && len) + rc = iobuf_write (chain, a->buffer, len); + if (rc) + { + log_error ("block_filter: write error: %s\n", + strerror (errno)); + rc = gpg_error_from_errno (errno); + } + xfree (a->buffer); + a->buffer = NULL; + a->buflen = 0; + } + else + { + iobuf_writebyte (chain, 0); + iobuf_writebyte (chain, 0); + } + } + else if (a->size) + { + log_error ("block_filter: pending bytes!\n"); + } + if (DBG_IOBUF) + log_debug ("free block_filter %p\n", a); + xfree (a); /* we can free our context now */ + } + + return rc; +} + + +static void +print_chain (iobuf_t a) +{ + if (!DBG_IOBUF) + return; + for (; a; a = a->chain) + { + size_t dummy_len = 0; + const char *desc = "[none]"; + + if (a->filter) + a->filter (a->filter_ov, IOBUFCTRL_DESC, NULL, + (byte *) & desc, &dummy_len); + + log_debug ("iobuf chain: %d.%d `%s' filter_eof=%d start=%d len=%d\n", + a->no, a->subno, desc, a->filter_eof, + (int) a->d.start, (int) a->d.len); + } +} + +int +iobuf_print_chain (iobuf_t a) +{ + print_chain (a); + return 0; +} + +/**************** + * Allocate a new io buffer, with no function assigned. + * Use is the desired usage: 1 for input, 2 for output, 3 for temp buffer + * BUFSIZE is a suggested buffer size. + */ +iobuf_t +iobuf_alloc (int use, size_t bufsize) +{ + iobuf_t a; + static int number = 0; + + a = xcalloc (1, sizeof *a); + a->use = use; + a->d.buf = xmalloc (bufsize); + a->d.size = bufsize; + a->no = ++number; + a->subno = 0; + a->opaque = NULL; + a->real_fname = NULL; + return a; +} + +int +iobuf_close (iobuf_t a) +{ + iobuf_t a2; + size_t dummy_len = 0; + int rc = 0; + + if (a && a->directfp) + { + fclose (a->directfp); + xfree (a->real_fname); + if (DBG_IOBUF) + log_debug ("iobuf_close -> %p\n", a->directfp); + return 0; + } + + for (; a && !rc; a = a2) + { + a2 = a->chain; + if (a->use == 2 && (rc = iobuf_flush (a))) + log_error ("iobuf_flush failed on close: %s\n", gpg_strerror (rc)); + + if (DBG_IOBUF) + log_debug ("iobuf-%d.%d: close `%s'\n", a->no, a->subno, a->desc); + if (a->filter && (rc = a->filter (a->filter_ov, IOBUFCTRL_FREE, + a->chain, NULL, &dummy_len))) + log_error ("IOBUFCTRL_FREE failed on close: %s\n", gpg_strerror (rc)); + xfree (a->real_fname); + if (a->d.buf) + { + memset (a->d.buf, 0, a->d.size); /* erase the buffer */ + xfree (a->d.buf); + } + xfree (a); + } + return rc; +} + +int +iobuf_cancel (iobuf_t a) +{ + const char *s; + iobuf_t a2; + int rc; +#if defined(HAVE_DOSISH_SYSTEM) || defined(__riscos__) + char *remove_name = NULL; +#endif + + if (a && a->use == 2) + { + s = iobuf_get_real_fname (a); + if (s && *s) + { +#if defined(HAVE_DOSISH_SYSTEM) || defined(__riscos__) + remove_name = m_strdup (s); +#else + remove (s); +#endif + } + } + + /* send a cancel message to all filters */ + for (a2 = a; a2; a2 = a2->chain) + { + size_t dummy; + if (a2->filter) + a2->filter (a2->filter_ov, IOBUFCTRL_CANCEL, a2->chain, NULL, &dummy); + } + + rc = iobuf_close (a); +#if defined(HAVE_DOSISH_SYSTEM) || defined(__riscos__) + if (remove_name) + { + /* Argg, MSDOS does not allow to remove open files. So + * we have to do it here */ + remove (remove_name); + xfree (remove_name); + } +#endif + return rc; +} + + +/**************** + * create a temporary iobuf, which can be used to collect stuff + * in an iobuf and later be written by iobuf_write_temp() to another + * iobuf. + */ +iobuf_t +iobuf_temp () +{ + iobuf_t a; + + a = iobuf_alloc (3, 8192); + + return a; +} + +iobuf_t +iobuf_temp_with_content (const char *buffer, size_t length) +{ + iobuf_t a; + + a = iobuf_alloc (3, length); + memcpy (a->d.buf, buffer, length); + a->d.len = length; + + return a; +} + +void +iobuf_enable_special_filenames (int yes) +{ + special_names_enabled = yes; +} + +/* + * see whether the filename has the for "-&nnnn", where n is a + * non-zero number. + * Returns this number or -1 if it is not the case. + */ +static int +check_special_filename (const char *fname) +{ + if (special_names_enabled && fname && *fname == '-' && fname[1] == '&') + { + int i; + + fname += 2; + for (i = 0; isdigit (fname[i]); i++) + ; + if (!fname[i]) + return atoi (fname); + } + return -1; +} + +/**************** + * Create a head iobuf for reading from a file + * returns: NULL if an error occures and sets errno + */ +iobuf_t +iobuf_open (const char *fname) +{ + iobuf_t a; + FILEP_OR_FD fp; + file_filter_ctx_t *fcx; + size_t len; + int print_only = 0; + int fd; + + if (!fname || (*fname == '-' && !fname[1])) + { + fp = FILEP_OR_FD_FOR_STDIN; +#ifdef USE_SETMODE + setmode (my_fileno (fp), O_BINARY); +#endif + fname = "[stdin]"; + print_only = 1; + } + else if ((fd = check_special_filename (fname)) != -1) + return iobuf_fdopen (translate_file_handle (fd, 0), "rb"); + else if ((fp = my_fopen_ro (fname, "rb")) == INVALID_FP) + return NULL; + a = iobuf_alloc (1, 8192); + fcx = xmalloc (sizeof *fcx + strlen (fname)); + fcx->fp = fp; + fcx->print_only_name = print_only; + strcpy (fcx->fname, fname); + if (!print_only) + a->real_fname = xstrdup (fname); + a->filter = file_filter; + a->filter_ov = fcx; + file_filter (fcx, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &len); + file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len); + if (DBG_IOBUF) + log_debug ("iobuf-%d.%d: open `%s' fd=%d\n", + a->no, a->subno, fname, (int) my_fileno (fcx->fp)); + + return a; +} + +/**************** + * Create a head iobuf for reading from a file + * returns: NULL if an error occures and sets errno + */ +iobuf_t +iobuf_fdopen (int fd, const char *mode) +{ + iobuf_t a; + FILEP_OR_FD fp; + file_filter_ctx_t *fcx; + size_t len; + +#ifdef FILE_FILTER_USES_STDIO + if (!(fp = fdopen (fd, mode))) + return NULL; +#else + fp = (FILEP_OR_FD) fd; +#endif + a = iobuf_alloc (strchr (mode, 'w') ? 2 : 1, 8192); + fcx = xmalloc (sizeof *fcx + 20); + fcx->fp = fp; + fcx->print_only_name = 1; + sprintf (fcx->fname, "[fd %d]", fd); + a->filter = file_filter; + a->filter_ov = fcx; + file_filter (fcx, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &len); + file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len); + if (DBG_IOBUF) + log_debug ("iobuf-%d.%d: fdopen `%s'\n", a->no, a->subno, fcx->fname); + iobuf_ioctl (a, 3, 1, NULL); /* disable fd caching */ + return a; +} + + +iobuf_t +iobuf_sockopen (int fd, const char *mode) +{ + iobuf_t a; +#ifdef __MINGW32__ + sock_filter_ctx_t *scx; + size_t len; + + a = iobuf_alloc (strchr (mode, 'w') ? 2 : 1, 8192); + scx = m_alloc (sizeof *scx + 25); + scx->sock = fd; + scx->print_only_name = 1; + sprintf (scx->fname, "[sock %d]", fd); + a->filter = sock_filter; + a->filter_ov = scx; + sock_filter (scx, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &len); + sock_filter (scx, IOBUFCTRL_INIT, NULL, NULL, &len); + if (DBG_IOBUF) + log_debug ("iobuf-%d.%d: sockopen `%s'\n", a->no, a->subno, scx->fname); + iobuf_ioctl (a, 3, 1, NULL); /* disable fd caching */ +#else + a = iobuf_fdopen (fd, mode); +#endif + return a; +} + +/**************** + * create an iobuf for writing to a file; the file will be created. + */ +iobuf_t +iobuf_create (const char *fname) +{ + iobuf_t a; + FILEP_OR_FD fp; + file_filter_ctx_t *fcx; + size_t len; + int print_only = 0; + int fd; + + if (!fname || (*fname == '-' && !fname[1])) + { + fp = FILEP_OR_FD_FOR_STDOUT; +#ifdef USE_SETMODE + setmode (my_fileno (fp), O_BINARY); +#endif + fname = "[stdout]"; + print_only = 1; + } + else if ((fd = check_special_filename (fname)) != -1) + return iobuf_fdopen (translate_file_handle (fd, 1), "wb"); + else if ((fp = my_fopen (fname, "wb")) == INVALID_FP) + return NULL; + a = iobuf_alloc (2, 8192); + fcx = xmalloc (sizeof *fcx + strlen (fname)); + fcx->fp = fp; + fcx->print_only_name = print_only; + strcpy (fcx->fname, fname); + if (!print_only) + a->real_fname = xstrdup (fname); + a->filter = file_filter; + a->filter_ov = fcx; + file_filter (fcx, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &len); + file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len); + if (DBG_IOBUF) + log_debug ("iobuf-%d.%d: create `%s'\n", a->no, a->subno, a->desc); + + return a; +} + +/**************** + * append to an iobuf; if the file does not exist, create it. + * cannot be used for stdout. + * Note: This is not used. + */ +#if 0 /* not used */ +iobuf_t +iobuf_append (const char *fname) +{ + iobuf_t a; + FILE *fp; + file_filter_ctx_t *fcx; + size_t len; + + if (!fname) + return NULL; + else if (!(fp = my_fopen (fname, "ab"))) + return NULL; + a = iobuf_alloc (2, 8192); + fcx = m_alloc (sizeof *fcx + strlen (fname)); + fcx->fp = fp; + strcpy (fcx->fname, fname); + a->real_fname = m_strdup (fname); + a->filter = file_filter; + a->filter_ov = fcx; + file_filter (fcx, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &len); + file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len); + if (DBG_IOBUF) + log_debug ("iobuf-%d.%d: append `%s'\n", a->no, a->subno, a->desc); + + return a; +} +#endif + +iobuf_t +iobuf_openrw (const char *fname) +{ + iobuf_t a; + FILEP_OR_FD fp; + file_filter_ctx_t *fcx; + size_t len; + + if (!fname) + return NULL; + else if ((fp = my_fopen (fname, "r+b")) == INVALID_FP) + return NULL; + a = iobuf_alloc (2, 8192); + fcx = xmalloc (sizeof *fcx + strlen (fname)); + fcx->fp = fp; + strcpy (fcx->fname, fname); + a->real_fname = xstrdup (fname); + a->filter = file_filter; + a->filter_ov = fcx; + file_filter (fcx, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &len); + file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len); + if (DBG_IOBUF) + log_debug ("iobuf-%d.%d: openrw `%s'\n", a->no, a->subno, a->desc); + + return a; +} + + +int +iobuf_ioctl (iobuf_t a, int cmd, int intval, void *ptrval) +{ + if (cmd == 1) + { /* keep system filepointer/descriptor open */ + if (DBG_IOBUF) + log_debug ("iobuf-%d.%d: ioctl `%s' keep=%d\n", + a ? a->no : -1, a ? a->subno : -1, a ? a->desc : "?", + intval); + for (; a; a = a->chain) + if (!a->chain && a->filter == file_filter) + { + file_filter_ctx_t *b = a->filter_ov; + b->keep_open = intval; + return 0; + } +#ifdef __MINGW32__ + else if (!a->chain && a->filter == sock_filter) + { + sock_filter_ctx_t *b = a->filter_ov; + b->keep_open = intval; + return 0; + } +#endif + } + else if (cmd == 2) + { /* invalidate cache */ + if (DBG_IOBUF) + log_debug ("iobuf-*.*: ioctl `%s' invalidate\n", + ptrval ? (char *) ptrval : "?"); + if (!a && !intval && ptrval) + { +#ifndef FILE_FILTER_USES_STDIO + fd_cache_invalidate (ptrval); +#endif + return 0; + } + } + else if (cmd == 3) + { /* disallow/allow caching */ + if (DBG_IOBUF) + log_debug ("iobuf-%d.%d: ioctl `%s' no_cache=%d\n", + a ? a->no : -1, a ? a->subno : -1, a ? a->desc : "?", + intval); + for (; a; a = a->chain) + if (!a->chain && a->filter == file_filter) + { + file_filter_ctx_t *b = a->filter_ov; + b->no_cache = intval; + return 0; + } +#ifdef __MINGW32__ + else if (!a->chain && a->filter == sock_filter) + { + sock_filter_ctx_t *b = a->filter_ov; + b->no_cache = intval; + return 0; + } +#endif + } + + return -1; +} + + +/**************** + * Register an i/o filter. + */ +int +iobuf_push_filter (iobuf_t a, + int (*f) (void *opaque, int control, + iobuf_t chain, byte * buf, size_t * len), + void *ov) +{ + return iobuf_push_filter2 (a, f, ov, 0); +} + +int +iobuf_push_filter2 (iobuf_t a, + int (*f) (void *opaque, int control, + iobuf_t chain, byte * buf, size_t * len), + void *ov, int rel_ov) +{ + iobuf_t b; + size_t dummy_len = 0; + int rc = 0; + + if (a->directfp) + BUG (); + + if (a->use == 2 && (rc = iobuf_flush (a))) + return rc; + /* make a copy of the current stream, so that + * A is the new stream and B the original one. + * The contents of the buffers are transferred to the + * new stream. + */ + b = xmalloc (sizeof *b); + memcpy (b, a, sizeof *b); + /* fixme: it is stupid to keep a copy of the name at every level + * but we need the name somewhere because the name known by file_filter + * may have been released when we need the name of the file */ + b->real_fname = a->real_fname ? xstrdup (a->real_fname) : NULL; + /* remove the filter stuff from the new stream */ + a->filter = NULL; + a->filter_ov = NULL; + a->filter_ov_owner = 0; + a->filter_eof = 0; + if (a->use == 3) + a->use = 2; /* make a write stream from a temp stream */ + + if (a->use == 2) + { /* allocate a fresh buffer for the + original stream */ + b->d.buf = xmalloc (a->d.size); + b->d.len = 0; + b->d.start = 0; + } + else + { /* allocate a fresh buffer for the new + stream */ + a->d.buf = xmalloc (a->d.size); + a->d.len = 0; + a->d.start = 0; + } + /* disable nlimit for the new stream */ + a->ntotal = b->ntotal + b->nbytes; + a->nlimit = a->nbytes = 0; + a->nofast &= ~1; + /* make a link from the new stream to the original stream */ + a->chain = b; + a->opaque = b->opaque; + + /* setup the function on the new stream */ + a->filter = f; + a->filter_ov = ov; + a->filter_ov_owner = rel_ov; + + a->subno = b->subno + 1; + f (ov, IOBUFCTRL_DESC, NULL, (byte *) & a->desc, &dummy_len); + + if (DBG_IOBUF) + { + log_debug ("iobuf-%d.%d: push `%s'\n", a->no, a->subno, a->desc); + print_chain (a); + } + + /* now we can initialize the new function if we have one */ + if (a->filter && (rc = a->filter (a->filter_ov, IOBUFCTRL_INIT, a->chain, + NULL, &dummy_len))) + log_error ("IOBUFCTRL_INIT failed: %s\n", gpg_strerror (rc)); + return rc; +} + +/**************** + * Remove an i/o filter. + */ +int +pop_filter (iobuf_t a, int (*f) (void *opaque, int control, + iobuf_t chain, byte * buf, size_t * len), + void *ov) +{ + iobuf_t b; + size_t dummy_len = 0; + int rc = 0; + + if (a->directfp) + BUG (); + + if (DBG_IOBUF) + log_debug ("iobuf-%d.%d: pop `%s'\n", a->no, a->subno, a->desc); + if (!a->filter) + { /* this is simple */ + b = a->chain; + assert (b); + xfree (a->d.buf); + xfree (a->real_fname); + memcpy (a, b, sizeof *a); + xfree (b); + return 0; + } + for (b = a; b; b = b->chain) + if (b->filter == f && (!ov || b->filter_ov == ov)) + break; + if (!b) + log_bug ("pop_filter(): filter function not found\n"); + + /* flush this stream if it is an output stream */ + if (a->use == 2 && (rc = iobuf_flush (b))) + { + log_error ("iobuf_flush failed in pop_filter: %s\n", gpg_strerror (rc)); + return rc; + } + /* and tell the filter to free it self */ + if (b->filter && (rc = b->filter (b->filter_ov, IOBUFCTRL_FREE, b->chain, + NULL, &dummy_len))) + { + log_error ("IOBUFCTRL_FREE failed: %s\n", gpg_strerror (rc)); + return rc; + } + if (b->filter_ov && b->filter_ov_owner) + { + xfree (b->filter_ov); + b->filter_ov = NULL; + } + + + /* and see how to remove it */ + if (a == b && !b->chain) + log_bug ("can't remove the last filter from the chain\n"); + else if (a == b) + { /* remove the first iobuf from the chain */ + /* everything from b is copied to a. This is save because + * a flush has been done on the to be removed entry + */ + b = a->chain; + xfree (a->d.buf); + xfree (a->real_fname); + memcpy (a, b, sizeof *a); + xfree (b); + if (DBG_IOBUF) + log_debug ("iobuf-%d.%d: popped filter\n", a->no, a->subno); + } + else if (!b->chain) + { /* remove the last iobuf from the chain */ + log_bug ("Ohh jeee, trying to remove a head filter\n"); + } + else + { /* remove an intermediate iobuf from the chain */ + log_bug ("Ohh jeee, trying to remove an intermediate filter\n"); + } + + return rc; +} + + +/**************** + * read underflow: read more bytes into the buffer and return + * the first byte or -1 on EOF. + */ +static int +underflow (iobuf_t a) +{ + size_t len; + int rc; + + assert (a->d.start == a->d.len); + if (a->use == 3) + return -1; /* EOF because a temp buffer can't do an underflow */ + + if (a->filter_eof) + { + if (a->chain) + { + iobuf_t b = a->chain; + if (DBG_IOBUF) + log_debug ("iobuf-%d.%d: pop `%s' in underflow\n", + a->no, a->subno, a->desc); + xfree (a->d.buf); + xfree (a->real_fname); + memcpy (a, b, sizeof *a); + xfree (b); + print_chain (a); + } + else + a->filter_eof = 0; /* for the top level filter */ + if (DBG_IOBUF) + log_debug ("iobuf-%d.%d: underflow: eof (due to filter eof)\n", + a->no, a->subno); + return -1; /* return one(!) EOF */ + } + if (a->error) + { + if (DBG_IOBUF) + log_debug ("iobuf-%d.%d: error\n", a->no, a->subno); + return -1; + } + + if (a->directfp) + { + FILE *fp = a->directfp; + + len = fread (a->d.buf, 1, a->d.size, fp); + if (len < a->d.size) + { + if (ferror (fp)) + a->error = gpg_error_from_errno (errno); + } + a->d.len = len; + a->d.start = 0; + return len ? a->d.buf[a->d.start++] : -1; + } + + + if (a->filter) + { + len = a->d.size; + if (DBG_IOBUF) + log_debug ("iobuf-%d.%d: underflow: req=%lu\n", + a->no, a->subno, (ulong) len); + rc = a->filter (a->filter_ov, IOBUFCTRL_UNDERFLOW, a->chain, + a->d.buf, &len); + if (DBG_IOBUF) + { + log_debug ("iobuf-%d.%d: underflow: got=%lu rc=%d\n", + a->no, a->subno, (ulong) len, rc); +/* if( a->no == 1 ) */ +/* log_hexdump (" data:", a->d.buf, len); */ + } + if (a->use == 1 && rc == -1) + { /* EOF: we can remove the filter */ + size_t dummy_len = 0; + + /* and tell the filter to free itself */ + if ((rc = a->filter (a->filter_ov, IOBUFCTRL_FREE, a->chain, + NULL, &dummy_len))) + log_error ("IOBUFCTRL_FREE failed: %s\n", gpg_strerror (rc)); + if (a->filter_ov && a->filter_ov_owner) + { + xfree (a->filter_ov); + a->filter_ov = NULL; + } + a->filter = NULL; + a->desc = NULL; + a->filter_ov = NULL; + a->filter_eof = 1; + if (!len && a->chain) + { + iobuf_t b = a->chain; + if (DBG_IOBUF) + log_debug ("iobuf-%d.%d: pop `%s' in underflow (!len)\n", + a->no, a->subno, a->desc); + xfree (a->d.buf); + xfree (a->real_fname); + memcpy (a, b, sizeof *a); + xfree (b); + print_chain (a); + } + } + else if (rc) + a->error = rc; + + if (!len) + { + if (DBG_IOBUF) + log_debug ("iobuf-%d.%d: underflow: eof\n", a->no, a->subno); + return -1; + } + a->d.len = len; + a->d.start = 0; + return a->d.buf[a->d.start++]; + } + else + { + if (DBG_IOBUF) + log_debug ("iobuf-%d.%d: underflow: eof (no filter)\n", + a->no, a->subno); + return -1; /* no filter; return EOF */ + } +} + + +int +iobuf_flush (iobuf_t a) +{ + size_t len; + int rc; + + if (a->directfp) + return 0; + + if (a->use == 3) + { /* increase the temp buffer */ + char *newbuf; + size_t newsize = a->d.size + 8192; + + if (DBG_IOBUF) + log_debug ("increasing temp iobuf from %lu to %lu\n", + (ulong) a->d.size, (ulong) newsize); + newbuf = xmalloc (newsize); + memcpy (newbuf, a->d.buf, a->d.len); + xfree (a->d.buf); + a->d.buf = newbuf; + a->d.size = newsize; + return 0; + } + else if (a->use != 2) + log_bug ("flush on non-output iobuf\n"); + else if (!a->filter) + log_bug ("iobuf_flush: no filter\n"); + len = a->d.len; + rc = a->filter (a->filter_ov, IOBUFCTRL_FLUSH, a->chain, a->d.buf, &len); + if (!rc && len != a->d.len) + { + log_info ("iobuf_flush did not write all!\n"); + rc = GPG_ERR_INTERNAL; + } + else if (rc) + a->error = rc; + a->d.len = 0; + + return rc; +} + + +/**************** + * Read a byte from the iobuf; returns -1 on EOF + */ +int +iobuf_readbyte (iobuf_t a) +{ + int c; + + /* nlimit does not work together with unget */ + /* nbytes is also not valid! */ + if (a->unget.buf) + { + if (a->unget.start < a->unget.len) + return a->unget.buf[a->unget.start++]; + xfree (a->unget.buf); + a->unget.buf = NULL; + a->nofast &= ~2; + } + + if (a->nlimit && a->nbytes >= a->nlimit) + return -1; /* forced EOF */ + + if (a->d.start < a->d.len) + { + c = a->d.buf[a->d.start++]; + } + else if ((c = underflow (a)) == -1) + return -1; /* EOF */ + + a->nbytes++; + return c; +} + + +int +iobuf_read (iobuf_t a, byte * buf, unsigned buflen) +{ + int c, n; + + if (a->unget.buf || a->nlimit) + { + /* handle special cases */ + for (n = 0; n < buflen; n++) + { + if ((c = iobuf_readbyte (a)) == -1) + { + if (!n) + return -1; /* eof */ + break; + } + else if (buf) + *buf = c; + if (buf) + buf++; + } + return n; + } + + n = 0; + do + { + if (n < buflen && a->d.start < a->d.len) + { + unsigned size = a->d.len - a->d.start; + if (size > buflen - n) + size = buflen - n; + if (buf) + memcpy (buf, a->d.buf + a->d.start, size); + n += size; + a->d.start += size; + if (buf) + buf += size; + } + if (n < buflen) + { + if ((c = underflow (a)) == -1) + { + a->nbytes += n; + return n ? n : -1 /*EOF*/; + } + if (buf) + *buf++ = c; + n++; + } + } + while (n < buflen); + a->nbytes += n; + return n; +} + + +/**************** + * Have a look at the iobuf. + * NOTE: This only works in special cases. + */ +int +iobuf_peek (iobuf_t a, byte * buf, unsigned buflen) +{ + int n = 0; + + if (a->filter_eof) + return -1; + + if (!(a->d.start < a->d.len)) + { + if (underflow (a) == -1) + return -1; + /* and unget this character */ + assert (a->d.start == 1); + a->d.start = 0; + } + + for (n = 0; n < buflen && (a->d.start + n) < a->d.len; n++, buf++) + *buf = a->d.buf[n]; + return n; +} + + + + +int +iobuf_writebyte (iobuf_t a, unsigned c) +{ + int rc; + + if (a->directfp) + BUG (); + + if (a->d.len == a->d.size) + if ((rc=iobuf_flush (a))) + return rc; + + assert (a->d.len < a->d.size); + a->d.buf[a->d.len++] = c; + return 0; +} + + +int +iobuf_write (iobuf_t a, byte * buf, unsigned buflen) +{ + int rc; + + if (a->directfp) + BUG (); + + do + { + if (buflen && a->d.len < a->d.size) + { + unsigned size = a->d.size - a->d.len; + if (size > buflen) + size = buflen; + memcpy (a->d.buf + a->d.len, buf, size); + buflen -= size; + buf += size; + a->d.len += size; + } + if (buflen) + { + rc = iobuf_flush (a); + if (rc) + return rc; + } + } + while (buflen); + return 0; +} + + +int +iobuf_writestr (iobuf_t a, const char *buf) +{ + int rc; + + for (; *buf; buf++) + if ((rc=iobuf_writebyte (a, *buf))) + return rc; + return 0; +} + + + +/**************** + * copy the contents of TEMP to A. + */ +int +iobuf_write_temp (iobuf_t a, iobuf_t temp) +{ + while (temp->chain) + pop_filter (temp, temp->filter, NULL); + return iobuf_write (a, temp->d.buf, temp->d.len); +} + +/**************** + * copy the contents of the temp io stream to BUFFER. + */ +size_t +iobuf_temp_to_buffer (iobuf_t a, byte * buffer, size_t buflen) +{ + size_t n = a->d.len; + + if (n > buflen) + n = buflen; + memcpy (buffer, a->d.buf, n); + return n; +} + + +/**************** + * Call this function to terminate processing of the temp stream + * without closing it. This removes all filters from the stream + * makes sure that iobuf_get_temp_{buffer,length}() returns correct + * values. + */ +void +iobuf_flush_temp (iobuf_t temp) +{ + while (temp->chain) + pop_filter (temp, temp->filter, NULL); +} + + +/**************** + * Set a limit on how many bytes may be read from the input stream A. + * Setting the limit to 0 disables this feature. + */ +void +iobuf_set_limit (iobuf_t a, off_t nlimit) +{ + if (nlimit) + a->nofast |= 1; + else + a->nofast &= ~1; + a->nlimit = nlimit; + a->ntotal += a->nbytes; + a->nbytes = 0; +} + + + +/**************** + * Return the length of an open file + */ +off_t +iobuf_get_filelength (iobuf_t a) +{ + struct stat st; + + if (a->directfp) + { + FILE *fp = a->directfp; + + if (!fstat (fileno (fp), &st)) + return st.st_size; + log_error ("fstat() failed: %s\n", strerror (errno)); + return 0; + } + + /* Hmmm: file_filter may have already been removed */ + for (; a; a = a->chain) + if (!a->chain && a->filter == file_filter) + { + file_filter_ctx_t *b = a->filter_ov; + FILEP_OR_FD fp = b->fp; + +#if defined(HAVE_DOSISH_SYSTEM) && !defined(FILE_FILTER_USES_STDIO) + ulong size; + + if ((size = GetFileSize (fp, NULL)) != 0xffffffff) + return size; + log_error ("GetFileSize for handle %p failed: ec=%d\n", + fp, (int) GetLastError ()); +#else + if (!fstat (my_fileno (fp), &st)) + return st.st_size; + log_error ("fstat() failed: %s\n", strerror (errno)); +#endif + break; + } + + return 0; +} + +/**************** + * Tell the file position, where the next read will take place + */ +off_t +iobuf_tell (iobuf_t a) +{ + return a->ntotal + a->nbytes; +} + + +#if !defined(HAVE_FSEEKO) && !defined(fseeko) + +#ifdef HAVE_LIMITS_H +# include +#endif +#ifndef LONG_MAX +# define LONG_MAX ((long) ((unsigned long) -1 >> 1)) +#endif +#ifndef LONG_MIN +# define LONG_MIN (-1 - LONG_MAX) +#endif + +/**************** + * A substitute for fseeko, for hosts that don't have it. + */ +static int +fseeko (FILE * stream, off_t newpos, int whence) +{ + while (newpos != (long) newpos) + { + long pos = newpos < 0 ? LONG_MIN : LONG_MAX; + if (fseek (stream, pos, whence) != 0) + return -1; + newpos -= pos; + whence = SEEK_CUR; + } + return fseek (stream, (long) newpos, whence); +} +#endif + +/**************** + * This is a very limited implementation. It simply discards all internal + * buffering and removes all filters but the first one. + */ +int +iobuf_seek (iobuf_t a, off_t newpos) +{ + file_filter_ctx_t *b = NULL; + + if (a->directfp) + { + FILE *fp = a->directfp; + if (fseeko (fp, newpos, SEEK_SET)) + { + log_error ("can't seek: %s\n", strerror (errno)); + return -1; + } + clearerr (fp); + } + else + { + for (; a; a = a->chain) + { + if (!a->chain && a->filter == file_filter) + { + b = a->filter_ov; + break; + } + } + if (!a) + return -1; +#ifdef FILE_FILTER_USES_STDIO + if (fseeko (b->fp, newpos, SEEK_SET)) + { + log_error ("can't fseek: %s\n", strerror (errno)); + return -1; + } +#else +#ifdef HAVE_DOSISH_SYSTEM + if (SetFilePointer (b->fp, newpos, NULL, FILE_BEGIN) == 0xffffffff) + { + log_error ("SetFilePointer failed on handle %p: ec=%d\n", + b->fp, (int) GetLastError ()); + return -1; + } +#else + if (lseek (b->fp, newpos, SEEK_SET) == (off_t) - 1) + { + log_error ("can't lseek: %s\n", strerror (errno)); + return -1; + } +#endif +#endif + } + a->d.len = 0; /* discard buffer */ + a->d.start = 0; + a->nbytes = 0; + a->nlimit = 0; + a->nofast &= ~1; + a->ntotal = newpos; + a->error = 0; + /* remove filters, but the last */ + if (a->chain) + log_debug ("pop_filter called in iobuf_seek - please report\n"); + while (a->chain) + pop_filter (a, a->filter, NULL); + + return 0; +} + + + + + + +/**************** + * Retrieve the real filename + */ +const char * +iobuf_get_real_fname (iobuf_t a) +{ + if (a->real_fname) + return a->real_fname; + + /* the old solution */ + for (; a; a = a->chain) + if (!a->chain && a->filter == file_filter) + { + file_filter_ctx_t *b = a->filter_ov; + return b->print_only_name ? NULL : b->fname; + } + + return NULL; +} + + +/**************** + * Retrieve the filename + */ +const char * +iobuf_get_fname (iobuf_t a) +{ + for (; a; a = a->chain) + if (!a->chain && a->filter == file_filter) + { + file_filter_ctx_t *b = a->filter_ov; + return b->fname; + } + + return NULL; +} + +/**************** + * Start the block write mode, see rfc1991.new for details. + * A value of 0 for N stops this mode (flushes and writes + * the end marker) + */ +void +iobuf_set_block_mode (iobuf_t a, size_t n) +{ + block_filter_ctx_t *ctx = xcalloc (1, sizeof *ctx); + + assert (a->use == 1 || a->use == 2); + ctx->use = a->use; + if (!n) + { + if (a->use == 1) + log_debug ("pop_filter called in set_block_mode - please report\n"); + pop_filter (a, block_filter, NULL); + } + else + { + ctx->size = n; /* only needed for use 2 */ + iobuf_push_filter (a, block_filter, ctx); + } +} + +/**************** + * enable partial block mode as described in the OpenPGP draft. + * LEN is the first length byte on read, but ignored on writes. + */ +void +iobuf_set_partial_block_mode (iobuf_t a, size_t len) +{ + block_filter_ctx_t *ctx = xcalloc (1, sizeof *ctx); + + assert (a->use == 1 || a->use == 2); + ctx->use = a->use; + if (!len) + { + if (a->use == 1) + log_debug ("pop_filter called in set_partial_block_mode" + " - please report\n"); + pop_filter (a, block_filter, NULL); + } + else + { + ctx->partial = 1; + ctx->size = 0; + ctx->first_c = len; + iobuf_push_filter (a, block_filter, ctx); + } +} + + +/**************** + * Checks whether the stream is in block mode + * Note: This does not work if other filters are pushed on the stream. + */ +int +iobuf_in_block_mode (iobuf_t a) +{ + if (a && a->filter == block_filter) + return 1; /* yes */ + return 0; /* no */ +} + + +/**************** + * Same as fgets() but if the buffer is too short a larger one will + * be allocated up to some limit *max_length. + * A line is considered a byte stream ending in a LF. + * Returns the length of the line. EOF is indicated by a line of + * length zero. The last LF may be missing due to an EOF. + * is max_length is zero on return, the line has been truncated. + * + * Note: The buffer is allocated with enough space to append a CR,LF,EOL + */ +unsigned int +iobuf_read_line (iobuf_t a, byte ** addr_of_buffer, + unsigned *length_of_buffer, unsigned *max_length) +{ + int c; + char *buffer = *addr_of_buffer; + unsigned length = *length_of_buffer; + unsigned nbytes = 0; + unsigned maxlen = *max_length; + char *p; + + if (!buffer) + { /* must allocate a new buffer */ + length = 256; + buffer = xmalloc (length); + *addr_of_buffer = buffer; + *length_of_buffer = length; + } + + length -= 3; /* reserve 3 bytes (cr,lf,eol) */ + p = buffer; + while ((c = iobuf_get (a)) != -1) + { + if (nbytes == length) + { /* increase the buffer */ + if (length > maxlen) + { /* this is out limit */ + /* skip the rest of the line */ + while (c != '\n' && (c = iobuf_get (a)) != -1) + ; + *p++ = '\n'; /* always append a LF (we have reserved space) */ + nbytes++; + *max_length = 0; /* indicate truncation */ + break; + } + length += 3; /* correct for the reserved byte */ + length += length < 1024 ? 256 : 1024; + buffer = xrealloc (buffer, length); + *addr_of_buffer = buffer; + *length_of_buffer = length; + length -= 3; /* and reserve again */ + p = buffer + nbytes; + } + *p++ = c; + nbytes++; + if (c == '\n') + break; + } + *p = 0; /* make sure the line is a string */ + + return nbytes; +} + +/* This is the non iobuf specific function */ +int +iobuf_translate_file_handle (int fd, int for_write) +{ +#ifdef __MINGW32__ + { + int x; + + if (fd <= 2) + return fd; /* do not do this for error, stdin, stdout, stderr */ + + x = _open_osfhandle (fd, for_write ? 1 : 0); + if (x == -1) + log_error ("failed to translate osfhandle %p\n", (void *) fd); + else + { + /*log_info ("_open_osfhandle %p yields %d%s\n", + (void*)fd, x, for_write? " for writing":"" ); */ + fd = x; + } + } +#endif + return fd; +} + +static int +translate_file_handle (int fd, int for_write) +{ +#ifdef __MINGW32__ +#ifdef FILE_FILTER_USES_STDIO + fd = iobuf_translate_file_handle (fd, for_write); +#else + { + int x; + + if (fd == 0) + x = (int) GetStdHandle (STD_INPUT_HANDLE); + else if (fd == 1) + x = (int) GetStdHandle (STD_OUTPUT_HANDLE); + else if (fd == 2) + x = (int) GetStdHandle (STD_ERROR_HANDLE); + else + x = fd; + + if (x == -1) + log_debug ("GetStdHandle(%d) failed: ec=%d\n", + fd, (int) GetLastError ()); + + fd = x; + } +#endif +#endif + return fd; +} diff --git a/common/iobuf.h b/common/iobuf.h new file mode 100644 index 000000000..0af94e22d --- /dev/null +++ b/common/iobuf.h @@ -0,0 +1,170 @@ +/* iobuf.h - I/O buffer + * Copyright (C) 1998, 1999, 2000, 2001, 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef GNUPG_COMMON_IOBUF_H +#define GNUPG_COMMON_IOBUF_H + +#include "../include/types.h" /* fixme: should be moved elsewhere. */ + + +#define DBG_IOBUF iobuf_debug_mode + + +#define IOBUFCTRL_INIT 1 +#define IOBUFCTRL_FREE 2 +#define IOBUFCTRL_UNDERFLOW 3 +#define IOBUFCTRL_FLUSH 4 +#define IOBUFCTRL_DESC 5 +#define IOBUFCTRL_CANCEL 6 +#define IOBUFCTRL_USER 16 + +typedef struct iobuf_struct *iobuf_t; + +/* fixme: we should hide most of this stuff */ +struct iobuf_struct +{ + int use; /* 1 input , 2 output, 3 temp */ + off_t nlimit; + off_t nbytes; /* used together with nlimit */ + off_t ntotal; /* total bytes read (position of stream) */ + int nofast; /* used by the iobuf_get() */ + void *directfp; + struct + { + size_t size; /* allocated size */ + size_t start; /* number of invalid bytes at the begin of the buffer */ + size_t len; /* currently filled to this size */ + byte *buf; + } + d; + int filter_eof; + int error; + int (*filter) (void *opaque, int control, + iobuf_t chain, byte * buf, size_t * len); + void *filter_ov; /* value for opaque */ + int filter_ov_owner; + char *real_fname; + iobuf_t chain; /* next iobuf used for i/o if any + (passed to filter) */ + int no, subno; + const char *desc; + void *opaque; /* can be used to hold any information + this value is copied to all + instances */ + struct + { + size_t size; /* allocated size */ + size_t start; /* number of invalid bytes at the + begin of the buffer */ + size_t len; /* currently filled to this size */ + byte *buf; + } + unget; +}; + +#ifndef EXTERN_UNLESS_MAIN_MODULE +#if defined (__riscos__) && !defined (INCLUDED_BY_MAIN_MODULE) +#define EXTERN_UNLESS_MAIN_MODULE extern +#else +#define EXTERN_UNLESS_MAIN_MODULE +#endif +#endif +EXTERN_UNLESS_MAIN_MODULE int iobuf_debug_mode; + +void iobuf_enable_special_filenames (int yes); +iobuf_t iobuf_alloc (int use, size_t bufsize); +iobuf_t iobuf_temp (void); +iobuf_t iobuf_temp_with_content (const char *buffer, size_t length); +iobuf_t iobuf_open (const char *fname); +iobuf_t iobuf_fdopen (int fd, const char *mode); +iobuf_t iobuf_sockopen (int fd, const char *mode); +iobuf_t iobuf_create (const char *fname); +iobuf_t iobuf_append (const char *fname); +iobuf_t iobuf_openrw (const char *fname); +int iobuf_ioctl (iobuf_t a, int cmd, int intval, void *ptrval); +int iobuf_close (iobuf_t iobuf); +int iobuf_cancel (iobuf_t iobuf); + +int iobuf_push_filter (iobuf_t a, int (*f) (void *opaque, int control, + iobuf_t chain, byte * buf, + size_t * len), void *ov); +int iobuf_push_filter2 (iobuf_t a, + int (*f) (void *opaque, int control, iobuf_t chain, + byte * buf, size_t * len), void *ov, + int rel_ov); +int iobuf_flush (iobuf_t a); +void iobuf_clear_eof (iobuf_t a); +#define iobuf_set_error(a) do { (a)->error = 1; } while(0) +#define iobuf_error(a) ((a)->error) + +void iobuf_set_limit (iobuf_t a, off_t nlimit); + +off_t iobuf_tell (iobuf_t a); +int iobuf_seek (iobuf_t a, off_t newpos); + +int iobuf_readbyte (iobuf_t a); +int iobuf_read (iobuf_t a, byte * buf, unsigned buflen); +unsigned iobuf_read_line (iobuf_t a, byte ** addr_of_buffer, + unsigned *length_of_buffer, unsigned *max_length); +int iobuf_peek (iobuf_t a, byte * buf, unsigned buflen); +int iobuf_writebyte (iobuf_t a, unsigned c); +int iobuf_write (iobuf_t a, byte * buf, unsigned buflen); +int iobuf_writestr (iobuf_t a, const char *buf); + +void iobuf_flush_temp (iobuf_t temp); +int iobuf_write_temp (iobuf_t a, iobuf_t temp); +size_t iobuf_temp_to_buffer (iobuf_t a, byte * buffer, size_t buflen); +void iobuf_unget_and_close_temp (iobuf_t a, iobuf_t temp); + +off_t iobuf_get_filelength (iobuf_t a); +#define IOBUF_FILELENGTH_LIMIT 0xffffffff +const char *iobuf_get_real_fname (iobuf_t a); +const char *iobuf_get_fname (iobuf_t a); + +void iobuf_set_block_mode (iobuf_t a, size_t n); +void iobuf_set_partial_block_mode (iobuf_t a, size_t len); +int iobuf_in_block_mode (iobuf_t a); + +int iobuf_translate_file_handle (int fd, int for_write); + + +/* get a byte form the iobuf; must check for eof prior to this function + * this function returns values in the range 0 .. 255 or -1 to indicate EOF + * iobuf_get_noeof() does not return -1 to indicate EOF, but masks the + * returned value to be in the range 0 ..255. + */ +#define iobuf_get(a) \ + ( ((a)->nofast || (a)->d.start >= (a)->d.len )? \ + iobuf_readbyte((a)) : ( (a)->nbytes++, (a)->d.buf[(a)->d.start++] ) ) +#define iobuf_get_noeof(a) (iobuf_get((a))&0xff) + +/* write a byte to the iobuf and return true on write error + * This macro does only write the low order byte + */ +#define iobuf_put(a,c) iobuf_writebyte(a,c) + +#define iobuf_where(a) "[don't know]" +#define iobuf_id(a) ((a)->no) + +#define iobuf_get_temp_buffer(a) ( (a)->d.buf ) +#define iobuf_get_temp_length(a) ( (a)->d.len ) +#define iobuf_is_temp(a) ( (a)->use == 3 ) + +#endif /*GNUPG_COMMON_IOBUF_H*/ diff --git a/common/maperror.c b/common/maperror.c new file mode 100644 index 000000000..13657bece --- /dev/null +++ b/common/maperror.c @@ -0,0 +1,157 @@ +/* maperror.c - Error mapping + * Copyright (C) 2001, 2002 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "util.h" +#include "errors.h" + +/* Note: we might want to wrap this in a macro to get our hands on + the line and file where the error occured */ +int +map_ksba_err (int err) +{ + switch (err) + { + case -1: + case 0: + break; + + case KSBA_Out_Of_Core: err = GPG_ERR_ENOMEM; break; + case KSBA_Invalid_Value: err = GPG_ERR_INV_VALUE; break; + case KSBA_Not_Implemented: err = GPG_ERR_NOT_IMPLEMENTED; break; + case KSBA_Conflict: err = GPG_ERR_CONFLICT; break; + case KSBA_Read_Error: err = GPG_ERR_EIO; break; + case KSBA_Write_Error: err = GPG_ERR_EIO; break; + case KSBA_No_Data: err = GPG_ERR_NO_DATA; break; + case KSBA_Bug: err = GPG_ERR_BUG; break; + case KSBA_Unsupported_Algorithm: err = GPG_ERR_UNSUPPORTED_ALGORITHM; break; + case KSBA_Invalid_Index: err = GPG_ERR_INV_INDEX; break; + case KSBA_Invalid_Sexp: err = GPG_ERR_INV_SEXP; break; + case KSBA_Unknown_Sexp: err = GPG_ERR_UNKNOWN_SEXP; break; + + default: + err = GPG_ERR_GENERAL; + break; + } + return err; +} + + +int +map_gcry_err (int err) +{ + return err; +} + +int +map_kbx_err (int err) +{ + return err; +} + +/* Map Assuan error code ERR to an GPG_ERR_ code. We need to + distinguish between genuine (and legacy) Assuan error codes and + application error codes shared with all GnuPG modules. The rule is + simple: All errors with a gpg_err_source of UNKNOWN are genuine + Assuan codes all others are passed verbatim through. */ +gpg_error_t +map_assuan_err (int err) +{ + gpg_err_code_t ec; + + if (gpg_err_source (err)) + return err; + + switch (err) + { + case -1: ec = GPG_ERR_EOF; break; + case 0: ec = 0; break; + + case ASSUAN_Canceled: ec = GPG_ERR_CANCELED; break; + case ASSUAN_Invalid_Index: ec = GPG_ERR_INV_INDEX; break; + + case ASSUAN_Not_Implemented: ec = GPG_ERR_NOT_IMPLEMENTED; break; + case ASSUAN_Server_Fault: ec = GPG_ERR_ASSUAN_SERVER_FAULT; break; + case ASSUAN_No_Public_Key: ec = GPG_ERR_NO_PUBKEY; break; + case ASSUAN_No_Secret_Key: ec = GPG_ERR_NO_SECKEY; break; + + case ASSUAN_Cert_Revoked: ec = GPG_ERR_CERT_REVOKED; break; + case ASSUAN_No_CRL_For_Cert: ec = GPG_ERR_NO_CRL_KNOWN; break; + case ASSUAN_CRL_Too_Old: ec = GPG_ERR_CRL_TOO_OLD; break; + + case ASSUAN_Not_Trusted: ec = GPG_ERR_NOT_TRUSTED; break; + + case ASSUAN_Card_Error: ec = GPG_ERR_CARD; break; + case ASSUAN_Invalid_Card: ec = GPG_ERR_INV_CARD; break; + case ASSUAN_No_PKCS15_App: ec = GPG_ERR_NO_PKCS15_APP; break; + case ASSUAN_Card_Not_Present: ec= GPG_ERR_CARD_NOT_PRESENT; break; + case ASSUAN_Not_Confirmed: ec = GPG_ERR_NOT_CONFIRMED; break; + case ASSUAN_Invalid_Id: ec = GPG_ERR_INV_ID; break; + + default: + ec = err < 100? GPG_ERR_ASSUAN_SERVER_FAULT : GPG_ERR_ASSUAN; + break; + } + return gpg_err_make (GPG_ERR_SOURCE_UNKNOWN, ec); +} + +/* Map GPG_xERR_xx error codes to Assuan status codes */ +int +map_to_assuan_status (int rc) +{ + gpg_err_code_t ec = gpg_err_code (rc); + gpg_err_source_t es = gpg_err_source (rc); + + if (!rc) + return 0; + if (!es) + { + es = GPG_ERR_SOURCE_USER_4; /* This should not happen, but we + need to make sure to pass a new + Assuan errorcode along. */ + log_debug ("map_to_assuan_status called with no error source\n"); + } + + if (ec == -1) + ec = GPG_ERR_NO_DATA; /* That used to be ASSUAN_No_Data_Available. */ + + return gpg_err_make (es, ec); +} + + + + + + + + + + + diff --git a/common/membuf.c b/common/membuf.c new file mode 100644 index 000000000..69e4ab908 --- /dev/null +++ b/common/membuf.c @@ -0,0 +1,89 @@ +/* membuf.c - A simple implementation of a dynamic buffer + * Copyright (C) 2001, 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include + +#include "membuf.h" + +#include "util.h" + + +/* A simple implementation of a dynamic buffer. Use init_membuf() to + create a buffer, put_membuf to append bytes and get_membuf to + release and return the buffer. Allocation errors are detected but + only returned at the final get_membuf(), this helps not to clutter + the code with out of core checks. */ + +void +init_membuf (membuf_t *mb, int initiallen) +{ + mb->len = 0; + mb->size = initiallen; + mb->out_of_core = 0; + mb->buf = xtrymalloc (initiallen); + if (!mb->buf) + mb->out_of_core = errno; +} + + +void +put_membuf (membuf_t *mb, const void *buf, size_t len) +{ + if (mb->out_of_core) + return; + + if (mb->len + len >= mb->size) + { + char *p; + + mb->size += len + 1024; + p = xtryrealloc (mb->buf, mb->size); + if (!p) + { + mb->out_of_core = errno; + return; + } + mb->buf = p; + } + memcpy (mb->buf + mb->len, buf, len); + mb->len += len; +} + + +void * +get_membuf (membuf_t *mb, size_t *len) +{ + char *p; + + if (mb->out_of_core) + { + xfree (mb->buf); + mb->buf = NULL; + return NULL; + } + + p = mb->buf; + *len = mb->len; + mb->buf = NULL; + mb->out_of_core = ENOMEM; /* hack to make sure it won't get reused. */ + return p; +} diff --git a/common/membuf.h b/common/membuf.h new file mode 100644 index 000000000..c199363cc --- /dev/null +++ b/common/membuf.h @@ -0,0 +1,41 @@ +/* membuf.h - A simple implementation of a dynamic buffer + * Copyright (C) 2001, 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef GNUPG_COMMON_MEMBUF_H +#define GNUPG_COMMON_MEMBUF_H + +/* The definition of the structure is private, we only need it here, + so it can be allocated on the stack. */ +struct private_membuf_s { + size_t len; + size_t size; + char *buf; + int out_of_core; +}; + +typedef struct private_membuf_s membuf_t; + + +void init_membuf (membuf_t *mb, int initiallen); +void put_membuf (membuf_t *mb, const void *buf, size_t len); +void *get_membuf (membuf_t *mb, size_t *len); + + +#endif /*GNUPG_COMMON_MEMBUF_H*/ diff --git a/common/miscellaneous.c b/common/miscellaneous.c new file mode 100644 index 000000000..bdb12c574 --- /dev/null +++ b/common/miscellaneous.c @@ -0,0 +1,126 @@ +/* miscellaneous.c - Stuff not fitting elsewhere + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include + +#include "util.h" +#include "iobuf.h" + +/* Decide whether the filename is stdout or a real filename and return + * an appropriate string. */ +const char * +print_fname_stdout (const char *s) +{ + if( !s || (*s == '-' && !s[1]) ) + return "[stdout]"; + return s; +} + + +/* Decide whether the filename is stdin or a real filename and return + * an appropriate string. */ +const char * +print_fname_stdin (const char *s) +{ + if( !s || (*s == '-' && !s[1]) ) + return "[stdin]"; + return s; +} + +void +print_string( FILE *fp, const byte *p, size_t n, int delim ) +{ + print_sanitized_buffer (fp, p, n, delim); +} + +void +print_utf8_string2 ( FILE *fp, const byte *p, size_t n, int delim ) +{ + print_sanitized_utf8_buffer (fp, p, n, delim); +} + +void +print_utf8_string( FILE *fp, const byte *p, size_t n ) +{ + print_utf8_string2 (fp, p, n, 0); +} + +char * +make_printable_string( const byte *p, size_t n, int delim ) +{ + return sanitize_buffer (p, n, delim); +} + + +/* + * Check if the file is compressed. + */ +int +is_file_compressed (const char *s, int *ret_rc) +{ + iobuf_t a; + byte buf[4]; + int i, rc = 0; + + struct magic_compress_s { + size_t len; + byte magic[4]; + } magic[] = { + { 3, { 0x42, 0x5a, 0x68, 0x00 } }, /* bzip2 */ + { 3, { 0x1f, 0x8b, 0x08, 0x00 } }, /* gzip */ + { 4, { 0x50, 0x4b, 0x03, 0x04 } }, /* (pk)zip */ + }; + + if ( !s || (*s == '-' && !s[1]) || !ret_rc ) + return 0; /* We can't check stdin or no file was given */ + + a = iobuf_open( s ); + if ( a == NULL ) { + *ret_rc = gpg_error_from_errno (errno); + return 0; + } + + if ( iobuf_get_filelength( a ) < 4 ) { + *ret_rc = 0; + goto leave; + } + + if ( iobuf_read( a, buf, 4 ) == -1 ) { + *ret_rc = a->error; + goto leave; + } + + for ( i = 0; i < DIM( magic ); i++ ) { + if ( !memcmp( buf, magic[i].magic, magic[i].len ) ) { + *ret_rc = 0; + rc = 1; + break; + } + } + +leave: + iobuf_close( a ); + return rc; +} + + + diff --git a/common/simple-pwquery.c b/common/simple-pwquery.c new file mode 100644 index 000000000..afdc4e2a4 --- /dev/null +++ b/common/simple-pwquery.c @@ -0,0 +1,486 @@ +/* simple-pwquery.c - A simple password query cleint for gpg-agent + * Copyright (C) 2002 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* This module is intended as a standalone client implementation to + gpg-agent's GET_PASSPHRASE command. In particular it does not use + the Assuan library and can only cope with an already running + gpg-agent. Some stuff is configurable in the header file. */ + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LOCALE_H +#include +#endif + +#define SIMPLE_PWQUERY_IMPLEMENTATION 1 +#include "simple-pwquery.h" + +#if defined(SPWQ_USE_LOGGING) && !defined(HAVE_JNLIB_LOGGING) +# undef SPWQ_USE_LOGGING +#endif + +#ifndef _ +#define _(a) (a) +#endif + +#if !defined (hexdigitp) && !defined (xtoi_2) +#define digitp(p) (*(p) >= '0' && *(p) <= '9') +#define hexdigitp(a) (digitp (a) \ + || (*(a) >= 'A' && *(a) <= 'F') \ + || (*(a) >= 'a' && *(a) <= 'f')) +#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ + *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) +#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) +#endif + + +/* Write NBYTES of BUF to file descriptor FD. */ +static int +writen (int fd, const void *buf, size_t nbytes) +{ + size_t nleft = nbytes; + int nwritten; + + while (nleft > 0) + { + nwritten = write( fd, buf, nleft ); + if (nwritten < 0) + { + if (errno == EINTR) + nwritten = 0; + else { +#ifdef SPWQ_USE_LOGGING + log_error ("write failed: %s\n", strerror (errno)); +#endif + return SPWQ_IO_ERROR; + } + } + nleft -= nwritten; + buf = (const char*)buf + nwritten; + } + + return 0; +} + + +/* Read an entire line and return number of bytes read. */ +static int +readline (int fd, char *buf, size_t buflen) +{ + size_t nleft = buflen; + char *p; + int nread = 0; + + while (nleft > 0) + { + int n = read (fd, buf, nleft); + if (n < 0) + { + if (errno == EINTR) + continue; + return -(SPWQ_IO_ERROR); + } + else if (!n) + { + return -(SPWQ_PROTOCOL_ERROR); /* incomplete line */ + } + p = buf; + nleft -= n; + buf += n; + nread += n; + + for (; n && *p != '\n'; n--, p++) + ; + if (n) + { + break; /* at least one full line available - that's enough. + This function is just a simple implementation, so + it is okay to forget about pending bytes */ + } + } + + return nread; +} + + +/* Send an option to the agent */ +static int +agent_send_option (int fd, const char *name, const char *value) +{ + char buf[200]; + int nread; + char *line; + int i; + + line = spwq_malloc (7 + strlen (name) + 1 + strlen (value) + 2); + if (!line) + return SPWQ_OUT_OF_CORE; + strcpy (stpcpy (stpcpy (stpcpy ( + stpcpy (line, "OPTION "), name), "="), value), "\n"); + i = writen (fd, line, strlen (line)); + spwq_free (line); + if (i) + return i; + + /* get response */ + nread = readline (fd, buf, DIM(buf)-1); + if (nread < 0) + return -nread; + if (nread < 3) + return SPWQ_PROTOCOL_ERROR; + + if (buf[0] == 'O' && buf[1] == 'K' && (buf[2] == ' ' || buf[2] == '\n')) + return 0; /* okay */ + + return SPWQ_ERR_RESPONSE; +} + + +/* Send all available options to the agent. */ +static int +agent_send_all_options (int fd) +{ + char *dft_display = NULL; + char *dft_ttyname = NULL; + char *dft_ttytype = NULL; + int rc = 0; + + dft_display = getenv ("DISPLAY"); + if (dft_display) + { + if ((rc = agent_send_option (fd, "display", dft_display))) + return rc; + } + + dft_ttyname = getenv ("GPG_TTY"); + if ((!dft_ttyname || !*dft_ttyname) && ttyname (0)) + dft_ttyname = ttyname (0); + if (dft_ttyname && *dft_ttyname) + { + if ((rc=agent_send_option (fd, "ttyname", dft_ttyname))) + return rc; + } + + dft_ttytype = getenv ("TERM"); + if (dft_ttyname && dft_ttytype) + { + if ((rc = agent_send_option (fd, "ttytype", dft_ttytype))) + return rc; + } + +#if defined(HAVE_SETLOCALE) + { + char *old_lc = NULL; + char *dft_lc = NULL; + +#if defined(LC_CTYPE) + old_lc = setlocale (LC_CTYPE, NULL); + if (old_lc) + { + char *p = spwq_malloc (strlen (old_lc)+1); + if (!p) + return SPWQ_OUT_OF_CORE; + strcpy (p, old_lc); + old_lc = p; + } + dft_lc = setlocale (LC_CTYPE, ""); + if (dft_ttyname && dft_lc) + rc = agent_send_option (fd, "lc-ctype", dft_lc); + if (old_lc) + { + setlocale (LC_CTYPE, old_lc); + spwq_free (old_lc); + } + if (rc) + return rc; +#endif + +#if defined(LC_MESSAGES) + old_lc = setlocale (LC_MESSAGES, NULL); + if (old_lc) + { + char *p = spwq_malloc (strlen (old_lc)+1); + if (!p) + return SPWQ_OUT_OF_CORE; + strcpy (p, old_lc); + old_lc = p; + } + dft_lc = setlocale (LC_MESSAGES, ""); + if (dft_ttyname && dft_lc) + rc = agent_send_option (fd, "lc-messages", dft_lc); + if (old_lc) + { + setlocale (LC_MESSAGES, old_lc); + spwq_free (old_lc); + } + if (rc) + return rc; +#endif + } +#endif /*HAVE_SETLOCALE*/ + + return 0; +} + + + +/* Try to open a connection to the agent, send all options and return + the file descriptor for the connection. Return -1 in case of + error. */ +static int +agent_open (int *rfd) +{ + int rc; + int fd; + char *infostr, *p; + struct sockaddr_un client_addr; + size_t len; + int prot; + char line[200]; + int nread; + + *rfd = -1; + infostr = getenv ( "GPG_AGENT_INFO" ); + if ( !infostr ) + { +#ifdef SPWQ_USE_LOGGING + log_error (_("gpg-agent is not available in this session\n")); +#endif + return SPWQ_NO_AGENT; + } + + if ( !(p = strchr ( infostr, ':')) || p == infostr + || (p-infostr)+1 >= sizeof client_addr.sun_path ) + { +#ifdef SPWQ_USE_LOGGING + log_error ( _("malformed GPG_AGENT_INFO environment variable\n")); +#endif + return SPWQ_NO_AGENT; + } + *p++ = 0; + + while (*p && *p != ':') + p++; + prot = *p? atoi (p+1) : 0; + if ( prot != 1) + { +#ifdef SPWQ_USE_LOGGING + log_error (_("gpg-agent protocol version %d is not supported\n"),prot); +#endif + return SPWQ_PROTOCOL_ERROR; + } + + if( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ) + { +#ifdef SPWQ_USE_LOGGING + log_error ("can't create socket: %s\n", strerror(errno) ); +#endif + return SPWQ_SYS_ERROR; + } + + memset (&client_addr, 0, sizeof client_addr); + client_addr.sun_family = AF_UNIX; + strcpy (client_addr.sun_path, infostr); + len = (offsetof (struct sockaddr_un, sun_path) + + strlen(client_addr.sun_path) + 1); + + if (connect (fd, (struct sockaddr*)&client_addr, len ) == -1) + { +#ifdef SPWQ_USE_LOGGING + log_error ( _("can't connect to `%s': %s\n"), infostr, strerror (errno)); +#endif + close (fd ); + return SPWQ_IO_ERROR; + } + + nread = readline (fd, line, DIM(line)); + if (nread < 3 || !(line[0] == 'O' && line[1] == 'K' + && (line[2] == '\n' || line[2] == ' ')) ) + { +#ifdef SPWQ_USE_LOGGING + log_error ( _("communication problem with gpg-agent\n")); +#endif + close (fd ); + return SPWQ_PROTOCOL_ERROR; + } + + rc = agent_send_all_options (fd); + if (rc) + { +#ifdef SPWQ_USE_LOGGING + log_error (_("problem setting the gpg-agent options\n")); +#endif + close (fd); + return rc; + } + + *rfd = fd; + return 0; +} + + +/* Copy text to BUFFER and escape as required. Return a poiinter to + the end of the new buffer. NOte that BUFFER must be large enough + to keep the entire text; allocataing it 3 times the size of TEXT + is sufficient. */ +static char * +copy_and_escape (char *buffer, const char *text) +{ + int i; + char *p = buffer; + + for (i=0; text[i]; i++) + { + if (text[i] < ' ' || text[i] == '+') + { + sprintf (p, "%%%02X", text[i]); + p += 3; + } + else if (text[i] == ' ') + *p++ = '+'; + else + *p++ = text[i]; + } + return p; +} + + +/* Ask the gpg-agent for a passphrase and present the user with a + DESCRIPTION, a PROMPT and optiaonlly with a TRYAGAIN extra text. + If a CACHEID is not NULL it is used to locate the passphrase in in + the cache and store it under this ID. If ERRORCODE is not NULL it + should point a variable receiving an errorcode; thsi errocode might + be 0 if the user canceled the operation. The function returns NULL + to indicate an error. */ +char * +simple_pwquery (const char *cacheid, + const char *tryagain, + const char *prompt, + const char *description, + int *errorcode) +{ + int fd = -1; + int nread; + char *result = NULL; + char *pw = NULL; + char *p; + int rc, i; + + rc = agent_open (&fd); + if (rc) + goto leave; + + if (!cacheid) + cacheid = "X"; + if (!tryagain) + tryagain = "X"; + if (!prompt) + prompt = "X"; + if (!description) + description = "X"; + + { + char *line; + /* We allocate 3 times the needed space so that there is enough + space for escaping. */ + line = spwq_malloc (15 + + 3*strlen (cacheid) + 1 + + 3*strlen (tryagain) + 1 + + 3*strlen (prompt) + 1 + + 3*strlen (description) + 1 + + 2); + if (!line) + { + rc = SPWQ_OUT_OF_CORE; + goto leave; + } + strcpy (line, "GET_PASSPHRASE "); + p = line+15; + p = copy_and_escape (p, cacheid); + *p++ = ' '; + p = copy_and_escape (p, tryagain); + *p++ = ' '; + p = copy_and_escape (p, prompt); + *p++ = ' '; + p = copy_and_escape (p, description); + *p++ = '\n'; + rc = writen (fd, line, p - line); + spwq_free (line); + if (rc) + goto leave; + } + + /* get response */ + pw = spwq_secure_malloc (500); + nread = readline (fd, pw, 499); + if (nread < 0) + { + rc = -nread; + goto leave; + } + if (nread < 3) + { + rc = SPWQ_PROTOCOL_ERROR; + goto leave; + } + + if (pw[0] == 'O' && pw[1] == 'K' && pw[2] == ' ') + { /* we got a passphrase - convert it back from hex */ + size_t pwlen = 0; + + for (i=3; i < nread && hexdigitp (pw+i); i+=2) + pw[pwlen++] = xtoi_2 (pw+i); + pw[pwlen] = 0; /* make a C String */ + result = pw; + pw = NULL; + } + else if (nread > 7 && !memcmp (pw, "ERR 111", 7) + && (pw[7] == ' ' || pw[7] == '\n') ) + { +#ifdef SPWQ_USE_LOGGING + log_info (_("canceled by user\n") ); +#endif + *errorcode = 0; /* canceled */ + } + else + { +#ifdef SPWQ_USE_LOGGING + log_error (_("problem with the agent\n")); +#endif + rc = SPWQ_ERR_RESPONSE; + } + + leave: + if (errorcode) + *errorcode = rc; + if (fd != -1) + close (fd); + if (pw) + spwq_free (pw); + return result; +} diff --git a/common/simple-pwquery.h b/common/simple-pwquery.h new file mode 100644 index 000000000..5947c42b5 --- /dev/null +++ b/common/simple-pwquery.h @@ -0,0 +1,69 @@ +/* simple-pwquery.c - A simple password query cleint for gpg-agent + * Copyright (C) 2002 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef SIMPLE_PWQUERY_H +#define SIMPLE_PWQUERY_H + +#ifdef SIMPLE_PWQUERY_IMPLEMENTATION /* Begin configuration stuff. */ + +/* Include whatever files you need. */ +#include +#include "../jnlib/logging.h" + +/* Try to write error message using the standard log mechanism. The + current implementation requires that the HAVE_JNLIB_LOGGING is also + defined. */ +#define SPWQ_USE_LOGGING 1 + +/* Memory allocation functions used by the implementation. Note, that + the returned value is expected to be freed with + spwq_secure_free. */ +#define spwq_malloc(a) gcry_malloc (a) +#define spwq_free(a) gcry_free (a) +#define spwq_secure_malloc(a) gcry_malloc_secure (a) +#define spwq_secure_free(a) gcry_free (a) + + +#endif /*SIMPLE_PWQUERY_IMPLEMENTATION*/ /* End configuration stuff. */ + + +/* Ask the gpg-agent for a passphrase and present the user with a + DESCRIPTION, a PROMPT and optiaonlly with a TRYAGAIN extra text. + If a CACHEID is not NULL it is used to locate the passphrase in in + the cache and store it under this ID. If ERRORCODE is not NULL it + should point a variable receiving an errorcode; this errocode might + be 0 if the user canceled the operation. The function returns NULL + to indicate an error. */ +char *simple_pwquery (const char *cacheid, + const char *tryagain, + const char *prompt, + const char *description, + int *errorcode); + + +#define SPWQ_OUT_OF_CORE 1 +#define SPWQ_IO_ERROR 2 +#define SPWQ_PROTOCOL_ERROR 3 +#define SPWQ_ERR_RESPONSE 4 +#define SPWQ_NO_AGENT 5 +#define SPWQ_SYS_ERROR 6 +#define SPWQ_GENERAL_ERROR 7 + +#endif /*SIMPLE_PWQUERY_H*/ diff --git a/common/ttyio.c b/common/ttyio.c new file mode 100644 index 000000000..fd748009e --- /dev/null +++ b/common/ttyio.c @@ -0,0 +1,508 @@ +/* ttyio.c - tty i/O functions + * Copyright (C) 1998,1999,2000,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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_TCGETATTR +#include +#else +#ifdef HAVE_TERMIO_H +/* simulate termios with termio */ +#include +#define termios termio +#define tcsetattr ioctl +#define TCSAFLUSH TCSETAF +#define tcgetattr(A,B) ioctl(A,TCGETA,B) +#define HAVE_TCGETATTR +#endif +#endif +#ifdef __MINGW32__ /* use the odd Win32 functions */ +#include +#ifdef HAVE_TCGETATTR +#error mingw32 and termios +#endif +#endif +#include +#include +#include "util.h" +#include "memory.h" +#include "ttyio.h" + +#define CONTROL_D ('D' - 'A' + 1) + +#ifdef __MINGW32__ /* use the odd Win32 functions */ +static struct { + HANDLE in, out; +} con; +#define DEF_INPMODE (ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT \ + |ENABLE_PROCESSED_INPUT ) +#define HID_INPMODE (ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT ) +#define DEF_OUTMODE (ENABLE_WRAP_AT_EOL_OUTPUT|ENABLE_PROCESSED_OUTPUT) + +#else /* yeah, we have a real OS */ +static FILE *ttyfp = NULL; +#endif + +static int initialized; +static int last_prompt_len; +static int batchmode; +static int no_terminal; + +#ifdef HAVE_TCGETATTR + static struct termios termsave; + static int restore_termios; +#endif + + + +/* This is a wrapper around ttyname so that we can use it even when + the standard streams are redirected. It figures the name out the + first time and returns it in a statically allocated buffer. */ +const char * +tty_get_ttyname (void) +{ + static char *name; + + /* On a GNU system ctermid() always return /dev/tty, so this does + not make much sense - however if it is ever changed we do the + Right Thing now. */ +#ifdef HAVE_CTERMID + static int got_name; + + if (!got_name) + { + const char *s; + s = ctermid (NULL); + if (s) + name = strdup (s); + got_name = 1; + } +#endif + /* Assume the staandrd tty on memory error or when tehre is no + certmid. */ + return name? name : "/dev/tty"; +} + + + +#ifdef HAVE_TCGETATTR +static void +cleanup(void) +{ + if( restore_termios ) { + restore_termios = 0; /* do it prios in case it is interrupted again */ + if( tcsetattr(fileno(ttyfp), TCSAFLUSH, &termsave) ) + log_error("tcsetattr() failed: %s\n", strerror(errno) ); + } +} +#endif + +static void +init_ttyfp(void) +{ + if( initialized ) + return; + +#if defined(__MINGW32__) + { + SECURITY_ATTRIBUTES sa; + + memset(&sa, 0, sizeof(sa)); + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + con.out = CreateFileA( "CONOUT$", GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ|FILE_SHARE_WRITE, + &sa, OPEN_EXISTING, 0, 0 ); + if( con.out == INVALID_HANDLE_VALUE ) + log_fatal("open(CONOUT$) failed: rc=%d", (int)GetLastError() ); + memset(&sa, 0, sizeof(sa)); + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + con.in = CreateFileA( "CONIN$", GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ|FILE_SHARE_WRITE, + &sa, OPEN_EXISTING, 0, 0 ); + if( con.in == INVALID_HANDLE_VALUE ) + log_fatal("open(CONIN$) failed: rc=%d", (int)GetLastError() ); + } + SetConsoleMode(con.in, DEF_INPMODE ); + SetConsoleMode(con.out, DEF_OUTMODE ); + +#elif defined(__EMX__) + ttyfp = stdout; /* Fixme: replace by the real functions: see wklib */ +#else + ttyfp = batchmode? stderr : fopen (tty_get_ttyname (), "r+"); + if( !ttyfp ) { + log_error("cannot open `%s': %s\n", tty_get_ttyname (), + strerror(errno) ); + exit(2); + } +#endif +#ifdef HAVE_TCGETATTR + atexit( cleanup ); +#endif + initialized = 1; +} + + +int +tty_batchmode( int onoff ) +{ + int old = batchmode; + if( onoff != -1 ) + batchmode = onoff; + return old; +} + +int +tty_no_terminal(int onoff) +{ + int old = no_terminal; + no_terminal = onoff ? 1 : 0; + return old; +} + +void +tty_printf( const char *fmt, ... ) +{ + va_list arg_ptr; + + if (no_terminal) + return; + + if( !initialized ) + init_ttyfp(); + + va_start( arg_ptr, fmt ) ; +#ifdef __MINGW32__ + { + char *buf = NULL; + int n; + DWORD nwritten; + + n = vasprintf(&buf, fmt, arg_ptr); + if( !buf ) + log_bug("vasprintf() failed\n"); + + if( !WriteConsoleA( con.out, buf, n, &nwritten, NULL ) ) + log_fatal("WriteConsole failed: rc=%d", (int)GetLastError() ); + if( n != nwritten ) + log_fatal("WriteConsole failed: %d != %d\n", n, (int)nwritten ); + last_prompt_len += n; + xfree (buf); + } +#else + last_prompt_len += vfprintf(ttyfp,fmt,arg_ptr) ; + fflush(ttyfp); +#endif + va_end(arg_ptr); +} + + +/**************** + * Print a string, but filter all control characters out. + */ +void +tty_print_string( byte *p, size_t n ) +{ + if (no_terminal) + return; + + if( !initialized ) + init_ttyfp(); + +#ifdef __MINGW32__ + /* not so effective, change it if you want */ + for( ; n; n--, p++ ) + if( iscntrl( *p ) ) { + if( *p == '\n' ) + tty_printf("\\n"); + else if( !*p ) + tty_printf("\\0"); + else + tty_printf("\\x%02x", *p); + } + else + tty_printf("%c", *p); +#else + for( ; n; n--, p++ ) + if( iscntrl( *p ) ) { + putc('\\', ttyfp); + if( *p == '\n' ) + putc('n', ttyfp); + else if( !*p ) + putc('0', ttyfp); + else + fprintf(ttyfp, "x%02x", *p ); + } + else + putc(*p, ttyfp); +#endif +} + +void +tty_print_utf8_string2( byte *p, size_t n, size_t max_n ) +{ + size_t i; + char *buf; + + if (no_terminal) + return; + + /* we can handle plain ascii simpler, so check for it first */ + for(i=0; i < n; i++ ) { + if( p[i] & 0x80 ) + break; + } + if( i < n ) { + buf = utf8_to_native( p, n, 0 ); + if( max_n && (strlen( buf ) > max_n )) { + buf[max_n] = 0; + } + /*(utf8 conversion already does the control character quoting)*/ + tty_printf("%s", buf ); + xfree( buf ); + } + else { + if( max_n && (n > max_n) ) { + n = max_n; + } + tty_print_string( p, n ); + } +} + +void +tty_print_utf8_string( byte *p, size_t n ) +{ + tty_print_utf8_string2( p, n, 0 ); +} + + +static char * +do_get( const char *prompt, int hidden ) +{ + char *buf; +#ifndef __riscos__ + byte cbuf[1]; +#endif + int c, n, i; + + if( batchmode ) { + log_error("Sorry, we are in batchmode - can't get input\n"); + exit(2); + } + + if (no_terminal) { + log_error("Sorry, no terminal at all requested - can't get input\n"); + exit(2); + } + + if( !initialized ) + init_ttyfp(); + + last_prompt_len = 0; + tty_printf( "%s", prompt ); + buf = xmalloc((n=50)); + i = 0; + +#ifdef __MINGW32__ /* windoze version */ + if( hidden ) + SetConsoleMode(con.in, HID_INPMODE ); + + for(;;) { + DWORD nread; + + if( !ReadConsoleA( con.in, cbuf, 1, &nread, NULL ) ) + log_fatal("ReadConsole failed: rc=%d", (int)GetLastError() ); + if( !nread ) + continue; + if( *cbuf == '\n' ) + break; + + if( !hidden ) + last_prompt_len++; + c = *cbuf; + if( c == '\t' ) + c = ' '; + else if( c > 0xa0 ) + ; /* we don't allow 0xa0, as this is a protected blank which may + * confuse the user */ + else if( iscntrl(c) ) + continue; + if( !(i < n-1) ) { + n += 50; + buf = xrealloc (buf, n); + } + buf[i++] = c; + } + + if( hidden ) + SetConsoleMode(con.in, DEF_INPMODE ); + +#elif defined(__riscos__) + do { + c = riscos_getchar(); + if (c == 0xa || c == 0xd) { /* Return || Enter */ + c = (int) '\n'; + } else if (c == 0x8 || c == 0x7f) { /* Backspace || Delete */ + if (i>0) { + i--; + if (!hidden) { + last_prompt_len--; + fputc(8, ttyfp); + fputc(32, ttyfp); + fputc(8, ttyfp); + fflush(ttyfp); + } + } else { + fputc(7, ttyfp); + fflush(ttyfp); + } + continue; + } else if (c == (int) '\t') { /* Tab */ + c = ' '; + } else if (c > 0xa0) { + ; /* we don't allow 0xa0, as this is a protected blank which may + * confuse the user */ + } else if (iscntrl(c)) { + continue; + } + if(!(i < n-1)) { + n += 50; + buf = xrealloc (buf, n); + } + buf[i++] = c; + if (!hidden) { + last_prompt_len++; + fputc(c, ttyfp); + fflush(ttyfp); + } + } while (c != '\n'); + i = (i>0) ? i-1 : 0; +#else /* unix version */ + if( hidden ) { +#ifdef HAVE_TCGETATTR + struct termios term; + + if( tcgetattr(fileno(ttyfp), &termsave) ) + log_fatal("tcgetattr() failed: %s\n", strerror(errno) ); + restore_termios = 1; + term = termsave; + term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); + if( tcsetattr( fileno(ttyfp), TCSAFLUSH, &term ) ) + log_fatal("tcsetattr() failed: %s\n", strerror(errno) ); +#endif + } + + /* fixme: How can we avoid that the \n is echoed w/o disabling + * canonical mode - w/o this kill_prompt can't work */ + while( read(fileno(ttyfp), cbuf, 1) == 1 && *cbuf != '\n' ) { + if( !hidden ) + last_prompt_len++; + c = *cbuf; + if( c == CONTROL_D ) + log_info("control d found\n"); + if( c == '\t' ) + c = ' '; + else if( c > 0xa0 ) + ; /* we don't allow 0xa0, as this is a protected blank which may + * confuse the user */ + else if( iscntrl(c) ) + continue; + if( !(i < n-1) ) { + n += 50; + buf = xrealloc (buf, n ); + } + buf[i++] = c; + } + if( *cbuf != '\n' ) { + buf[0] = CONTROL_D; + i = 1; + } + + + if( hidden ) { +#ifdef HAVE_TCGETATTR + if( tcsetattr(fileno(ttyfp), TCSAFLUSH, &termsave) ) + log_error("tcsetattr() failed: %s\n", strerror(errno) ); + restore_termios = 0; +#endif + } +#endif /* end unix version */ + buf[i] = 0; + return buf; +} + + +char * +tty_get( const char *prompt ) +{ + return do_get( prompt, 0 ); +} + +char * +tty_get_hidden( const char *prompt ) +{ + return do_get( prompt, 1 ); +} + + +void +tty_kill_prompt() +{ + if ( no_terminal ) + return; + + if( !initialized ) + init_ttyfp(); + + if( batchmode ) + last_prompt_len = 0; + if( !last_prompt_len ) + return; +#ifdef __MINGW32__ + tty_printf("\r%*s\r", last_prompt_len, ""); +#else + { + int i; + putc('\r', ttyfp); + for(i=0; i < last_prompt_len; i ++ ) + putc(' ', ttyfp); + putc('\r', ttyfp); + fflush(ttyfp); + } +#endif + last_prompt_len = 0; +} + + +int +tty_get_answer_is_yes( const char *prompt ) +{ + int yes; + char *p = tty_get( prompt ); + tty_kill_prompt(); + yes = answer_is_yes(p); + xfree(p); + return yes; +} diff --git a/common/ttyio.h b/common/ttyio.h new file mode 100644 index 000000000..b3ca7dcaf --- /dev/null +++ b/common/ttyio.h @@ -0,0 +1,40 @@ +/* ttyio.h + * Copyright (C) 1998, 1999, 2000, 2001, 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ +#ifndef GNUPG_COMMON_TTYIO_H +#define GNUPG_COMMON_TTYIO_H + +const char *tty_get_ttyname (void); +int tty_batchmode (int onoff); +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 ) +void tty_printf (const char *fmt, ... ) __attribute__ ((format (printf,1,2))); +#else +void tty_printf (const char *fmt, ... ); +#endif +void tty_print_string (unsigned char *p, size_t n); +void tty_print_utf8_string (unsigned char *p, size_t n); +void tty_print_utf8_string2 (unsigned char *p, size_t n, size_t max_n); +char *tty_get (const char *prompt); +char *tty_get_hidden (const char *prompt); +void tty_kill_prompt (void); +int tty_get_answer_is_yes (const char *prompt); +int tty_no_terminal (int onoff); + + +#endif /*GNUPG_COMMON_TTYIO_H*/ diff --git a/common/util.h b/common/util.h new file mode 100644 index 000000000..045851481 --- /dev/null +++ b/common/util.h @@ -0,0 +1,120 @@ +/* util.h - Utility functions for Gnupg + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef GNUPG_COMMON_UTIL_H +#define GNUPG_COMMON_UTIL_H + +#include /* We need this for the memory function protos. */ +#include /* We need time_t. */ +#include /* we need gpg-error_t. */ + +/* to pass hash functions to libksba we need to cast it */ +#define HASH_FNC ((void (*)(void *, const void*,size_t))gcry_md_write) + +/* get all the stuff from jnlib */ +#include "../jnlib/logging.h" +#include "../jnlib/argparse.h" +#include "../jnlib/stringhelp.h" +#include "../jnlib/mischelp.h" +#include "../jnlib/strlist.h" +#include "../jnlib/dotlock.h" +#include "../jnlib/utf8conv.h" + +/* handy malloc macros - use only them */ +#define xtrymalloc(a) gcry_malloc ((a)) +#define xtrycalloc(a,b) gcry_calloc ((a),(b)) +#define xtryrealloc(a,b) gcry_realloc ((a),(b)) +#define xtrystrdup(a) gcry_strdup ((a)) +#define xfree(a) gcry_free ((a)) + +#define xmalloc(a) gcry_xmalloc ((a)) +#define xmalloc_secure(a) gcry_xmalloc_secure ((a)) +#define xcalloc(a,b) gcry_xcalloc ((a),(b)) +#define xcalloc_secure(a,b) gcry_xcalloc_secure ((a),(b)) +#define xrealloc(a,b) gcry_xrealloc ((a),(b)) +#define xstrdup(a) gcry_xstrdup ((a)) + +/*-- maperror.c --*/ +int map_ksba_err (int err); +int map_gcry_err (int err); +int map_kbx_err (int err); +gpg_error_t map_assuan_err (int err); +int map_to_assuan_status (int rc); + +/*-- gettime.c --*/ +time_t gnupg_get_time (void); +void gnupg_set_time (time_t newtime, int freeze); +int gnupg_faked_time_p (void); +u32 make_timestamp (void); +u32 scan_isodatestr (const char *string); +u32 add_days_to_timestamp (u32 stamp, u16 days); +const char *strtimevalue (u32 stamp); +const char *strtimestamp (u32 stamp); /* GMT */ +const char *asctimestamp (u32 stamp); /* localized */ + +/*-- signal.c --*/ +void gnupg_init_signals (int mode, void (*fast_cleanup)(void)); +void gnupg_pause_on_sigusr (int which); +void gnupg_block_all_signals (void); +void gnupg_unblock_all_signals (void); + +/*-- yesno.c --*/ +int answer_is_yes (const char *s); +int answer_is_yes_no_default (const char *s, int def_answer); +int answer_is_yes_no_quit (const char *s); + + +/*-- miscellaneous.c --*/ +const char *print_fname_stdout (const char *s); +const char *print_fname_stdin (const char *s); +void print_string (FILE *fp, const byte *p, size_t n, int delim); +void print_utf8_string2 ( FILE *fp, const byte *p, size_t n, int delim); +void print_utf8_string (FILE *fp, const byte *p, size_t n); +char *make_printable_string (const byte *p, size_t n, int delim); + +int is_file_compressed (const char *s, int *ret_rc); + + +/*-- replacement functions from funcname.c --*/ +#if !HAVE_VASPRINTF +#include +int vasprintf (char **result, const char *format, va_list *args); +int asprintf (char **result, const char *format, ...); +#endif + + + +/*-- some macros to replace ctype ones and avoid locale problems --*/ +#define spacep(p) (*(p) == ' ' || *(p) == '\t') +#define digitp(p) (*(p) >= '0' && *(p) <= '9') +#define hexdigitp(a) (digitp (a) \ + || (*(a) >= 'A' && *(a) <= 'F') \ + || (*(a) >= 'a' && *(a) <= 'f')) +/* the atoi macros assume that the buffer has only valid digits */ +#define atoi_1(p) (*(p) - '0' ) +#define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1)) +#define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2)) +#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ + *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) +#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) + + + +#endif /*GNUPG_COMMON_UTIL_H*/ diff --git a/common/yesno.c b/common/yesno.c new file mode 100644 index 000000000..2a96b4e5d --- /dev/null +++ b/common/yesno.c @@ -0,0 +1,96 @@ +/* yesno.c - Yes/No questions + * Copyright (C) 1998, 1999, 2000, 2001, 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include + +#include "i18n.h" +#include "util.h" + +int +answer_is_yes_no_default( const char *s, int def_answer ) +{ + const char *long_yes = _("yes"); + const char *short_yes = _("yY"); + const char *long_no = _("no"); + const char *short_no = _("nN"); + + /* Note: we have to use the local dependent strcasecmp here */ + if( !strcasecmp(s, long_yes ) ) + return 1; + if( *s && strchr( short_yes, *s ) && !s[1] ) + return 1; + /* test for no strings to catch ambiguities for the next test */ + if( !strcasecmp(s, long_no ) ) + return 0; + if( *s && strchr( short_no, *s ) && !s[1] ) + return 0; + /* test for the english version (for those who are used to type yes) */ + if( !ascii_strcasecmp(s, "yes" ) ) + return 1; + if( *s && strchr( "yY", *s ) && !s[1] ) + return 1; + return def_answer; +} + +int +answer_is_yes( const char *s ) +{ + return answer_is_yes_no_default(s,0); +} + +/**************** + * Return 1 for yes, -1 for quit, or 0 for no + */ +int +answer_is_yes_no_quit( const char *s ) +{ + const char *long_yes = _("yes"); + const char *long_no = _("no"); + const char *long_quit = _("quit"); + const char *short_yes = _("yY"); + const char *short_no = _("nN"); + const char *short_quit = _("qQ"); + + /* Note: We have to use the locale dependent strcasecmp */ + if( !strcasecmp(s, long_no ) ) + return 0; + if( !strcasecmp(s, long_yes ) ) + return 1; + if( !strcasecmp(s, long_quit ) ) + return -1; + if( *s && strchr( short_no, *s ) && !s[1] ) + return 0; + if( *s && strchr( short_yes, *s ) && !s[1] ) + return 1; + if( *s && strchr( short_quit, *s ) && !s[1] ) + return -1; + /* but not here */ + if( !ascii_strcasecmp(s, "yes" ) ) + return 1; + if( !ascii_strcasecmp(s, "quit" ) ) + return -1; + if( *s && strchr( "yY", *s ) && !s[1] ) + return 1; + if( *s && strchr( "qQ", *s ) && !s[1] ) + return -1; + return 0; +} diff --git a/kbx/Makefile.am b/kbx/Makefile.am new file mode 100644 index 000000000..4f0c40043 --- /dev/null +++ b/kbx/Makefile.am @@ -0,0 +1,52 @@ +# Keybox Makefile +# 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 2 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +## Process this file with automake to produce Makefile.in + +localedir = $(datadir)/locale +INCLUDES = -I../intl -DLOCALEDIR=\"$(localedir)\" + +EXTRA_DIST = mkerrors +AM_CPPFLAGS = -I$(top_srcdir)/common -I$(top_srcdir)/intl \ + $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) + +noinst_LIBRARIES = libkeybox.a +bin_PROGRAMS = kbxutil + +common_sources = \ + keybox.h keybox-defs.h keybox-search-desc.h \ + keybox-util.c \ + keybox-init.c \ + keybox-blob.c \ + keybox-file.c \ + keybox-search.c \ + keybox-update.c \ + keybox-dump.c + + +libkeybox_a_SOURCES = $(common_sources) + +kbxutil_SOURCES = kbxutil.c $(common_sources) +kbxutil_LDADD = ../jnlib/libjnlib.a $(KSBA_LIBS) $(LIBGCRYPT_LIBS) \ + -lgpg-error @INTLLIBS@ + + + + + diff --git a/kbx/kbxutil.c b/kbx/kbxutil.c new file mode 100644 index 000000000..abca4faa9 --- /dev/null +++ b/kbx/kbxutil.c @@ -0,0 +1,339 @@ +/* kbxutil.c - The Keybox utility + * Copyright (C) 2000, 2001 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "../jnlib/logging.h" +#include "../jnlib/argparse.h" +#include "../jnlib/stringhelp.h" +#include "../common/i18n.h" +#include "keybox-defs.h" + +#include + + +enum cmd_and_opt_values { + aNull = 0, + oArmor = 'a', + oDryRun = 'n', + oOutput = 'o', + oQuiet = 'q', + oVerbose = 'v', + + aNoSuchCmd = 500, /* force other values not to be a letter */ + aFindByFpr, + aFindByKid, + aFindByUid, + + oDebug, + oDebugAll, + + oNoArmor, + + + aTest +}; + + +static ARGPARSE_OPTS opts[] = { + { 300, NULL, 0, N_("@Commands:\n ") }, + + { aFindByFpr, "find-by-fpr", 0, "|FPR| find key using it's fingerprnt" }, + { aFindByKid, "find-by-kid", 0, "|KID| find key using it's keyid" }, + { aFindByUid, "find-by-uid", 0, "|NAME| find key by user name" }, + + { 301, NULL, 0, N_("@\nOptions:\n ") }, + + { oArmor, "armor", 0, N_("create ascii armored output")}, + { oArmor, "armour", 0, "@" }, + { oOutput, "output", 2, N_("use as output file")}, + { oVerbose, "verbose", 0, N_("verbose") }, + { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, + { oDryRun, "dry-run", 0, N_("do not make any changes") }, + + { oDebug, "debug" ,4|16, N_("set debugging flags")}, + { oDebugAll, "debug-all" ,0, N_("enable full debugging")}, + + {0} /* end of list */ +}; + + +void myexit (int rc); + +int keybox_errors_seen = 0; + + +static const char * +my_strusage( int level ) +{ + const char *p; + switch( level ) { + case 11: p = "kbxutil (GnuPG)"; + break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = + _("Please report bugs to " PACKAGE_BUGREPORT ".\n"); + break; + case 1: + case 40: p = + _("Usage: kbxutil [options] [files] (-h for help)"); + break; + case 41: p = + _("Syntax: kbxutil [options] [files]\n" + "list, export, import Keybox data\n"); + break; + + + default: p = NULL; + } + return p; +} + + +static void +i18n_init(void) +{ + #ifdef USE_SIMPLE_GETTEXT + set_gettext_file( PACKAGE ); + #else + #ifdef ENABLE_NLS + #ifdef HAVE_LC_MESSAGES + setlocale( LC_TIME, "" ); + setlocale( LC_MESSAGES, "" ); + #else + setlocale( LC_ALL, "" ); + #endif + bindtextdomain( PACKAGE, LOCALEDIR ); + textdomain( PACKAGE ); + #endif + #endif +} + + +/* static void */ +/* wrong_args( const char *text ) */ +/* { */ +/* log_error("usage: kbxutil %s\n", text); */ +/* myexit ( 1 ); */ +/* } */ + + +#if 0 +static int +hextobyte( const byte *s ) +{ + int c; + + if( *s >= '0' && *s <= '9' ) + c = 16 * (*s - '0'); + else if( *s >= 'A' && *s <= 'F' ) + c = 16 * (10 + *s - 'A'); + else if( *s >= 'a' && *s <= 'f' ) + c = 16 * (10 + *s - 'a'); + else + return -1; + s++; + if( *s >= '0' && *s <= '9' ) + c += *s - '0'; + else if( *s >= 'A' && *s <= 'F' ) + c += 10 + *s - 'A'; + else if( *s >= 'a' && *s <= 'f' ) + c += 10 + *s - 'a'; + else + return -1; + return c; +} +#endif + +#if 0 +static char * +format_fingerprint ( const char *s ) +{ + int i, c; + byte fpr[20]; + + for (i=0; i < 20 && *s; ) { + if ( *s == ' ' || *s == '\t' ) { + s++; + continue; + } + c = hextobyte(s); + if (c == -1) { + return NULL; + } + fpr[i++] = c; + s += 2; + } + return gcry_xstrdup ( fpr ); +} +#endif + +#if 0 +static int +format_keyid ( const char *s, u32 *kid ) +{ + char helpbuf[9]; + switch ( strlen ( s ) ) { + case 8: + kid[0] = 0; + kid[1] = strtoul( s, NULL, 16 ); + return 10; + + case 16: + mem2str( helpbuf, s, 9 ); + kid[0] = strtoul( helpbuf, NULL, 16 ); + kid[1] = strtoul( s+8, NULL, 16 ); + return 11; + } + return 0; /* error */ +} +#endif + + +int +main( int argc, char **argv ) +{ + ARGPARSE_ARGS pargs; + enum cmd_and_opt_values cmd = 0; + + set_strusage( my_strusage ); + /*log_set_name("kbxutil"); fixme */ +#if 0 + /* check that the libraries are suitable. Do it here because + * the option parse may need services of the library */ + if ( !gcry_check_version ( "1.1.4" ) ) + { + log_fatal(_("libgcrypt is too old (need %s, have %s)\n"), + "1.1.4", gcry_check_version(NULL) ); + } +#endif + + /*create_dotlock(NULL); register locking cleanup */ + i18n_init(); + + /* We need to use the gcry malloc function because jnlib does use them */ + keybox_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free); + ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free ); + + + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1; /* do not remove the args */ + while (arg_parse( &pargs, opts) ) + { + switch (pargs.r_opt) + { + case oVerbose: + /*opt.verbose++;*/ + /*gcry_control( GCRYCTL_SET_VERBOSITY, (int)opt.verbose );*/ + break; + case oDebug: + /*opt.debug |= pargs.r.ret_ulong; */ + break; + case oDebugAll: + /*opt.debug = ~0;*/ + break; + + case aFindByFpr: + case aFindByKid: + case aFindByUid: + cmd = pargs.r_opt; + break; + + default: + pargs.err = 2; + break; + } + } + if (log_get_errorcount(0) ) + myexit(2); + + if (!cmd) + { /* default is to list a KBX file */ + if (!argc) + _keybox_dump_file (NULL, stdout); + else + { + for (; argc; argc--, argv++) + _keybox_dump_file (*argv, stdout); + } + } +#if 0 + else if ( cmd == aFindByFpr ) { + char *fpr; + if ( argc != 2 ) + wrong_args ("kbxfile foingerprint"); + fpr = format_fingerprint ( argv[1] ); + if ( !fpr ) + log_error ("invalid formatted fingerprint\n"); + else { + kbxfile_search_by_fpr ( argv[0], fpr ); + gcry_free ( fpr ); + } + } + else if ( cmd == aFindByKid ) { + u32 kid[2]; + int mode; + + if ( argc != 2 ) + wrong_args ("kbxfile short-or-long-keyid"); + mode = format_keyid ( argv[1], kid ); + if ( !mode ) + log_error ("invalid formatted keyID\n"); + else { + kbxfile_search_by_kid ( argv[0], kid, mode ); + } + } + else if ( cmd == aFindByUid ) { + if ( argc != 2 ) + wrong_args ("kbxfile userID"); + kbxfile_search_by_uid ( argv[0], argv[1] ); + } +#endif + else + log_error ("unsupported action\n"); + + myexit(0); + return 8; /*NEVER REACHED*/ +} + + +void +myexit( int rc ) +{ + /* if( opt.debug & DBG_MEMSTAT_VALUE ) {*/ +/* gcry_control( GCRYCTL_DUMP_MEMORY_STATS ); */ +/* gcry_control( GCRYCTL_DUMP_RANDOM_STATS ); */ + /* }*/ +/* if( opt.debug ) */ +/* gcry_control( GCRYCTL_DUMP_SECMEM_STATS ); */ + rc = rc? rc : log_get_errorcount(0)? 2 : + keybox_errors_seen? 1 : 0; + exit(rc ); +} + + diff --git a/kbx/keybox-blob.c b/kbx/keybox-blob.c new file mode 100644 index 000000000..5ad1d2610 --- /dev/null +++ b/kbx/keybox-blob.c @@ -0,0 +1,1008 @@ +/* keybox-blob.c - KBX Blob handling + * Copyright (C) 2000, 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + + +/* The keybox data formats + +The KeyBox uses an augmented OpenPGP/X.509 key format. This makes +random access to a keyblock/Certificate easier and also gives the +opportunity to store additional information (e.g. the fingerprint) +along with the key. All integers are stored in network byte order, +offsets are counted from the beginning of the Blob. + +The first record of a plain KBX file has a special format: + + u32 length of the first record + byte Blob type (1) + byte version number (1) + byte reserved + byte reserved + u32 magic 'KBXf' + byte pgp_marginals used for validity calculation of this file + byte pgp_completes ditto. + byte pgp_cert_depth ditto. + +The OpenPGP and X.509 blob are verry similiar, things which are +X.509 specific are noted like [X.509: xxx] + + u32 length of this blob (including these 4 bytes) + byte Blob type (2) [X509: 3] + byte version number of this blob type (1) + u16 Blob flags + bit 0 = contains secret key material + bit 1 = ephemeral blob (e.g. used while quering external resources) + + u32 offset to the OpenPGP keyblock or X509 DER encoded certificate + u32 and its length + u16 number of keys (at least 1!) [X509: always 1] + u16 size of additional key information + n times: + b20 The keys fingerprint + (fingerprints are always 20 bytes, MD5 left padded with zeroes) + u32 offset to the n-th key's keyID (a keyID is always 8 byte) + or 0 if not known which is the case opnly for X509. + u16 special key flags + bit 0 = + u16 reserved + u16 size of serialnumber(may be zero) + n u16 (see above) bytes of serial number + u16 number of user IDs + u16 size of additional user ID information + n times: + u32 offset to the n-th user ID + u32 length of this user ID. + u16 special user ID flags. + bit 0 = + byte validity + byte reserved + [For X509, the first user ID is the ISsuer, the second the subject + and the others are subjectAltNames] + u16 number of signatures + u16 size of signature information (4) + u32 expiration time of signature with some special values: + 0x00000000 = not checked + 0x00000001 = missing key + 0x00000002 = bad signature + 0x10000000 = valid and expires at some date in 1978. + 0xffffffff = valid and does not expire + u8 assigned ownertrust [X509: no used] + u8 all_validity [X509: no used] + u16 reserved + u32 recheck_after + u32 Newest timestamp in the keyblock (useful for KS syncronsiation?) + u32 Blob created at + u32 size of reserved space (not including this field) + reserved space + + Here we might want to put other data + + Here comes the keyblock + + maybe we put a signature here later. + + b16 MD5 checksum (useful for KS syncronisation), we might also want to use + a mac here. + b4 resevered + +*/ + + +#include +#include +#include +#include +#include +#include + +#include "keybox-defs.h" +#include + +#ifdef KEYBOX_WITH_OPENPGP +/* include stuff to parse the packets */ +#endif +#ifdef KEYBOX_WITH_X509 +#include +#endif + + + +/* special values of the signature status */ +#define SF_NONE(a) ( !(a) ) +#define SF_NOKEY(a) ((a) & (1<<0)) +#define SF_BAD(a) ((a) & (1<<1)) +#define SF_VALID(a) ((a) & (1<<29)) + + +struct membuf { + size_t len; + size_t size; + char *buf; + int out_of_core; +}; + + +/* #if MAX_FINGERPRINT_LEN < 20 */ +/* #error fingerprints are 20 bytes */ +/* #endif */ + +struct keyboxblob_key { + char fpr[20]; + u32 off_kid; + ulong off_kid_addr; + u16 flags; +}; +struct keyboxblob_uid { + ulong off_addr; + char *name; /* used only with x509 */ + u32 len; + u16 flags; + byte validity; +}; + +struct keyid_list { + struct keyid_list *next; + int seqno; + byte kid[8]; +}; + +struct fixup_list { + struct fixup_list *next; + u32 off; + u32 val; +}; + + +struct keyboxblob { + byte *blob; + size_t bloblen; + off_t fileoffset; + + /* stuff used only by keybox_create_blob */ + unsigned char *serialbuf; + const unsigned char *serial; + size_t seriallen; + int nkeys; + struct keyboxblob_key *keys; + int nuids; + struct keyboxblob_uid *uids; + int nsigs; + u32 *sigs; + struct fixup_list *fixups; + int fixup_out_of_core; + + struct keyid_list *temp_kids; + struct membuf bufbuf; /* temporary store for the blob */ + struct membuf *buf; +}; + + + +/* A simple implemnation of a dynamic buffer. Use init_membuf() to + create a buffer, put_membuf to append bytes and get_membuf to + release and return the buffer. Allocation errors are detected but + only returned at the final get_membuf(), this helps not to clutter + the code with out of core checks. */ + +static void +init_membuf (struct membuf *mb, int initiallen) +{ + mb->len = 0; + mb->size = initiallen; + mb->out_of_core = 0; + mb->buf = xtrymalloc (initiallen); + if (!mb->buf) + mb->out_of_core = 1; +} + +static void +put_membuf (struct membuf *mb, const void *buf, size_t len) +{ + if (mb->out_of_core) + return; + + if (mb->len + len >= mb->size) + { + char *p; + + mb->size += len + 1024; + p = xtryrealloc (mb->buf, mb->size); + if (!p) + { + mb->out_of_core = 1; + return; + } + mb->buf = p; + } + memcpy (mb->buf + mb->len, buf, len); + mb->len += len; +} + +static void * +get_membuf (struct membuf *mb, size_t *len) +{ + char *p; + + if (mb->out_of_core) + { + xfree (mb->buf); + mb->buf = NULL; + return NULL; + } + + p = mb->buf; + *len = mb->len; + mb->buf = NULL; + mb->out_of_core = 1; /* don't allow a reuse */ + return p; +} + + +static void +put8 (struct membuf *mb, byte a ) +{ + put_membuf (mb, &a, 1); +} + +static void +put16 (struct membuf *mb, u16 a ) +{ + unsigned char tmp[2]; + tmp[0] = a>>8; + tmp[1] = a; + put_membuf (mb, tmp, 2); +} + +static void +put32 (struct membuf *mb, u32 a ) +{ + unsigned char tmp[4]; + tmp[0] = a>>24; + tmp[1] = a>>16; + tmp[2] = a>>8; + tmp[3] = a; + put_membuf (mb, tmp, 4); +} + + +/* Store a value in the fixup list */ +static void +add_fixup (KEYBOXBLOB blob, u32 off, u32 val) +{ + struct fixup_list *fl; + + if (blob->fixup_out_of_core) + return; + + fl = xtrycalloc(1, sizeof *fl); + if (!fl) + blob->fixup_out_of_core = 1; + else + { + fl->off = off; + fl->val = val; + fl->next = blob->fixups; + blob->fixups = fl; + } +} + + +/* + Some wrappers +*/ + +static u32 +make_timestamp (void) +{ + return time(NULL); +} + + + +#ifdef KEYBOX_WITH_OPENPGP +/* + OpenPGP specific stuff +*/ + + +/* + We must store the keyid at some place because we can't calculate the + offset yet. This is only used for v3 keyIDs. Function returns an + index value for later fixup or -1 for out of core. The value must be + a non-zero value */ +static int +pgp_temp_store_kid (KEYBOXBLOB blob, PKT_public_key *pk) +{ + struct keyid_list *k, *r; + + k = xtrymalloc (sizeof *k); + if (!k) + return -1; + k->kid[0] = pk->keyid[0] >> 24 ; + k->kid[1] = pk->keyid[0] >> 16 ; + k->kid[2] = pk->keyid[0] >> 8 ; + k->kid[3] = pk->keyid[0] ; + k->kid[4] = pk->keyid[0] >> 24 ; + k->kid[5] = pk->keyid[0] >> 16 ; + k->kid[6] = pk->keyid[0] >> 8 ; + k->kid[7] = pk->keyid[0] ; + k->seqno = 0; + k->next = blob->temp_kids; + blob->temp_kids = k; + for (r=k; r; r = r->next) + k->seqno++; + + return k->seqno; +} + +static int +pgp_create_key_part (KEYBOXBLOB blob, KBNODE keyblock) +{ + KBNODE node; + size_t fprlen; + int n; + + for (n=0, node = keyblock; node; node = node->next) + { + if ( node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) + { + PKT_public_key *pk = node->pkt->pkt.public_key; + char tmp[20]; + + fingerprint_from_pk (pk, tmp , &fprlen); + memcpy (blob->keys[n].fpr, tmp, 20); + if ( fprlen != 20 ) /*v3 fpr - shift right and fill with zeroes*/ + { + assert (fprlen == 16); + memmove (blob->keys[n].fpr+4, blob->keys[n].fpr, 16); + memset (blob->keys[n].fpr, 0, 4); + blob->keys[n].off_kid = pgp_temp_store_kid (blob, pk); + } + else + { + blob->keys[n].off_kid = 0; /* will be fixed up later */ + } + blob->keys[n].flags = 0; + n++; + } + else if ( node->pkt->pkttype == PKT_SECRET_KEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY ) + { + never_reached (); /* actually not yet implemented */ + } + } + assert (n == blob->nkeys); + return 0; +} + +static int +pgp_create_uid_part (KEYBOXBLOB blob, KBNODE keyblock) +{ + KBNODE node; + int n; + + for (n=0, node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_USER_ID) + { + PKT_user_id *u = node->pkt->pkt.user_id; + + blob->uids[n].len = u->len; + blob->uids[n].flags = 0; + blob->uids[n].validity = 0; + n++; + } + } + assert (n == blob->nuids); + return 0; +} + +static int +pgp_create_sig_part (KEYBOXBLOB blob, KBNODE keyblock) +{ + KBNODE node; + int n; + + for (n=0, node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_SIGNATURE) + { + PKT_signature *sig = node->pkt->pkt.signature; + + blob->sigs[n] = 0; /* FIXME: check the signature here */ + n++; + } + } + assert( n == blob->nsigs ); + return 0; +} + +static int +pgp_create_blob_keyblock (KEYBOXBLOB blob, KBNODE keyblock) +{ + struct membuf *a = blob->buf; + KBNODE node; + int rc; + int n; + u32 kbstart = a->len; + + add_fixup (blob, kbstart); + + for (n = 0, node = keyblock; node; node = node->next) + { + rc = build_packet ( a, node->pkt ); + if ( rc ) { + gpg_log_error ("build_packet(%d) for keyboxblob failed: %s\n", + node->pkt->pkttype, gpg_errstr(rc) ); + return GPGERR_WRITE_FILE; + } + if ( node->pkt->pkttype == PKT_USER_ID ) + { + PKT_user_id *u = node->pkt->pkt.user_id; + /* build_packet has set the offset of the name into u ; + * now we can do the fixup */ + add_fixup (blob, blob->uids[n].off_addr, u->stored_at); + n++; + } + } + assert (n == blob->nuids); + + add_fixup (blob, a->len - kbstart); + return 0; +} + +#endif /*KEYBOX_WITH_OPENPGP*/ + + +#ifdef KEYBOX_WITH_X509 +/* + X.509 specific stuff + */ + +/* Write the raw certificate out */ +static int +x509_create_blob_cert (KEYBOXBLOB blob, KsbaCert cert) +{ + struct membuf *a = blob->buf; + const unsigned char *image; + size_t length; + u32 kbstart = a->len; + + /* Store our offset for later fixup */ + add_fixup (blob, 8, kbstart); + + image = ksba_cert_get_image (cert, &length); + if (!image) + return gpg_error (GPG_ERR_GENERAL); + put_membuf (a, image, length); + + add_fixup (blob, 12, a->len - kbstart); + return 0; +} + +#endif /*KEYBOX_WITH_X509*/ + +/* Write a stored keyID out to the buffer */ +static void +write_stored_kid (KEYBOXBLOB blob, int seqno) +{ + struct keyid_list *r; + + for ( r = blob->temp_kids; r; r = r->next ) + { + if (r->seqno == seqno ) + { + put_membuf (blob->buf, r->kid, 8); + return; + } + } + never_reached (); +} + +/* Release a list of key IDs */ +static void +release_kid_list (struct keyid_list *kl) +{ + struct keyid_list *r, *r2; + + for ( r = kl; r; r = r2 ) + { + r2 = r->next; + xfree (r); + } +} + + + +static int +create_blob_header (KEYBOXBLOB blob, int blobtype, int as_ephemeral) +{ + struct membuf *a = blob->buf; + int i; + + put32 ( a, 0 ); /* blob length, needs fixup */ + put8 ( a, blobtype); + put8 ( a, 1 ); /* blob type version */ + put16 ( a, as_ephemeral? 2:0 ); /* blob flags */ + + put32 ( a, 0 ); /* offset to the raw data, needs fixup */ + put32 ( a, 0 ); /* length of the raw data, needs fixup */ + + put16 ( a, blob->nkeys ); + put16 ( a, 20 + 4 + 2 + 2 ); /* size of key info */ + for ( i=0; i < blob->nkeys; i++ ) + { + put_membuf (a, blob->keys[i].fpr, 20); + blob->keys[i].off_kid_addr = a->len; + put32 ( a, 0 ); /* offset to keyid, fixed up later */ + put16 ( a, blob->keys[i].flags ); + put16 ( a, 0 ); /* reserved */ + } + + put16 (a, blob->seriallen); /*fixme: check that it fits into 16 bits*/ + if (blob->serial) + put_membuf (a, blob->serial, blob->seriallen); + + put16 ( a, blob->nuids ); + put16 ( a, 4 + 4 + 2 + 1 + 1 ); /* size of uid info */ + for (i=0; i < blob->nuids; i++) + { + blob->uids[i].off_addr = a->len; + put32 ( a, 0 ); /* offset to userid, fixed up later */ + put32 ( a, blob->uids[i].len ); + put16 ( a, blob->uids[i].flags ); + put8 ( a, 0 ); /* validity */ + put8 ( a, 0 ); /* reserved */ + } + + put16 ( a, blob->nsigs ); + put16 ( a, 4 ); /* size of sig info */ + for (i=0; i < blob->nsigs; i++) + { + put32 ( a, blob->sigs[i]); + } + + put8 ( a, 0 ); /* assigned ownertrust */ + put8 ( a, 0 ); /* validity of all user IDs */ + put16 ( a, 0 ); /* reserved */ + put32 ( a, 0 ); /* time of next recheck */ + put32 ( a, 0 ); /* newest timestamp (none) */ + put32 ( a, make_timestamp() ); /* creation time */ + put32 ( a, 0 ); /* size of reserved space */ + /* reserved space (which is currently of size 0) */ + + /* space where we write keyIDs and and other stuff so that the + pointers can actually point to somewhere */ + if (blobtype == BLOBTYPE_PGP) + { + /* We need to store the keyids for all pgp v3 keys because those key + IDs are not part of the fingerprint. While we are doing that, we + fixup all the keyID offsets */ + for (i=0; i < blob->nkeys; i++ ) + { + if (blob->keys[i].off_kid) + { /* this is a v3 one */ + add_fixup (blob, blob->keys[i].off_kid_addr, a->len); + write_stored_kid (blob, blob->keys[i].off_kid); + } + else + { /* the better v4 key IDs - just store an offset 8 bytes back */ + add_fixup (blob, blob->keys[i].off_kid_addr, + blob->keys[i].off_kid_addr - 8); + } + } + } + + if (blobtype == BLOBTYPE_X509) + { + /* We don't want to point to ASN.1 encoded UserIDs (DNs) but to + the utf-8 string represenation of them */ + for (i=0; i < blob->nuids; i++ ) + { + if (blob->uids[i].name) + { /* this is a v3 one */ + add_fixup (blob, blob->uids[i].off_addr, a->len); + put_membuf (blob->buf, blob->uids[i].name, blob->uids[i].len); + } + } + } + + return 0; +} + + + +static int +create_blob_trailer (KEYBOXBLOB blob) +{ + return 0; +} + + +static int +create_blob_finish (KEYBOXBLOB blob) +{ + struct membuf *a = blob->buf; + byte *p; + char *pp; + int i; + size_t n; + + /* write a placeholder for the checksum */ + for (i = 0; i < 16; i++ ) + put32 (a, 0); /* Hmmm: why put32() ?? */ + + /* get the memory area */ + p = get_membuf (a, &n); + if (!p) + return gpg_error (GPG_ERR_ENOMEM); + assert (n >= 20); + + /* fixup the length */ + add_fixup (blob, 0, n); + + /* do the fixups */ + if (blob->fixup_out_of_core) + return gpg_error (GPG_ERR_ENOMEM); + + { + struct fixup_list *fl; + for (fl = blob->fixups; fl; fl = fl->next) + { + assert (fl->off+4 <= n); + p[fl->off+0] = fl->val >> 24; + p[fl->off+1] = fl->val >> 16; + p[fl->off+2] = fl->val >> 8; + p[fl->off+3] = fl->val; + } + } + + /* calculate and store the MD5 checksum */ + gcry_md_hash_buffer (GCRY_MD_MD5, p + n - 16, p, n - 16); + + pp = xtrymalloc (n); + if ( !pp ) + return gpg_error (gpg_err_code_from_errno (errno)); + memcpy (pp , p, n); + blob->blob = pp; + blob->bloblen = n; + + return 0; +} + + +#ifdef KEYBOX_WITH_OPENPGP + +int +_keybox_create_pgp_blob (KEYBOXBLOB *r_blob, KBNODE keyblock, int as_ephemeral) +{ + int rc = 0; + KBNODE node; + KEYBOXBLOB blob; + + *r_blob = NULL; + blob = xtrycalloc (1, sizeof *blob); + if (!blob) + return gpg_error (gpg_err_code_from_errno (errno)); + + /* fixme: Do some sanity checks on the keyblock */ + + /* count userids and keys so that we can allocate the arrays */ + for (node = keyblock; node; node = node->next) + { + switch (node->pkt->pkttype) + { + case PKT_PUBLIC_KEY: + case PKT_SECRET_KEY: + case PKT_PUBLIC_SUBKEY: + case PKT_SECRET_SUBKEY: blob->nkeys++; break; + case PKT_USER_ID: blob->nuids++; break; + case PKT_SIGNATURE: blob->nsigs++; break; + default: break; + } + } + + blob->keys = xtrycalloc (blob->nkeys, sizeof *blob->keys ); + blob->uids = xtrycalloc (blob->nuids, sizeof *blob->uids ); + blob->sigs = xtrycalloc (blob->nsigs, sizeof *blob->sigs ); + if (!blob->keys || !blob->uids || !blob->sigs) + { + rc = gpg_error (GPG_ERR_ENOMEM); + goto leave; + } + + rc = pgp_create_key_part ( blob, keyblock ); + if (rc) + goto leave; + rc = pgp_create_uid_part ( blob, keyblock ); + if (rc) + goto leave; + rc = pgp_create_sig_part ( blob, keyblock ); + if (rc) + goto leave; + + init_membuf (&blob->bufbuf, 1024); + blob->buf = &blob->bufbuf; + rc = create_blob_header (blob, BLOBTYPE_OPENPGP, as_ephemeral); + if (rc) + goto leave; + rc = pgp_create_blob_keyblock (blob, keyblock); + if (rc) + goto leave; + rc = create_blob_trailer (blob); + if (rc) + goto leave; + rc = create_blob_finish ( blob ); + if (rc) + goto leave; + + + leave: + release_kid_list (blob->temp_kids); + blob->temp_kids = NULL; + if (rc) + { + keybox_release_blob (blob); + *r_blob = NULL; + } + else + { + *r_blob = blob; + } + return rc; +} +#endif /*KEYBOX_WITH_OPENPGP*/ + +#ifdef KEYBOX_WITH_X509 + +/* return an allocated string with the email address extracted from a + DN */ +static char * +x509_email_kludge (const char *name) +{ + const unsigned char *p; + unsigned char *buf; + int n; + + if (strncmp (name, "1.2.840.113549.1.9.1=#", 22)) + return NULL; + /* This looks pretty much like an email address in the subject's DN + we use this to add an additional user ID entry. This way, + openSSL generated keys get a nicer and usable listing */ + name += 22; + for (n=0, p=name; hexdigitp (p) && hexdigitp (p+1); p +=2, n++) + ; + if (*p != '#' || !n) + return NULL; + buf = xtrymalloc (n+3); + if (!buf) + return NULL; /* oops, out of core */ + *buf = '<'; + for (n=1, p=name; *p != '#'; p +=2, n++) + buf[n] = xtoi_2 (p); + buf[n++] = '>'; + buf[n] = 0; + return buf; +} + + + +/* Note: We should move calculation of the digest into libksba and + remove that parameter */ +int +_keybox_create_x509_blob (KEYBOXBLOB *r_blob, KsbaCert cert, + unsigned char *sha1_digest, int as_ephemeral) +{ + int i, rc = 0; + KEYBOXBLOB blob; + unsigned char *p; + unsigned char **names = NULL; + size_t max_names; + + *r_blob = NULL; + blob = xtrycalloc (1, sizeof *blob); + if( !blob ) + return gpg_error (gpg_err_code_from_errno (errno)); + + p = ksba_cert_get_serial (cert); + if (p) + { + size_t n, len; + n = gcry_sexp_canon_len (p, 0, NULL, NULL); + if (n < 2) + { + xfree (p); + return gpg_error (GPG_ERR_GENERAL); + } + blob->serialbuf = p; + p++; n--; /* skip '(' */ + for (len=0; n && *p && *p != ':' && digitp (p); n--, p++) + len = len*10 + atoi_1 (p); + if (*p != ':') + { + xfree (blob->serialbuf); + blob->serialbuf = NULL; + return gpg_error (GPG_ERR_GENERAL); + } + p++; + blob->serial = p; + blob->seriallen = len; + } + + blob->nkeys = 1; + + /* create list of names */ + blob->nuids = 0; + max_names = 100; + names = xtrymalloc (max_names * sizeof *names); + if (!names) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + goto leave; + } + p = ksba_cert_get_issuer (cert, 0); + if (!p) + { + rc = gpg_error (GPG_ERR_MISSING_VALUE); + goto leave; + } + names[blob->nuids++] = p; + for (i=0; (p = ksba_cert_get_subject (cert, i)); i++) + { + + if (blob->nuids >= max_names) + { + unsigned char **tmp; + + max_names += 100; + tmp = xtryrealloc (names, max_names * sizeof *names); + if (!tmp) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + goto leave; + } + } + names[blob->nuids++] = p; + if (!i && (p=x509_email_kludge (p))) + names[blob->nuids++] = p; /* due to !i we don't need to check bounds*/ + } + + /* space for signature information */ + blob->nsigs = 1; + + blob->keys = xtrycalloc (blob->nkeys, sizeof *blob->keys ); + blob->uids = xtrycalloc (blob->nuids, sizeof *blob->uids ); + blob->sigs = xtrycalloc (blob->nsigs, sizeof *blob->sigs ); + if (!blob->keys || !blob->uids || !blob->sigs) + { + rc = gpg_error (GPG_ERR_ENOMEM); + goto leave; + } + + memcpy (blob->keys[0].fpr, sha1_digest, 20); + blob->keys[0].off_kid = 0; /* We don't have keyids */ + blob->keys[0].flags = 0; + + /* issuer and subject names */ + for (i=0; i < blob->nuids; i++) + { + blob->uids[i].name = names[i]; + blob->uids[i].len = strlen(names[i]); + names[i] = NULL; + blob->uids[i].flags = 0; + blob->uids[i].validity = 0; + } + xfree (names); + names = NULL; + + /* signatures */ + blob->sigs[0] = 0; /* not yet checked */ + + /* Create a temporary buffer for further processing */ + init_membuf (&blob->bufbuf, 1024); + blob->buf = &blob->bufbuf; + /* write out what we already have */ + rc = create_blob_header (blob, BLOBTYPE_X509, as_ephemeral); + if (rc) + goto leave; + rc = x509_create_blob_cert (blob, cert); + if (rc) + goto leave; + rc = create_blob_trailer (blob); + if (rc) + goto leave; + rc = create_blob_finish ( blob ); + if (rc) + goto leave; + + + leave: + release_kid_list (blob->temp_kids); + blob->temp_kids = NULL; + if (blob && names) + { + for (i=0; i < blob->nuids; i++) + xfree (names[i]); + } + xfree (names); + if (rc) + { + _keybox_release_blob (blob); + *r_blob = NULL; + } + else + { + *r_blob = blob; + } + return rc; +} +#endif /*KEYBOX_WITH_X509*/ + + + +int +_keybox_new_blob (KEYBOXBLOB *r_blob, char *image, size_t imagelen, off_t off) +{ + KEYBOXBLOB blob; + + *r_blob = NULL; + blob = xtrycalloc (1, sizeof *blob); + if (!blob) + return gpg_error (gpg_err_code_from_errno (errno)); + + blob->blob = image; + blob->bloblen = imagelen; + blob->fileoffset = off; + *r_blob = blob; + return 0; +} + +void +_keybox_release_blob (KEYBOXBLOB blob) +{ + int i; + if (!blob) + return; + /* hmmm: release membuf here?*/ + xfree (blob->keys ); + xfree (blob->serialbuf); + for (i=0; i < blob->nuids; i++) + xfree (blob->uids[i].name); + xfree (blob->uids ); + xfree (blob->sigs ); + xfree (blob->blob ); + xfree (blob ); +} + + + +const char * +_keybox_get_blob_image ( KEYBOXBLOB blob, size_t *n ) +{ + *n = blob->bloblen; + return blob->blob; +} + +off_t +_keybox_get_blob_fileoffset (KEYBOXBLOB blob) +{ + return blob->fileoffset; +} + diff --git a/scd/ChangeLog b/scd/ChangeLog new file mode 100644 index 000000000..ad4b0518c --- /dev/null +++ b/scd/ChangeLog @@ -0,0 +1,242 @@ +2003-07-31 Werner Koch + + * Makefile.am (scdaemon_LDADD): Added INTLLIBS. + +2003-07-28 Werner Koch + + * app-openpgp.c (do_setattr): Change implementation. Allow all + useful DOs. + +2003-07-27 Werner Koch + + Adjusted for gcry_mpi_print and gcry_mpi_scan API change. + +2003-07-24 Werner Koch + + * app-openpgp.c (do_learn_status): Print more status information. + (app_select_openpgp): Store the card version. + (store_fpr): Add argument card_version and fix DOs for old cards. + (app_openpgp_storekey): Likewise. + +2003-07-23 Werner Koch + + * command.c (cmd_pkauth): New. + (cmd_setdata): Check whether data was given at all to avoid + passing 0 to malloc. + + * app.c (app_auth): New. + * app-openpgp.c (do_auth): New. + +2003-07-22 Werner Koch + + * command.c (cmd_passwd): New. + * app.c (app_change_pin): New. + * app-openpgp.c (do_change_pin): New. + * iso7816.c (iso7816_reset_retry_counter): Implemented. + + * sc-investigate.c (main): New option --gen-random. + * iso7816.c (iso7816_get_challenge): Don't create APDUs with a + length larger than 255. + +2003-07-17 Werner Koch + + * command.c (cmd_random): New command RANDOM. + + * iso7816.c (map_sw): New. Use it in this file to return + meaningful error messages. Changed all public fucntions to return + a gpg_error_t. + (iso7816_change_reference_data): New. + * apdu.c (apdu_open_reader): Use faked status words for soem + system errors. + +2003-07-16 Werner Koch + + * apdu.c (apdu_send_simple): Use apdu_send_le so that we can + specify not to send Le as it should be. + +2003-07-15 Werner Koch + + * Makefile.am: Add sc-copykeys program. + * sc-copykeys.c: New. + * app-openpgp.c (app_openpgp_storekey): New. + (app_openpgp_cardinfo): New. + (count_bits): New. + (store_fpr): And use it here to get the actual length in bit. + +2003-07-03 Werner Koch + + * app-openpgp.c (do_setattr): Add setting of the URL. + (app_select_openpgp): Dump card data only in very verbose mode. + (do_decipher): New. + +2003-07-02 Werner Koch + + * app-openpgp.c (get_sig_counter): New. + (do_sign): Print the signature counter and enable the PIN callback. + (do_genkey): Implement the PIN callback. + +2003-07-01 Werner Koch + + * app-openpgp.c (store_fpr): Fixed fingerprint calculation. + +2003-06-26 Werner Koch + + * app-openpgp.c (find_tlv): Fixed length header parsing. + + * app.c (app_genkey): New. + * command.c (cmd_genkey): New. + +2003-06-25 Werner Koch + + * command.c (percent_plus_unescape): New. + (cmd_setattr): New. + +2003-06-24 Werner Koch + + * command.c (send_status_info): New. + + * app-openpgp.c (app_select_openpgp): Replace SLOT arg by APP arg + and setup the function pointers in APP on success. Changed callers. + * app.c: New. + * app-common.h: New. + * scdaemon.h (APP): New type to handle applications. + (server_control_s): Add an APP context field. + + * command.c (cmd_serialno): Handle applications. + (cmd_pksign): Ditto. + (cmd_pkdecrypt): Ditto. + (reset_notify): Ditto. + (cmd_learn): For now return error for application contexts. + (cmd_readcert): Ditto. + (cmd_readkey): Ditto. + +2003-06-04 Werner Koch + + * card.c (map_sc_err): Renamed gpg_make_err to gpg_err_make. + + Renamed error codes from INVALID to INV and removed _ERROR suffixes. + +2003-06-03 Werner Koch + + Changed all error codes in all files to the new libgpg-error scheme. + + * scdaemon.h: Include gpg-error.h and errno.h + * card.c (map_sc_err): Use unknown for the error source. + * Makefile.am: Link with libgpg-error + +2003-05-14 Werner Koch + + * atr.c, atr.h: New. + * sc-investigate.c: Dump the ATR in a human readable format. + +2003-05-08 Werner Koch + + * scdaemon.h (DBG_CARD_IO_VALUE): New. + + * sc-investigate.c: New. + * scdaemon.c (main): Removed --print-atr option. + + * iso7816.c, iso7816.h, app-openpgp.c: New. + +2003-04-29 Werner Koch + + * scdaemon.c: New options --print-atr and --reader-port + * apdu.c, apdu.h: New + + * card.c, card-p15.c, card-dinsig.c: Allow build without OpenSC. + + * Makefile.am (LDFLAGS): Removed. + + * command.c (register_commands): Adjusted for new Assuan semantics. + +2002-08-21 Werner Koch + + * scdaemon.c (main): New option --daemon so that the program is + not accidently started in the background. + +2002-08-16 Werner Koch + + * scdaemon.c: Include i18n.h. + + * card-common.h (struct p15_private_s): Forward declaration. Add + it to card_ctx_s. + * card.c (card_close): Make sure private data is released. + (card_enum_certs): New. + * card-p15.c (p15_release_private_data): New. + (init_private_data): New to work around an OpenSC weirdness. + (p15_enum_keypairs): Do an OpenSC get_objects only once. + (p15_enum_certs): New. + (card_p15_bind): Bind new function. + * command.c (cmd_learn): Return information about the certificates. + +2002-08-09 Werner Koch + + * card.c (card_get_serial_and_stamp): Use the tokeinfo serial + number as a fallback. Add a special prefix for serial numbers. + +2002-07-30 Werner Koch + + Changes to cope with OpenSC 0.7.0: + + * card.c: Removed the check for the packed opensc version. + Changed include file names of opensc. + (map_sc_err): Adjusted error codes for new opensc version. + * card-p15.c: Changed include filename of opensc. + * card-dinsig.c: Ditto. + + * card-p15.c (p15_decipher): Add flags argument to OpenSC call. + +2002-07-24 Werner Koch + + * card.c (find_simple_tlv, find_iccsn): New. + (card_get_serial_and_stamp): Improved serial number parser. + +2002-06-27 Werner Koch + + * scdaemon.c (main): Use GNUPG_DEFAULT_HOMEDIR constant. + +2002-06-15 Werner Koch + + * card-dinsig.c: Documented some stuff from the DIN norm. + +2002-04-15 Werner Koch + + * command.c (cmd_pksign, cmd_pkdecrypt): Use a copy of the key ID. + +2002-04-12 Werner Koch + + * scdaemon.c: New option --debug-sc N. + * card.c (card_open): set it here. + + * card-p15.c (p15_prepare_key): Factored out common code from ... + (p15_sign, p15_decipher): here and made the decryption work the + regular way. + +2002-04-10 Werner Koch + + * card.c (card_open): Return immediately when no reader is available. + +2002-03-27 Werner Koch + + * card.c (card_open, card_close): Adjusted for changes in OpenSC. + +2002-03-10 Werner Koch + + * card-p15.c, card-dinsig.c, card-common.h: New. + * card.c: Factored most code out to the new modules, so that we + can better support different types of card applications. + +2002-01-26 Werner Koch + + * scdaemon.c scdaemon.h, command.c: New. Based on the code from + the gpg-agent. + + Copyright 2002 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. diff --git a/scd/Makefile.am b/scd/Makefile.am new file mode 100644 index 000000000..0771beb60 --- /dev/null +++ b/scd/Makefile.am @@ -0,0 +1,72 @@ +# Copyright (C) 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 2 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +## Process this file with automake to produce Makefile.in + +localedir = $(datadir)/locale +INCLUDES = -I../intl -DLOCALEDIR=\"$(localedir)\" + +bin_PROGRAMS = scdaemon sc-investigate sc-copykeys + +AM_CPPFLAGS = -I$(top_srcdir)/common $(OPENSC_CFLAGS) $(LIBGCRYPT_CFLAGS) \ + $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) + +scdaemon_SOURCES = \ + scdaemon.c scdaemon.h \ + command.c card.c \ + card-common.h \ + card-p15.c card-dinsig.c \ + apdu.c apdu.h \ + iso7816.c iso7816.h \ + app.c app-common.h \ + app-openpgp.c + +scdaemon_LDADD = ../jnlib/libjnlib.a ../common/libcommon.a \ + $(OPENSC_LIBS) $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBASSUAN_LIBS) \ + -lgpg-error @INTLLIBS@ -ldl + +sc_investigate_SOURCES = \ + sc-investigate.c scdaemon.h \ + apdu.c apdu.h \ + iso7816.c iso7816.h \ + app.c app-common.h \ + app-openpgp.c \ + atr.c atr.h + +sc_investigate_LDADD = \ + ../jnlib/libjnlib.a ../common/libcommon.a \ + $(LIBGCRYPT_LIBS) @INTLLIBS@ -lgpg-error -ldl + + +sc_copykeys_SOURCES = \ + sc-copykeys.c scdaemon.h \ + apdu.c apdu.h \ + iso7816.c iso7816.h \ + app.c app-common.h \ + app-openpgp.c \ + atr.c atr.h + +sc_copykeys_LDADD = \ + ../jnlib/libjnlib.a ../common/libcommon.a \ + ../common/libsimple-pwquery.a \ + $(LIBGCRYPT_LIBS) -lgpg-error @INTLLIBS@ -ldl + + + + + diff --git a/scd/apdu.c b/scd/apdu.c new file mode 100644 index 000000000..6fec584b9 --- /dev/null +++ b/scd/apdu.c @@ -0,0 +1,558 @@ +/* apdu.c - ISO 7816 APDU functions and low level I/O + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "scdaemon.h" +#include "apdu.h" + +#define HAVE_CTAPI 1 + +#define MAX_READER 4 /* Number of readers we support concurrently. */ +#define CARD_CONNECT_TIMEOUT 1 /* Number of seconds to wait for + insertion of the card (1 = don't wait). */ + + + +/* A global table to keep track of active readers. */ +static struct { + int used; /* True if slot is used. */ + unsigned short port; /* port number0 = unused, 1 - dev/tty */ + int status; + unsigned char atr[33]; + size_t atrlen; +} reader_table[MAX_READER]; + + +/* ct API function pointer. */ +static char (*CT_init) (unsigned short ctn, unsigned short Pn); +static char (*CT_data) (unsigned short ctn, unsigned char *dad, + unsigned char *sad, unsigned short lc, + unsigned char *cmd, unsigned short *lr, + unsigned char *rsp); +static char (*CT_close) (unsigned short ctn); + + + + + +/* + Helper + */ + + +/* Find an unused reader slot for PORT and put it into the reader + table. Return -1 on error or the index into the reader table. */ +static int +new_reader_slot (int port) +{ + int i, reader = -1; + + if (port < 0 || port > 0xffff) + { + log_error ("new_reader_slot: invalid port %d requested\n", port); + return -1; + } + + for (i=0; i < MAX_READER; i++) + { + if (reader_table[i].used && reader_table[i].port == port) + { + log_error ("new_reader_slot: requested port %d already in use\n", + reader); + return -1; + } + else if (!reader_table[i].used && reader == -1) + reader = i; + } + if (reader == -1) + { + log_error ("new_reader_slot: out of slots\n"); + return -1; + } + reader_table[reader].used = 1; + reader_table[reader].port = port; + return reader; +} + + +static void +dump_reader_status (int reader) +{ + log_info ("reader %d: %s\n", reader, + reader_table[reader].status == 1? "Processor ICC present" : + reader_table[reader].status == 0? "Memory ICC present" : + "ICC not present" ); + + if (reader_table[reader].status != -1) + { + log_info ("reader %d: ATR=", reader); + log_printhex ("", reader_table[reader].atr, + reader_table[reader].atrlen); + } +} + + + +#ifdef HAVE_CTAPI +/* + ct API Interface + */ + +static const char * +ct_error_string (int err) +{ + switch (err) + { + case 0: return "okay"; + case -1: return "invalid data"; + case -8: return "ct error"; + case -10: return "transmission error"; + case -11: return "memory allocation error"; + case -128: return "HTSI error"; + default: return "unknown CT-API error"; + } +} + +/* Wait for the card in READER and activate it. Return -1 on error or + 0 on success. */ +static int +ct_activate_card (int reader) +{ + int rc, count; + + for (count = 0; count < CARD_CONNECT_TIMEOUT; count++) + { + unsigned char dad[1], sad[1], cmd[11], buf[256]; + unsigned short buflen; + + if (count) + sleep (1); /* FIXME: we should use a more reliable timer. */ + + /* Check whether card has been inserted. */ + dad[0] = 1; /* Destination address: CT. */ + sad[0] = 2; /* Source address: Host. */ + + cmd[0] = 0x20; /* Class byte. */ + cmd[1] = 0x13; /* Request status. */ + cmd[2] = 0x00; /* From kernel. */ + cmd[3] = 0x80; /* Return card's DO. */ + cmd[4] = 0x00; + + buflen = DIM(buf); + + rc = CT_data (reader, dad, sad, 5, cmd, &buflen, buf); + if (rc || buflen < 2 || buf[buflen-2] != 0x90) + { + log_error ("ct_activate_card: can't get status of reader %d: %s\n", + reader, ct_error_string (rc)); + return -1; + } + + if (buf[0] == 0x05) + { /* Connected, now activate the card. */ + dad[0] = 1; /* Destination address: CT. */ + sad[0] = 2; /* Source address: Host. */ + + cmd[0] = 0x20; /* Class byte. */ + cmd[1] = 0x12; /* Request ICC. */ + cmd[2] = 0x01; /* From first interface. */ + cmd[3] = 0x01; /* Return card's ATR. */ + cmd[4] = 0x00; + + buflen = DIM(buf); + + rc = CT_data (reader, dad, sad, 5, cmd, &buflen, buf); + if (rc || buflen < 2 || buf[buflen-2] != 0x90) + { + log_error ("ct_activate_card(%d): activation failed: %s\n", + reader, ct_error_string (rc)); + return -1; + } + + /* Store the type and the ATR. */ + if (buflen - 2 > DIM (reader_table[0].atr)) + { + log_error ("ct_activate_card(%d): ATR too long\n", reader); + return -1; + } + + reader_table[reader].status = buf[buflen - 1]; + memcpy (reader_table[reader].atr, buf, buflen - 2); + reader_table[reader].atrlen = buflen - 2; + return 0; + } + + } + + log_info ("ct_activate_card(%d): timeout waiting for card\n", reader); + return -1; +} + + +/* Open a reader and return an internal handle for it. PORT is a + non-negative value with the port number of the reader. USB readers + do have port numbers starting at 32769. */ +static int +open_ct_reader (int port) +{ + int rc, reader; + + reader = new_reader_slot (port); + if (reader == -1) + return reader; + + rc = CT_init (reader, (unsigned short)port); + if (rc) + { + log_error ("apdu_open_ct_reader failed on port %d: %s\n", + port, ct_error_string (rc)); + reader_table[reader].used = 0; + return -1; + } + + rc = ct_activate_card (reader); + if (rc) + { + reader_table[reader].used = 0; + return -1; + } + + dump_reader_status (reader); + return reader; +} + + +/* Actually send the APDU of length APDULEN to SLOT and return a + maximum of *BUFLEN data in BUFFER, the actual retruned size will be + set to BUFLEN. Returns: CT API error code. */ +static int +ct_send_apdu (int slot, unsigned char *apdu, size_t apdulen, + unsigned char *buffer, size_t *buflen) +{ + int rc; + unsigned char dad[1], sad[1]; + unsigned short ctbuflen; + + dad[0] = 0; /* Destination address: Card. */ + sad[0] = 2; /* Source address: Host. */ + ctbuflen = *buflen; + if (DBG_CARD_IO) + log_printhex (" CT_data:", apdu, apdulen); + rc = CT_data (slot, dad, sad, apdulen, apdu, &ctbuflen, buffer); + *buflen = ctbuflen; + + /* FIXME: map the errorcodes to GNUPG ones, so that they can be + shared between CTAPI and PCSC. */ + return rc; +} + + +#endif /*HAVE_CTAPI*/ + + +#ifdef HAVE_PCSC +/* + PC/SC Interface + */ + + +#endif /*HAVE_PCSC*/ + + +/* + Driver Access + */ + +/* Open the reader and return an internal slot number or -1 on + error. */ +int +apdu_open_reader (int port) +{ + static int ct_api_loaded; + + if (!ct_api_loaded) + { + void *handle; + + handle = dlopen ("libtowitoko.so", RTLD_LAZY); + if (!handle) + { + log_error ("apdu_open_reader: failed to open driver: %s", + dlerror ()); + return -1; + } + CT_init = dlsym (handle, "CT_init"); + CT_data = dlsym (handle, "CT_data"); + CT_close = dlsym (handle, "CT_close"); + if (!CT_init || !CT_data || !CT_close) + { + log_error ("apdu_open_reader: invalid driver\n"); + dlclose (handle); + return -1; + } + ct_api_loaded = 1; + } + return open_ct_reader (port); +} + + +unsigned char * +apdu_get_atr (int slot, size_t *atrlen) +{ + char *buf; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return NULL; + + buf = xtrymalloc (reader_table[slot].atrlen); + if (!buf) + return NULL; + memcpy (buf, reader_table[slot].atr, reader_table[slot].atrlen); + *atrlen = reader_table[slot].atrlen; + return buf; +} + + +static const char * +error_string (int slot, int rc) +{ +#ifdef HAVE_CTAPI + return ct_error_string (rc); +#elif defined(HAVE_PCSC) + return "?"; +#else + return "?"; +#endif +} + + +/* Dispatcher for the actual send_apdu fucntion. */ +static int +send_apdu (int slot, unsigned char *apdu, size_t apdulen, + unsigned char *buffer, size_t *buflen) +{ +#ifdef HAVE_CTAPI + return ct_send_apdu (slot, apdu, apdulen, buffer, buflen); +#elif defined(HAVE_PCSC) + return SW_HOST_NO_DRIVER; +#else + return SW_HOST_NO_DRIVER; +#endif +} + +/* Send an APDU to the card in SLOT. The APDU is created from all + given parameters: CLASS, INS, P0, P1, LC, DATA, LE. A value of -1 + for LC won't sent this field and the data field; in this case DATA + must also be passed as NULL. The return value is the status word + or -1 for an invalid SLOT or other non card related error. If + RETBUF is not NULL, it will receive an allocated buffer with the + returned data. The length of that data will be put into + *RETBUFLEN. The caller is reponsible for releasing the buffer even + in case of errors. */ +int +apdu_send_le(int slot, int class, int ins, int p0, int p1, + int lc, const char *data, int le, + unsigned char **retbuf, size_t *retbuflen) +{ + unsigned char result[256+10]; /* 10 extra in case of bugs in the driver. */ + size_t resultlen = 256; + unsigned char apdu[5+256+1]; + size_t apdulen; + int rc, sw; + + if (DBG_CARD_IO) + log_debug ("send apdu: c=%02X i=%02X p0=%02X p1=%02X lc=%d le=%d\n", + class, ins, p0, p1, lc, le); + + if (lc != -1 && (lc > 255 || lc < 0)) + return SW_WRONG_LENGTH; + if (le != -1 && (le > 256 || le < 1)) + return SW_WRONG_LENGTH; + if ((!data && lc != -1) || (data && lc == -1)) + return SW_HOST_INV_VALUE; + + apdulen = 0; + apdu[apdulen++] = class; + apdu[apdulen++] = ins; + apdu[apdulen++] = p0; + apdu[apdulen++] = p1; + if (lc != -1) + { + apdu[apdulen++] = lc; + memcpy (apdu+apdulen, data, lc); + apdulen += lc; + } + if (le != -1) + apdu[apdulen++] = le; /* Truncation is okay becuase 0 means 256. */ + assert (sizeof (apdu) >= apdulen); + /* As safeguard don't pass any garbage from the stack to the driver. */ + memset (apdu+apdulen, 0, sizeof (apdu) - apdulen); + rc = send_apdu (slot, apdu, apdulen, result, &resultlen); + if (rc || resultlen < 2) + { + log_error ("apdu_send_simple(%d) failed: %s\n", + slot, error_string (slot, rc)); + return SW_HOST_INCOMPLETE_CARD_RESPONSE; + } + sw = (result[resultlen-2] << 8) | result[resultlen-1]; + /* store away the returned data but strip the statusword. */ + resultlen -= 2; + if (DBG_CARD_IO) + { + log_debug (" response: sw=%04X datalen=%d\n", sw, resultlen); + if ( !retbuf && (sw == SW_SUCCESS || (sw & 0xff00) == SW_MORE_DATA)) + log_printhex (" dump: ", result, resultlen); + } + + if (sw == SW_SUCCESS) + { + if (retbuf) + { + *retbuf = xtrymalloc (resultlen? resultlen : 1); + if (!*retbuf) + return SW_HOST_OUT_OF_CORE; + *retbuflen = resultlen; + memcpy (*retbuf, result, resultlen); + } + } + else if ((sw & 0xff00) == SW_MORE_DATA) + { + unsigned char *p = NULL, *tmp; + size_t bufsize = 4096; + + /* It is likely that we need to return much more data, so we + start off with a large buffer. */ + if (retbuf) + { + *retbuf = p = xtrymalloc (bufsize); + if (!*retbuf) + return SW_HOST_OUT_OF_CORE; + assert (resultlen < bufsize); + memcpy (p, result, resultlen); + p += resultlen; + } + + do + { + int len = (sw & 0x00ff); + + log_debug ("apdu_send_simple(%d): %d more bytes available\n", + slot, len); + apdulen = 0; + apdu[apdulen++] = class; + apdu[apdulen++] = 0xC0; + apdu[apdulen++] = 0; + apdu[apdulen++] = 0; + apdu[apdulen++] = 64; /* that is 256 bytes for Le */ + memset (apdu+apdulen, 0, sizeof (apdu) - apdulen); + rc = send_apdu (slot, apdu, apdulen, result, &resultlen); + if (rc || resultlen < 2) + { + log_error ("apdu_send_simple(%d) for get response failed: %s\n", + slot, error_string (slot, rc)); + return SW_HOST_INCOMPLETE_CARD_RESPONSE; + } + sw = (result[resultlen-2] << 8) | result[resultlen-1]; + resultlen -= 2; + if (DBG_CARD_IO) + { + log_debug (" more: sw=%04X datalen=%d\n", sw, resultlen); + if (!retbuf && (sw==SW_SUCCESS || (sw&0xff00)==SW_MORE_DATA)) + log_printhex (" dump: ", result, resultlen); + } + + if ((sw & 0xff00) == SW_MORE_DATA || sw == SW_SUCCESS) + { + if (retbuf) + { + if (p - *retbuf + resultlen > bufsize) + { + bufsize += resultlen > 4096? resultlen: 4096; + tmp = xtryrealloc (*retbuf, bufsize); + if (!tmp) + return SW_HOST_OUT_OF_CORE; + p = tmp + (p - *retbuf); + *retbuf = tmp; + } + memcpy (p, result, resultlen); + p += resultlen; + } + } + else + log_info ("apdu_send_simple(%d) " + "got unexpected status %04X from get response\n", + slot, sw); + } + while ((sw & 0xff00) == SW_MORE_DATA); + + if (retbuf) + { + *retbuflen = p - *retbuf; + tmp = xtryrealloc (*retbuf, *retbuflen); + if (tmp) + *retbuf = tmp; + } + } + if (DBG_CARD_IO && retbuf && sw == SW_SUCCESS) + log_printhex (" dump: ", *retbuf, *retbuflen); + + return sw; +} + +/* Send an APDU to the card in SLOT. The APDU is created from all + given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for + LC won't sent this field and the data field; in this case DATA must + also be passed as NULL. The return value is the status word or -1 + for an invalid SLOT or other non card related error. If RETBUF is + not NULL, it will receive an allocated buffer with the returned + data. The length of that data will be put into *RETBUFLEN. The + caller is reponsible for releasing the buffer even in case of + errors. */ +int +apdu_send (int slot, int class, int ins, int p0, int p1, + int lc, const char *data, unsigned char **retbuf, size_t *retbuflen) +{ + return apdu_send_le (slot, class, ins, p0, p1, lc, data, 256, + retbuf, retbuflen); +} + +/* Send an APDU to the card in SLOT. The APDU is created from all + given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for + LC won't sent this field and the data field; in this case DATA must + also be passed as NULL. The return value is the status word or -1 + for an invalid SLOT or other non card related error. No data will be + returned. */ +int +apdu_send_simple (int slot, int class, int ins, int p0, int p1, + int lc, const char *data) +{ + return apdu_send_le (slot, class, ins, p0, p1, lc, data, -1, NULL, NULL); +} + + + + diff --git a/scd/apdu.h b/scd/apdu.h new file mode 100644 index 000000000..44166a3fe --- /dev/null +++ b/scd/apdu.h @@ -0,0 +1,73 @@ +/* apdu.h - ISO 7816 APDU functions and low level I/O + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef APDU_H +#define APDU_H + +/* ISO 7816 values for the statusword are defined here because they + should not be visible to the users of the actual ISO command + API. */ +enum { + SW_MORE_DATA = 0x6100, /* Note: that the low byte must be + masked of.*/ + SW_EEPROM_FAILURE = 0x6581, + SW_WRONG_LENGTH = 0x6700, + SW_CHV_WRONG = 0x6982, + SW_CHV_BLOCKED = 0x6983, + SW_USE_CONDITIONS = 0x6985, + SW_NOT_SUPPORTED = 0x6a81, + SW_BAD_PARAMETER = 0x6a80, /* (in the data field) */ + SW_REF_NOT_FOUND = 0x6a88, + SW_BAD_P0_P1 = 0x6b00, + SW_INS_NOT_SUP = 0x6d00, + SW_CLA_NOT_SUP = 0x6e00, + SW_SUCCESS = 0x9000, + + /* The follwoing statuswords are no real ones but used to map host + OS errors into status words. A status word is 16 bit so that + those values can't be issued by a card. */ + SW_HOST_OUT_OF_CORE = 0x10001, /* No way yet to differentiate + between errnos on a failed malloc. */ + SW_HOST_INV_VALUE = 0x10002, + SW_HOST_INCOMPLETE_CARD_RESPONSE = 0x10003, +}; + + + +/* Note , that apdu_open_reader returns no status word but -1 on error. */ +int apdu_open_reader (int port); +unsigned char *apdu_get_atr (int slot, size_t *atrlen); + + +/* The apdu send functions do return status words. */ +int apdu_send_simple (int slot, int class, int ins, int p0, int p1, + int lc, const char *data); +int apdu_send (int slot, int class, int ins, int p0, int p1, + int lc, const char *data, + unsigned char **retbuf, size_t *retbuflen); +int apdu_send_le (int slot, int class, int ins, int p0, int p1, + int lc, const char *data, int le, + unsigned char **retbuf, size_t *retbuflen); + + +#endif /*APDU_H*/ + + + diff --git a/scd/app-common.h b/scd/app-common.h new file mode 100644 index 000000000..282f82715 --- /dev/null +++ b/scd/app-common.h @@ -0,0 +1,128 @@ +/* app-common.h - Common declarations for all card applications + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef GNUPG_SCD_APP_COMMON_H +#define GNUPG_SCD_APP_COMMON_H + +struct app_ctx_s { + int initialized; /* The application has been initialied and the + function pointers may be used. Note that for + unsupported operations the particular + function pointer is set to NULL */ + int slot; /* Used reader. */ + unsigned char *serialno; /* Serialnumber in raw form, allocated. */ + size_t serialnolen; /* Length in octets of serialnumber. */ + unsigned int card_version; + int did_chv1; + int did_chv2; + int did_chv3; + struct { + int (*learn_status) (APP app, CTRL ctrl); + int (*setattr) (APP app, const char *name, + int (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *value, size_t valuelen); + int (*sign) (APP app, + const char *keyidstr, int hashalgo, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ); + int (*auth) (APP app, const char *keyidstr, + int (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen); + int (*decipher) (APP app, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen); + int (*genkey) (APP app, CTRL ctrl, + const char *keynostr, unsigned int flags, + int (*pincb)(void*, const char *, char **), + void *pincb_arg); + int (*change_pin) (APP app, CTRL ctrl, + const char *chvnostr, int reset_mode, + int (*pincb)(void*, const char *, char **), + void *pincb_arg); + } fnc; + + +}; + +/*-- app.c --*/ +APP select_application (void); +int app_get_serial_and_stamp (APP app, char **serial, time_t *stamp); +int app_write_learn_status (APP app, CTRL ctrl); +int app_setattr (APP app, const char *name, + int (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *value, size_t valuelen); +int app_sign (APP app, const char *keyidstr, int hashalgo, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ); +int app_auth (APP app, const char *keyidstr, + int (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen); +int app_decipher (APP app, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ); +int app_genkey (APP app, CTRL ctrl, const char *keynostr, unsigned int flags, + int (*pincb)(void*, const char *, char **), + void *pincb_arg); +int app_get_challenge (APP app, size_t nbytes, unsigned char *buffer); +int app_change_pin (APP app, CTRL ctrl, const char *chvnostr, int reset_mode, + int (*pincb)(void*, const char *, char **), + void *pincb_arg); + + +/*-- app-openpgp.c --*/ +int app_select_openpgp (APP app, unsigned char **sn, size_t *snlen); + +int app_openpgp_cardinfo (APP app, + char **serialno, + char **disp_name, + char **pubkey_url, + unsigned char **fpr1, + unsigned char **fpr2, + unsigned char **fpr3); +int app_openpgp_storekey (APP app, int keyno, + unsigned char *template, size_t template_len, + time_t created_at, + const unsigned char *m, size_t mlen, + const unsigned char *e, size_t elen, + int (*pincb)(void*, const char *, char **), + void *pincb_arg); +int app_openpgp_readkey (APP app, int keyno, + unsigned char **m, size_t *mlen, + unsigned char **e, size_t *elen); + + +#endif /*GNUPG_SCD_APP_COMMON_H*/ + + + diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c new file mode 100644 index 000000000..09a19699d --- /dev/null +++ b/scd/app-openpgp.c @@ -0,0 +1,1482 @@ +/* app-openpgp.c - The OpenPGP card application. + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include + +#include "scdaemon.h" +#include "app-common.h" +#include "iso7816.h" + + + +static struct { + int tag; + int constructed; + int get_from; /* Constructed DO with this DO or 0 for direct access. */ + int binary; + char *desc; +} data_objects[] = { + { 0x005E, 0, 0, 1, "Login Data" }, + { 0x5F50, 0, 0, 0, "URL" }, + { 0x0065, 1, 0, 1, "Cardholder Related Data"}, + { 0x005B, 0, 0x65, 0, "Name" }, + { 0x5F2D, 0, 0x65, 0, "Language preferences" }, + { 0x5F35, 0, 0x65, 0, "Sex" }, + { 0x006E, 1, 0, 1, "Application Related Data" }, + { 0x004F, 0, 0x6E, 1, "AID" }, + { 0x0073, 1, 0, 1, "Discretionary Data Objects" }, + { 0x0047, 0, 0x6E, 1, "Card Capabilities" }, + { 0x00C0, 0, 0x6E, 1, "Extended Card Capabilities" }, + { 0x00C1, 0, 0x6E, 1, "Algorithm Attributes Signature" }, + { 0x00C2, 0, 0x6E, 1, "Algorithm Attributes Decryption" }, + { 0x00C3, 0, 0x6E, 1, "Algorithm Attributes Authentication" }, + { 0x00C4, 0, 0x6E, 1, "CHV Status Bytes" }, + { 0x00C5, 0, 0x6E, 1, "Fingerprints" }, + { 0x00C6, 0, 0x6E, 1, "CA Fingerprints" }, + { 0x007A, 1, 0, 1, "Security Support Template" }, + { 0x0093, 0, 0x7A, 1, "Digital Signature Counter" }, + { 0 } +}; + + +static unsigned long get_sig_counter (APP app); + + +/* Locate a TLV encoded data object in BUFFER of LENGTH and + return a pointer to value as well as its length in NBYTES. Return + NULL if it was not found. Note, that the function does not check + whether the value fits into the provided buffer. + + FIXME: Move this to an extra file, it is mostly duplicated from card.c. +*/ +static const unsigned char * +find_tlv (const unsigned char *buffer, size_t length, + int tag, size_t *nbytes, int nestlevel) +{ + const unsigned char *s = buffer; + size_t n = length; + size_t len; + int this_tag; + int composite; + + for (;;) + { + buffer = s; + if (n < 2) + return NULL; /* buffer definitely too short for tag and length. */ + if (!*s || *s == 0xff) + { /* Skip optional filler between TLV objects. */ + s++; + n--; + continue; + } + composite = !!(*s & 0x20); + if ((*s & 0x1f) == 0x1f) + { /* more tag bytes to follow */ + s++; + n--; + if (n < 2) + return NULL; /* buffer definitely too short for tag and length. */ + if ((*s & 0x1f) == 0x1f) + return NULL; /* We support only up to 2 bytes. */ + this_tag = (s[-1] << 8) | (s[0] & 0x7f); + } + else + this_tag = s[0]; + len = s[1]; + s += 2; n -= 2; + if (len < 0x80) + ; + else if (len == 0x81) + { /* One byte length follows. */ + if (!n) + return NULL; /* we expected 1 more bytes with the length. */ + len = s[0]; + s++; n--; + } + else if (len == 0x82) + { /* Two byte length follows. */ + if (n < 2) + return NULL; /* we expected 2 more bytes with the length. */ + len = (s[0] << 8) | s[1]; + s += 2; n -= 2; + } + else + return NULL; /* APDU limit is 65535, thus it does not make + sense to assume longer length fields. */ + + if (composite && nestlevel < 100) + { /* Dive into this composite DO after checking for too deep + nesting. */ + const unsigned char *tmp_s; + size_t tmp_len; + + tmp_s = find_tlv (s, len, tag, &tmp_len, nestlevel+1); + if (tmp_s) + { + *nbytes = tmp_len; + return tmp_s; + } + } + + if (this_tag == tag) + { + *nbytes = len; + return s; + } + if (len > n) + return NULL; /* buffer too short to skip to the next tag. */ + s += len; n -= len; + } +} + + +/* Get the DO identified by TAG from the card in SLOT and return a + buffer with its content in RESULT and NBYTES. The return value is + NULL if not found or a pointer which must be used to release the + buffer holding value. */ +static void * +get_one_do (int slot, int tag, unsigned char **result, size_t *nbytes) +{ + int rc, i; + unsigned char *buffer; + size_t buflen; + unsigned char *value; + size_t valuelen; + + *result = NULL; + *nbytes = 0; + for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++) + ; + + value = NULL; + rc = -1; + if (data_objects[i].tag && data_objects[i].get_from) + { + rc = iso7816_get_data (slot, data_objects[i].get_from, + &buffer, &buflen); + if (!rc) + { + const unsigned char *s; + + s = find_tlv (buffer, buflen, tag, &valuelen, 0); + if (!s) + value = NULL; /* not found */ + else if (valuelen > buflen - (s - buffer)) + { + log_error ("warning: constructed DO too short\n"); + value = NULL; + xfree (buffer); buffer = NULL; + } + else + value = buffer + (s - buffer); + } + } + + if (!value) /* Not in a constructed DO, try simple. */ + { + rc = iso7816_get_data (slot, tag, &buffer, &buflen); + if (!rc) + { + value = buffer; + valuelen = buflen; + } + } + + if (!rc) + { + *nbytes = valuelen; + *result = value; + return buffer; + } + return NULL; +} + +#if 0 /* not used */ +static void +dump_one_do (int slot, int tag) +{ + int rc, i; + unsigned char *buffer; + size_t buflen; + const char *desc; + int binary; + const unsigned char *value; + size_t valuelen; + + for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++) + ; + desc = data_objects[i].tag? data_objects[i].desc : "?"; + binary = data_objects[i].tag? data_objects[i].binary : 1; + + value = NULL; + rc = -1; + if (data_objects[i].tag && data_objects[i].get_from) + { + rc = iso7816_get_data (slot, data_objects[i].get_from, + &buffer, &buflen); + if (!rc) + { + value = find_tlv (buffer, buflen, tag, &valuelen, 0); + if (!value) + ; /* not found */ + else if (valuelen > buflen - (value - buffer)) + { + log_error ("warning: constructed DO too short\n"); + value = NULL; + xfree (buffer); buffer = NULL; + } + } + } + + if (!value) /* Not in a constructed DO, try simple. */ + { + rc = iso7816_get_data (slot, tag, &buffer, &buflen); + if (!rc) + { + value = buffer; + valuelen = buflen; + } + } + if (rc == 0x6a88) + log_info ("DO `%s' not available\n", desc); + else if (rc) + log_info ("DO `%s' not available (rc=%04X)\n", desc, rc); + else + { + if (binary) + { + log_info ("DO `%s': ", desc); + log_printhex ("", value, valuelen); + } + else + log_info ("DO `%s': `%.*s'\n", + desc, (int)valuelen, value); /* FIXME: sanitize */ + xfree (buffer); + } +} +#endif /*not used*/ + + +static void +dump_all_do (int slot) +{ + int rc, i, j; + unsigned char *buffer; + size_t buflen; + + for (i=0; data_objects[i].tag; i++) + { + if (data_objects[i].get_from) + continue; + + rc = iso7816_get_data (slot, data_objects[i].tag, &buffer, &buflen); + if (rc == 0x6a88) + ; + else if (rc) + log_info ("DO `%s' not available (rc=%04X)\n", + data_objects[i].desc, rc); + else + { + if (data_objects[i].binary) + { + log_info ("DO `%s': ", data_objects[i].desc); + log_printhex ("", buffer, buflen); + } + else + log_info ("DO `%s': `%.*s'\n", + data_objects[i].desc, + (int)buflen, buffer); /* FIXME: sanitize */ + } + + if (data_objects[i].constructed) + { + for (j=0; data_objects[j].tag; j++) + { + const unsigned char *value; + size_t valuelen; + + if (j==i || data_objects[i].tag != data_objects[j].get_from) + continue; + value = find_tlv (buffer, buflen, + data_objects[j].tag, &valuelen, 0); + if (!value) + ; /* not found */ + else if (valuelen > buflen - (value - buffer)) + log_error ("warning: constructed DO too short\n"); + else + { + if (data_objects[j].binary) + { + log_info ("DO `%s': ", data_objects[j].desc); + log_printhex ("", value, valuelen); + } + else + log_info ("DO `%s': `%.*s'\n", + data_objects[j].desc, + (int)valuelen, value); /* FIXME: sanitize */ + } + } + } + xfree (buffer); buffer = NULL; + } +} + + +/* Count the number of bits, assuming the A represents an unsigned big + integer of length LEN bytes. */ +static unsigned int +count_bits (const unsigned char *a, size_t len) +{ + unsigned int n = len * 8; + int i; + + for (; len && !*a; len--, a++, n -=8) + ; + if (len) + { + for (i=7; i && !(*a & (1<> 8; /* 2 byte length header */ + *p++ = n; + *p++ = 4; /* key packet version */ + *p++ = timestamp >> 24; + *p++ = timestamp >> 16; + *p++ = timestamp >> 8; + *p++ = timestamp; + *p++ = 1; /* RSA */ + nbits = count_bits (m, mlen); + *p++ = nbits >> 8; + *p++ = nbits; + memcpy (p, m, mlen); p += mlen; + nbits = count_bits (e, elen); + *p++ = nbits >> 8; + *p++ = nbits; + memcpy (p, e, elen); p += elen; + + log_printhex ("fprbuf:", buffer, n+3); + gcry_md_hash_buffer (GCRY_MD_SHA1, fpr, buffer, n+3); + + xfree (buffer); + + rc = iso7816_put_data (slot, (card_version > 0x0007? 0xC7 : 0xC6) + + keynumber, fpr, 20); + if (rc) + log_error ("failed to store the fingerprint: rc=%04X\n", rc); + + return rc; +} + + +static void +send_fpr_if_not_null (CTRL ctrl, const char *keyword, + int number, const unsigned char *fpr) +{ + int i; + char buf[41]; + char numbuf[25]; + + for (i=0; i < 20 && !fpr[i]; i++) + ; + if (i==20) + return; /* All zero. */ + for (i=0; i< 20; i++) + sprintf (buf+2*i, "%02X", fpr[i]); + if (number == -1) + *numbuf = 0; /* Don't print the key number */ + else + sprintf (numbuf, "%d", number); + send_status_info (ctrl, keyword, + numbuf, (size_t)strlen(numbuf), + buf, (size_t)strlen (buf), NULL, 0); +} + +static void +send_key_data (CTRL ctrl, const char *name, + const unsigned char *a, size_t alen) +{ + char *p, *buf = xmalloc (alen*2+1); + + for (p=buf; alen; a++, alen--, p += 2) + sprintf (p, "%02X", *a); + + send_status_info (ctrl, "KEY-DATA", + name, (size_t)strlen(name), + buf, (size_t)strlen (buf), + NULL, 0); + xfree (buf); +} + + + +static int +do_learn_status (APP app, CTRL ctrl) +{ + void *relptr; + unsigned char *value; + size_t valuelen; + int i; + + relptr = get_one_do (app->slot, 0x005B, &value, &valuelen); + if (relptr) + { + send_status_info (ctrl, "DISP-NAME", value, valuelen, NULL, 0); + xfree (relptr); + } + relptr = get_one_do (app->slot, 0x5F2D, &value, &valuelen); + if (relptr) + { + send_status_info (ctrl, "DISP-LANG", value, valuelen, NULL, 0); + xfree (relptr); + } + relptr = get_one_do (app->slot, 0x5F35, &value, &valuelen); + if (relptr) + { + send_status_info (ctrl, "DISP-SEX", value, valuelen, NULL, 0); + xfree (relptr); + } + relptr = get_one_do (app->slot, 0x5F50, &value, &valuelen); + if (relptr) + { + send_status_info (ctrl, "PUBKEY-URL", value, valuelen, NULL, 0); + xfree (relptr); + } + relptr = get_one_do (app->slot, 0x005E, &value, &valuelen); + if (relptr) + { + send_status_info (ctrl, "LOGIN-DATA", value, valuelen, NULL, 0); + xfree (relptr); + } + + relptr = get_one_do (app->slot, 0x00C5, &value, &valuelen); + if (relptr && valuelen >= 60) + { + for (i=0; i < 3; i++) + send_fpr_if_not_null (ctrl, "KEY-FPR", i+1, value+i*20); + } + xfree (relptr); + relptr = get_one_do (app->slot, 0x00C6, &value, &valuelen); + if (relptr && valuelen >= 60) + { + for (i=0; i < 3; i++) + send_fpr_if_not_null (ctrl, "CA-FPR", i+1, value+i*20); + } + xfree (relptr); + relptr = get_one_do (app->slot, 0x00C4, &value, &valuelen); + if (relptr) + { + char numbuf[7*23]; + + for (i=0,*numbuf=0; i < valuelen && i < 7; i++) + sprintf (numbuf+strlen (numbuf), " %d", value[i]); + send_status_info (ctrl, "CHV-STATUS", numbuf, strlen (numbuf), NULL, 0); + xfree (relptr); + } + + { + unsigned long ul = get_sig_counter (app); + char numbuf[23]; + + sprintf (numbuf, "%lu", ul); + send_status_info (ctrl, "SIG-COUNTER", numbuf, strlen (numbuf), NULL, 0); + } + return 0; +} + + +/* Handle the SETATTR operation. All arguments are already basically + checked. */ +static int +do_setattr (APP app, const char *name, + int (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *value, size_t valuelen) +{ + gpg_error_t rc; + int idx; + static struct { + const char *name; + int tag; + } table[] = { + { "DISP-NAME", 0x005B }, + { "LOGIN-DATA", 0x005E }, + { "DISP-LANG", 0x5F2D }, + { "DISP-SEX", 0x5F35 }, + { "PUBKEY-URL", 0x5F50 }, + { "CHV-STATUS-1", 0x00C4 }, + { "CA-FPR-1", 0x00CA }, + { "CA-FPR-2", 0x00CB }, + { "CA-FPR-3", 0x00CC }, + { NULL, 0 } + }; + + + for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++) + ; + if (!table[idx].name) + return gpg_error (GPG_ERR_INV_NAME); + + if (!app->did_chv3) + { + char *pinvalue; + + rc = pincb (pincb_arg, "Admin PIN (CHV3)", + &pinvalue); +/* pinvalue = xstrdup ("12345678"); */ +/* rc = 0; */ + if (rc) + { + log_info ("PIN callback returned error: %s\n", gpg_strerror (rc)); + return rc; + } + + rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue)); + xfree (pinvalue); + if (rc) + { + log_error ("verify CHV3 failed\n"); + rc = gpg_error (GPG_ERR_GENERAL); + return rc; + } + app->did_chv3 = 1; + } + + rc = iso7816_put_data (app->slot, table[idx].tag, value, valuelen); + if (rc) + log_error ("failed to set `%s': %s\n", table[idx].name, gpg_strerror (rc)); + /* FIXME: If this fails we should *once* try again after + doing a verify command, so that in case of a problem with + tracking the verify operation we have a fallback. */ + + return rc; +} + +/* Handle the PASSWD command. */ +static int +do_change_pin (APP app, CTRL ctrl, const char *chvnostr, int reset_mode, + int (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + int rc = 0; + int chvno = atoi (chvnostr); + char *pinvalue; + + if (reset_mode && chvno == 3) + { + rc = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + else if (reset_mode || chvno == 3) + { + rc = pincb (pincb_arg, "Admin PIN", &pinvalue); + if (rc) + { + log_error ("error getting PIN: %s\n", gpg_strerror (rc)); + goto leave; + } + rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue)); + xfree (pinvalue); + if (rc) + { + log_error ("verify CHV3 failed: rc=%04X\n", rc); + goto leave; + } + } + else if (chvno == 1) + { + rc = pincb (pincb_arg, "Signature PIN", &pinvalue); + if (rc) + { + log_error ("error getting PIN: %s\n", gpg_strerror (rc)); + goto leave; + } + rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue)); + xfree (pinvalue); + if (rc) + { + log_error ("verify CHV1 failed: rc=%04X\n", rc); + goto leave; + } + } + else if (chvno == 2) + { + rc = pincb (pincb_arg, "Decryption PIN", &pinvalue); + if (rc) + { + log_error ("error getting PIN: %s\n", gpg_strerror (rc)); + goto leave; + } + rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue)); + xfree (pinvalue); + if (rc) + { + log_error ("verify CHV2 failed: rc=%04X\n", rc); + goto leave; + } + } + else + { + rc = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + + + rc = pincb (pincb_arg, chvno == 1? "New Signature PIN" : + chvno == 2? "New Decryption PIN" : + chvno == 3? "New Admin PIN" : "?", &pinvalue); + if (rc) + { + log_error ("error getting new PIN: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (reset_mode) + rc = iso7816_reset_retry_counter (app->slot, 0x80 + chvno, + pinvalue, strlen (pinvalue)); + else + rc = iso7816_change_reference_data (app->slot, 0x80 + chvno, + NULL, 0, + pinvalue, strlen (pinvalue)); + xfree (pinvalue); + + + leave: + return rc; +} + + + +/* Handle the GENKEY command. */ +static int +do_genkey (APP app, CTRL ctrl, const char *keynostr, unsigned int flags, + int (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + int rc; + int i; + char numbuf[30]; + unsigned char fprbuf[20]; + const unsigned char *fpr; + const unsigned char *keydata, *m, *e; + unsigned char *buffer; + size_t buflen, keydatalen, n, mlen, elen; + time_t created_at; + int keyno = atoi (keynostr); + int force = (flags & 1); + time_t start_at; + + if (keyno < 1 || keyno > 3) + return gpg_error (GPG_ERR_INV_ID); + keyno--; + + rc = iso7816_get_data (app->slot, 0x006E, &buffer, &buflen); + if (rc) + { + log_error ("error reading application data\n"); + return gpg_error (GPG_ERR_GENERAL); + } + fpr = find_tlv (buffer, buflen, 0x00C5, &n, 0); + if (!fpr || n != 60) + { + rc = gpg_error (GPG_ERR_GENERAL); + log_error ("error reading fingerprint DO\n"); + goto leave; + } + fpr += 20*keyno; + for (i=0; i < 20 && !fpr[i]; i++) + ; + if (i!=20 && !force) + { + rc = gpg_error (GPG_ERR_EEXIST); + log_error ("key already exists\n"); + goto leave; + } + else if (i!=20) + log_info ("existing key will be replaced\n"); + else + log_info ("generating new key\n"); + + { + char *pinvalue; + rc = pincb (pincb_arg, "Admin PIN", &pinvalue); + if (rc) + { + log_error ("error getting PIN: %s\n", gpg_strerror (rc)); + return rc; + } + rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue)); + xfree (pinvalue); + } + if (rc) + { + log_error ("verify CHV3 failed: rc=%04X\n", rc); + goto leave; + } + + xfree (buffer); buffer = NULL; +#if 1 + log_info ("please wait while key is being generated ...\n"); + start_at = time (NULL); + rc = iso7816_generate_keypair +#else +#warning key generation temporary replaced by reading an existing key. + rc = iso7816_read_public_key +#endif + (app->slot, + keyno == 0? "\xB6" : + keyno == 1? "\xB8" : "\xA4", + 2, + &buffer, &buflen); + if (rc) + { + rc = gpg_error (GPG_ERR_CARD); + log_error ("generating key failed\n"); + goto leave; + } + log_info ("key generation completed (%d seconds)\n", + (int)(time (NULL) - start_at)); + keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen, 0); + if (!keydata) + { + rc = gpg_error (GPG_ERR_CARD); + log_error ("response does not contain the public key data\n"); + goto leave; + } + + m = find_tlv (keydata, keydatalen, 0x0081, &mlen, 0); + if (!m) + { + rc = gpg_error (GPG_ERR_CARD); + log_error ("response does not contain the RSA modulus\n"); + goto leave; + } +/* log_printhex ("RSA n:", m, mlen); */ + send_key_data (ctrl, "n", m, mlen); + + e = find_tlv (keydata, keydatalen, 0x0082, &elen, 0); + if (!e) + { + rc = gpg_error (GPG_ERR_CARD); + log_error ("response does not contain the RSA public exponent\n"); + goto leave; + } +/* log_printhex ("RSA e:", e, elen); */ + send_key_data (ctrl, "e", e, elen); + + created_at = gnupg_get_time (); + sprintf (numbuf, "%lu", (unsigned long)created_at); + send_status_info (ctrl, "KEY-CREATED-AT", + numbuf, (size_t)strlen(numbuf), NULL, 0); + + rc = store_fpr (app->slot, keyno, (u32)created_at, + m, mlen, e, elen, fprbuf, app->card_version); + if (rc) + goto leave; + send_fpr_if_not_null (ctrl, "KEY-FPR", -1, fprbuf); + + + leave: + xfree (buffer); + return rc; +} + + +static unsigned long +get_sig_counter (APP app) +{ + void *relptr; + unsigned char *value; + size_t valuelen; + unsigned long ul; + + relptr = get_one_do (app->slot, 0x0093, &value, &valuelen); + if (!relptr) + return 0; + if (valuelen == 3 ) + ul = (value[0] << 16) | (value[1] << 8) | value[2]; + else + { + log_error ("invalid structure of OpenPGP card (DO 0x93)\n"); + ul = 0; + } + xfree (relptr); + return ul; +} + +static int +compare_fingerprint (APP app, int keyno, unsigned char *sha1fpr) +{ + const unsigned char *fpr; + unsigned char *buffer; + size_t buflen, n; + int rc, i; + + assert (keyno >= 1 && keyno <= 3); + + rc = iso7816_get_data (app->slot, 0x006E, &buffer, &buflen); + if (rc) + { + log_error ("error reading application data\n"); + return gpg_error (GPG_ERR_GENERAL); + } + fpr = find_tlv (buffer, buflen, 0x00C5, &n, 0); + if (!fpr || n != 60) + { + xfree (buffer); + log_error ("error reading fingerprint DO\n"); + return gpg_error (GPG_ERR_GENERAL); + } + fpr += (keyno-1)*20; + for (i=0; i < 20; i++) + if (sha1fpr[i] != fpr[i]) + { + xfree (buffer); + return gpg_error (GPG_ERR_WRONG_SECKEY); + } + xfree (buffer); + return 0; +} + + + +/* Compute a digital signature on INDATA which is expected to be the + raw message digest. For this application the KEYIDSTR consists of + the serialnumber and the fingerprint delimited by a slash. + + Note that this fucntion may return the error code + GPG_ERR_WRONG_CARD to indicate that the card currently present does + not match the one required for the requested action (e.g. the + serial number does not match). */ +static int +do_sign (APP app, const char *keyidstr, int hashalgo, + int (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */ + { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, + 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; + static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */ + { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, + 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 }; + int rc; + unsigned char data[35]; + unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */ + const char *s; + int n; + const char *fpr = NULL; + unsigned long sigcount; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + if (indatalen != 20) + return gpg_error (GPG_ERR_INV_VALUE); + + /* Check whether an OpenPGP card of any version has been requested. */ + if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; hexdigitp (s); s++, n++) + ; + if (n != 32) + return gpg_error (GPG_ERR_INV_ID); + else if (!*s) + ; /* no fingerprint given: we allow this for now. */ + else if (*s == '/') + fpr = s + 1; + else + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; n < 16; s += 2, n++) + tmp_sn[n] = xtoi_2 (s); + + if (app->serialnolen != 16) + return gpg_error (GPG_ERR_INV_CARD); + if (memcmp (app->serialno, tmp_sn, 16)) + return gpg_error (GPG_ERR_WRONG_CARD); + + /* If a fingerprint has been specified check it against the one on + the card. This is allows for a meaningful error message in case + the key on the card has been replaced but the shadow information + known to gpg was not updated. If there is no fingerprint, gpg + will detect a bogus signature anyway due to the + verify-after-signing feature. */ + if (fpr) + { + for (s=fpr, n=0; hexdigitp (s); s++, n++) + ; + if (n != 40) + return gpg_error (GPG_ERR_INV_ID); + else if (!*s) + ; /* okay */ + else + return gpg_error (GPG_ERR_INV_ID); + + for (s=fpr, n=0; n < 20; s += 2, n++) + tmp_sn[n] = xtoi_2 (s); + rc = compare_fingerprint (app, 1, tmp_sn); + if (rc) + return rc; + } + + if (hashalgo == GCRY_MD_SHA1) + memcpy (data, sha1_prefix, 15); + else if (hashalgo == GCRY_MD_RMD160) + memcpy (data, rmd160_prefix, 15); + else + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + memcpy (data+15, indata, indatalen); + + sigcount = get_sig_counter (app); + log_info ("signatures created so far: %lu\n", sigcount); + + /* FIXME: Check whether we are really required to enter the PIN for + each signature. There is a DO for this. */ + if (!app->did_chv1 || 1) + { + char *pinvalue; + + { + char *prompt; + if (asprintf (&prompt, "Signature PIN [sigs done: %lu]", sigcount) < 0) + return gpg_error_from_errno (errno); + rc = pincb (pincb_arg, prompt, &pinvalue); + free (prompt); + } +/* pinvalue = xstrdup ("123456"); */ +/* rc = 0; */ + if (rc) + { + log_info ("PIN callback returned error: %s\n", gpg_strerror (rc)); + return rc; + } + + rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue)); + xfree (pinvalue); + if (rc) + { + log_error ("verify CHV1 failed\n"); + rc = gpg_error (GPG_ERR_GENERAL); + return rc; + } + app->did_chv1 = 1; + } + + rc = iso7816_compute_ds (app->slot, data, 35, outdata, outdatalen); + return rc; +} + +/* Compute a digital signature using the INTERNAL AUTHENTICATE command + on INDATA which is expected to be the raw message digest. For this + application the KEYIDSTR consists of the serialnumber and the + fingerprint delimited by a slash. + + Note that this fucntion may return the error code + GPG_ERR_WRONG_CARD to indicate that the card currently present does + not match the one required for the requested action (e.g. the + serial number does not match). */ +static int +do_auth (APP app, const char *keyidstr, + int (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + int rc; + unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */ + const char *s; + int n; + const char *fpr = NULL; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + if (indatalen > 50) /* For a 1024 bit key. */ + return gpg_error (GPG_ERR_INV_VALUE); + + /* Check whether an OpenPGP card of any version has been requested. */ + if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; hexdigitp (s); s++, n++) + ; + if (n != 32) + return gpg_error (GPG_ERR_INV_ID); + else if (!*s) + ; /* no fingerprint given: we allow this for now. */ + else if (*s == '/') + fpr = s + 1; + else + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; n < 16; s += 2, n++) + tmp_sn[n] = xtoi_2 (s); + + if (app->serialnolen != 16) + return gpg_error (GPG_ERR_INV_CARD); + if (memcmp (app->serialno, tmp_sn, 16)) + return gpg_error (GPG_ERR_WRONG_CARD); + + /* If a fingerprint has been specified check it against the one on + the card. This is allows for a meaningful error message in case + the key on the card has been replaced but the shadow information + known to gpg was not updated. If there is no fingerprint, gpg + will detect a bogus signature anyway due to the + verify-after-signing feature. */ + if (fpr) + { + for (s=fpr, n=0; hexdigitp (s); s++, n++) + ; + if (n != 40) + return gpg_error (GPG_ERR_INV_ID); + else if (!*s) + ; /* okay */ + else + return gpg_error (GPG_ERR_INV_ID); + + for (s=fpr, n=0; n < 20; s += 2, n++) + tmp_sn[n] = xtoi_2 (s); + rc = compare_fingerprint (app, 3, tmp_sn); + if (rc) + return rc; + } + + if (!app->did_chv2) + { + char *pinvalue; + + rc = pincb (pincb_arg, "Authentication/Decryption PIN", &pinvalue); + if (rc) + { + log_info ("PIN callback returned error: %s\n", gpg_strerror (rc)); + return rc; + } + + rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue)); + xfree (pinvalue); + if (rc) + { + log_error ("verify CHV2 failed\n"); + rc = gpg_error (GPG_ERR_GENERAL); + return rc; + } + app->did_chv2 = 1; + } + + rc = iso7816_internal_authenticate (app->slot, indata, indatalen, + outdata, outdatalen); + return rc; +} + + +static int +do_decipher (APP app, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + int rc; + unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */ + const char *s; + int n; + const char *fpr = NULL; + + if (!keyidstr || !*keyidstr || !indatalen) + return gpg_error (GPG_ERR_INV_VALUE); + + /* Check whether an OpenPGP card of any version has been requested. */ + if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; hexdigitp (s); s++, n++) + ; + if (n != 32) + return gpg_error (GPG_ERR_INV_ID); + else if (!*s) + ; /* no fingerprint given: we allow this for now. */ + else if (*s == '/') + fpr = s + 1; + else + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; n < 16; s += 2, n++) + tmp_sn[n] = xtoi_2 (s); + + if (app->serialnolen != 16) + return gpg_error (GPG_ERR_INV_CARD); + if (memcmp (app->serialno, tmp_sn, 16)) + return gpg_error (GPG_ERR_WRONG_CARD); + + /* If a fingerprint has been specified check it against the one on + the card. This is allows for a meaningful error message in case + the key on the card has been replaced but the shadow information + known to gpg was not updated. If there is no fingerprint, the + decryption will won't produce the right plaintext anyway. */ + if (fpr) + { + for (s=fpr, n=0; hexdigitp (s); s++, n++) + ; + if (n != 40) + return gpg_error (GPG_ERR_INV_ID); + else if (!*s) + ; /* okay */ + else + return gpg_error (GPG_ERR_INV_ID); + + for (s=fpr, n=0; n < 20; s += 2, n++) + tmp_sn[n] = xtoi_2 (s); + rc = compare_fingerprint (app, 2, tmp_sn); + if (rc) + return rc; + } + + if (!app->did_chv2) + { + char *pinvalue; + + rc = pincb (pincb_arg, "Decryption PIN", &pinvalue); +/* pinvalue = xstrdup ("123456"); */ +/* rc = 0; */ + if (rc) + { + log_info ("PIN callback returned error: %s\n", gpg_strerror (rc)); + return rc; + } + + rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue)); + xfree (pinvalue); + if (rc) + { + log_error ("verify CHV2 failed\n"); + rc = gpg_error (GPG_ERR_GENERAL); + return rc; + } + app->did_chv2 = 1; + } + + rc = iso7816_decipher (app->slot, indata, indatalen, outdata, outdatalen); + return rc; +} + + + + +/* Select the OpenPGP application on the card in SLOT. This function + must be used before any other OpenPGP application functions. */ +int +app_select_openpgp (APP app, unsigned char **sn, size_t *snlen) +{ + static char const aid[] = { 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01 }; + int slot = app->slot; + int rc; + unsigned char *buffer; + size_t buflen; + + rc = iso7816_select_application (slot, aid, sizeof aid); + if (!rc) + { + /* fixme: get the full AID and check that the version is okay + with us. */ + rc = iso7816_get_data (slot, 0x004F, &buffer, &buflen); + if (rc) + goto leave; + if (opt.verbose) + { + log_info ("got AID: "); + log_printhex ("", buffer, buflen); + } + + if (sn) + { + *sn = buffer; + *snlen = buflen; + app->card_version = buffer[6] << 8; + app->card_version |= buffer[7]; + } + else + xfree (buffer); + + if (opt.verbose > 1) + dump_all_do (slot); + + app->fnc.learn_status = do_learn_status; + app->fnc.setattr = do_setattr; + app->fnc.genkey = do_genkey; + app->fnc.sign = do_sign; + app->fnc.auth = do_auth; + app->fnc.decipher = do_decipher; + app->fnc.change_pin = do_change_pin; + } + +leave: + return rc; +} + + + +/* This function is a hack to retrieve essential information about the + card to be displayed by simple tools. It mostly resembles what the + LEARN command returns. All parameters return allocated strings or + buffers or NULL if the data object is not available. All returned + values are sanitized. */ +int +app_openpgp_cardinfo (APP app, + char **serialno, + char **disp_name, + char **pubkey_url, + unsigned char **fpr1, + unsigned char **fpr2, + unsigned char **fpr3) +{ + int rc; + void *relptr; + unsigned char *value; + size_t valuelen; + + if (serialno) + { + time_t dummy; + + *serialno = NULL; + rc = app_get_serial_and_stamp (app, serialno, &dummy); + if (rc) + { + log_error ("error getting serial number: %s\n", gpg_strerror (rc)); + return rc; + } + } + + if (disp_name) + { + *disp_name = NULL; + relptr = get_one_do (app->slot, 0x005B, &value, &valuelen); + if (relptr) + { + *disp_name = make_printable_string (value, valuelen, 0); + xfree (relptr); + } + } + + if (pubkey_url) + { + *pubkey_url = NULL; + relptr = get_one_do (app->slot, 0x5F50, &value, &valuelen); + if (relptr) + { + *pubkey_url = make_printable_string (value, valuelen, 0); + xfree (relptr); + } + } + + if (fpr1) + *fpr1 = NULL; + if (fpr2) + *fpr2 = NULL; + if (fpr3) + *fpr3 = NULL; + relptr = get_one_do (app->slot, 0x00C5, &value, &valuelen); + if (relptr && valuelen >= 60) + { + if (fpr1) + { + *fpr1 = xmalloc (20); + memcpy (*fpr1, value + 0, 20); + } + if (fpr2) + { + *fpr2 = xmalloc (20); + memcpy (*fpr2, value + 20, 20); + } + if (fpr3) + { + *fpr3 = xmalloc (20); + memcpy (*fpr3, value + 40, 20); + } + } + xfree (relptr); + + return 0; +} + + + +/* This function is currently only used by the sc-copykeys program to + store a key on the smartcard. APP ist the application handle, + KEYNO is the number of the key and PINCB, PINCB_ARG are used to ask + for the SO PIN. TEMPLATE and TEMPLATE_LEN describe a buffer with + the key template to store. CREATED_AT is the timestamp used to + create the fingerprint. M, MLEN is the RSA modulus and E, ELEN the + RSA public exponent. This function silently overwrites an existing + key.*/ +int +app_openpgp_storekey (APP app, int keyno, + unsigned char *template, size_t template_len, + time_t created_at, + const unsigned char *m, size_t mlen, + const unsigned char *e, size_t elen, + int (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + int rc; + unsigned char fprbuf[20]; + + if (keyno < 1 || keyno > 3) + return gpg_error (GPG_ERR_INV_ID); + keyno--; + + { + char *pinvalue; + rc = pincb (pincb_arg, "Admin PIN", &pinvalue); + if (rc) + { + log_error ("error getting PIN: %s\n", gpg_strerror (rc)); + return rc; + } + rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue)); + xfree (pinvalue); + } + if (rc) + { + log_error ("verify CHV3 failed: rc=%04X\n", rc); + goto leave; + } + + rc = iso7816_put_data (app->slot, + (app->card_version > 0x0007? 0xE0 : 0xE9) + keyno, + template, template_len); + if (rc) + { + log_error ("failed to store the key: rc=%04X\n", rc); + rc = gpg_error (GPG_ERR_CARD); + goto leave; + } + +/* log_printhex ("RSA n:", m, mlen); */ +/* log_printhex ("RSA e:", e, elen); */ + + rc = store_fpr (app->slot, keyno, (u32)created_at, + m, mlen, e, elen, fprbuf, app->card_version); + + leave: + return rc; +} + + +/* Utility function for external tools: Read the public RSA key at + KEYNO and return modulus and exponent in (M,MLEN) and (E,ELEN). */ +int +app_openpgp_readkey (APP app, int keyno, unsigned char **m, size_t *mlen, + unsigned char **e, size_t *elen) +{ + int rc; + const unsigned char *keydata, *a; + unsigned char *buffer; + size_t buflen, keydatalen, alen; + + *m = NULL; + *e = NULL; + + if (keyno < 1 || keyno > 3) + return gpg_error (GPG_ERR_INV_ID); + keyno--; + + rc = iso7816_read_public_key(app->slot, + keyno == 0? "\xB6" : + keyno == 1? "\xB8" : "\xA4", + 2, + &buffer, &buflen); + if (rc) + { + rc = gpg_error (GPG_ERR_CARD); + log_error ("reading key failed\n"); + goto leave; + } + + keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen, 0); + if (!keydata) + { + log_error ("response does not contain the public key data\n"); + rc = gpg_error (GPG_ERR_CARD); + goto leave; + } + + a = find_tlv (keydata, keydatalen, 0x0081, &alen, 0); + if (!a) + { + log_error ("response does not contain the RSA modulus\n"); + rc = gpg_error (GPG_ERR_CARD); + goto leave; + } + *mlen = alen; + *m = xmalloc (alen); + memcpy (*m, a, alen); + + a = find_tlv (keydata, keydatalen, 0x0082, &alen, 0); + if (!e) + { + log_error ("response does not contain the RSA public exponent\n"); + rc = gpg_error (GPG_ERR_CARD); + goto leave; + } + *elen = alen; + *e = xmalloc (alen); + memcpy (*e, a, alen); + + leave: + xfree (buffer); + if (rc) + { + xfree (*m); *m = NULL; + xfree (*e); *e = NULL; + } + return rc; +} diff --git a/scd/app.c b/scd/app.c new file mode 100644 index 000000000..7a85df336 --- /dev/null +++ b/scd/app.c @@ -0,0 +1,278 @@ +/* app.c - Application selection. + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include + +#include "scdaemon.h" +#include "app-common.h" +#include "apdu.h" +#include "iso7816.h" + +/* The select the best fitting application and return a context. + Returns NULL if no application was found or no card is present. */ +APP +select_application (void) +{ + int reader_port = 32768; /* First USB reader. */ + int slot; + int rc; + APP app; + + slot = apdu_open_reader (reader_port); + if (slot == -1) + { + log_error ("card reader not available\n"); + return NULL; + } + + app = xtrycalloc (1, sizeof *app); + if (!app) + { + rc = out_of_core (); + log_info ("error allocating context: %s\n", gpg_strerror (rc)); + /*apdu_close_reader (slot);*/ + return NULL; + } + + app->slot = slot; + rc = app_select_openpgp (app, &app->serialno, &app->serialnolen); + if (rc) + { +/* apdu_close_reader (slot); */ + log_info ("selecting openpgp failed: %s\n", gpg_strerror (rc)); + xfree (app); + return NULL; + } + + app->initialized = 1; + return app; +} + + + +/* Retrieve the serial number and the time of the last update of the + card. The serial number is returned as a malloced string (hex + encoded) in SERIAL and the time of update is returned in STAMP. If + no update time is available the returned value is 0. Caller must + free SERIAL unless the function returns an error. */ +int +app_get_serial_and_stamp (APP app, char **serial, time_t *stamp) +{ + unsigned char *buf, *p; + int i; + + if (!app || !serial || !stamp) + return gpg_error (GPG_ERR_INV_VALUE); + + *serial = NULL; + *stamp = 0; /* not available */ + + buf = xtrymalloc (app->serialnolen * 2 + 1); + if (!buf) + return gpg_error_from_errno (errno); + for (p=buf, i=0; i < app->serialnolen; p +=2, i++) + sprintf (p, "%02X", app->serialno[i]); + *p = 0; + *serial = buf; + return 0; +} + + +/* Write out the application specifig status lines for the LEARN + command. */ +int +app_write_learn_status (APP app, CTRL ctrl) +{ + if (!app) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.learn_status) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + return app->fnc.learn_status (app, ctrl); +} + + +/* Perform a SETATTR operation. */ +int +app_setattr (APP app, const char *name, + int (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *value, size_t valuelen) +{ + if (!app || !name || !*name || !value) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.setattr) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + return app->fnc.setattr (app, name, pincb, pincb_arg, value, valuelen); +} + +/* Create the signature and return the allocated result in OUTDATA. + If a PIN is required the PINCB will be used to ask for the PIN; it + should return the PIN in an allocated buffer and put it into PIN. */ +int +app_sign (APP app, const char *keyidstr, int hashalgo, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + int rc; + + if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.sign) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = app->fnc.sign (app, keyidstr, hashalgo, + pincb, pincb_arg, + indata, indatalen, + outdata, outdatalen); + if (opt.verbose) + log_info ("operation sign result: %s\n", gpg_strerror (rc)); + return rc; +} + +/* Create the signature using the INTERNAL AUTHENTICATE command and + return the allocated result in OUTDATA. If a PIN is required the + PINCB will be used to ask for the PIN; it should return the PIN in + an allocated buffer and put it into PIN. */ +int +app_auth (APP app, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + int rc; + + if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.auth) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = app->fnc.auth (app, keyidstr, + pincb, pincb_arg, + indata, indatalen, + outdata, outdatalen); + if (opt.verbose) + log_info ("operation auth result: %s\n", gpg_strerror (rc)); + return rc; +} + + +/* Decrypt the data in INDATA and return the allocated result in OUTDATA. + If a PIN is required the PINCB will be used to ask for the PIN; it + should return the PIN in an allocated buffer and put it into PIN. */ +int +app_decipher (APP app, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + int rc; + + if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.decipher) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = app->fnc.decipher (app, keyidstr, + pincb, pincb_arg, + indata, indatalen, + outdata, outdatalen); + if (opt.verbose) + log_info ("operation decipher result: %s\n", gpg_strerror (rc)); + return rc; +} + + +/* Perform a SETATTR operation. */ +int +app_genkey (APP app, CTRL ctrl, const char *keynostr, unsigned int flags, + int (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + int rc; + + if (!app || !keynostr || !*keynostr || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.genkey) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = app->fnc.genkey (app, ctrl, keynostr, flags, pincb, pincb_arg); + if (opt.verbose) + log_info ("operation genkey result: %s\n", gpg_strerror (rc)); + return rc; +} + + +/* Perform a GET CHALLENGE operation. This fucntion is special as it + directly accesses the card without any application specific + wrapper. */ +int +app_get_challenge (APP app, size_t nbytes, unsigned char *buffer) +{ + if (!app || !nbytes || !buffer) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + return iso7816_get_challenge (app->slot, nbytes, buffer); +} + + + +/* Perform a CHANGE REFERENCE DATA or RESET RETRY COUNTER operation. */ +int +app_change_pin (APP app, CTRL ctrl, const char *chvnostr, int reset_mode, + int (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + int rc; + + if (!app || !chvnostr || !*chvnostr || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.change_pin) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = app->fnc.change_pin (app, ctrl, chvnostr, reset_mode, pincb, pincb_arg); + if (opt.verbose) + log_info ("operation change_pin result: %s\n", gpg_strerror (rc)); + return rc; +} + + + + + + diff --git a/scd/card-common.h b/scd/card-common.h new file mode 100644 index 000000000..31f0dfe8f --- /dev/null +++ b/scd/card-common.h @@ -0,0 +1,73 @@ +/* card-common.h - Common declarations for all card types + * Copyright (C) 2001, 2002 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef CARD_COMMON_H +#define CARD_COMMON_H + +/* Declaration of private data structure used by card-p15.c */ +struct p15private_s; + + +struct card_ctx_s { + int reader; /* used reader */ + struct sc_context *ctx; + struct sc_card *scard; + struct sc_pkcs15_card *p15card; /* only if there is a pkcs15 application */ + struct p15private_s *p15priv; /* private data used by card-p15.c */ + + struct { + int initialized; /* the card has been initialied and the function + pointers may be used. However for + unsupported operations the particular + function pointer is set to NULL */ + + int (*enum_keypairs) (CARD card, int idx, + unsigned char *keygrip, char **keyid); + int (*enum_certs) (CARD card, int idx, char **certid, int *certtype); + int (*read_cert) (CARD card, const char *certidstr, + unsigned char **cert, size_t *ncert); + int (*sign) (CARD card, + const char *keyidstr, int hashalgo, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ); + int (*decipher) (CARD card, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen); + } fnc; + +}; + +/*-- card.c --*/ +gpg_error_t map_sc_err (int rc); +int card_help_get_keygrip (KsbaCert cert, unsigned char *array); + +/*-- card-15.c --*/ +void p15_release_private_data (CARD card); + +/* constructors */ +void card_p15_bind (CARD card); +void card_dinsig_bind (CARD card); + + +#endif /*CARD_COMMON_H*/ diff --git a/scd/card-p15.c b/scd/card-p15.c new file mode 100644 index 000000000..3cf4ba519 --- /dev/null +++ b/scd/card-p15.c @@ -0,0 +1,502 @@ +/* card-p15.c - PKCS-15 based card access + * Copyright (C) 2002 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_OPENSC +#include +#include + +#include "scdaemon.h" +#include "card-common.h" + + +struct p15private_s { + int n_prkey_rsa_objs; + struct sc_pkcs15_object *prkey_rsa_objs[32]; + int n_cert_objs; + struct sc_pkcs15_object *cert_objs[32]; +}; + + +/* Allocate private data. */ +static int +init_private_data (CARD card) +{ + struct p15private_s *priv; + int rc; + + if (card->p15priv) + return 0; /* already done. */ + + priv = xtrycalloc (1, sizeof *priv); + if (!priv) + return out_of_core (); + + /* OpenSC (0.7.0) is a bit strange in that the get_objects functions + tries to be a bit too clever and implicitly does an enumeration + which eventually leads to the fact that every call to this + fucntion returns one more macthing object. The old code in + p15_enum_keypairs assume that it would alwyas return the same + numer of objects and used this to figure out what the last object + enumerated is. We now do an enum_objects just once and keep it + in the private data. */ + rc = sc_pkcs15_get_objects (card->p15card, SC_PKCS15_TYPE_PRKEY_RSA, + priv->prkey_rsa_objs, + DIM (priv->prkey_rsa_objs)); + if (rc < 0) + { + log_error ("private keys enumeration failed: %s\n", sc_strerror (rc)); + xfree (priv); + return gpg_error (GPG_ERR_CARD); + } + priv->n_prkey_rsa_objs = rc; + + /* Read all certificate objects. */ + rc = sc_pkcs15_get_objects (card->p15card, SC_PKCS15_TYPE_CERT_X509, + priv->cert_objs, + DIM (priv->cert_objs)); + if (rc < 0) + { + log_error ("private keys enumeration failed: %s\n", sc_strerror (rc)); + xfree (priv); + return gpg_error (GPG_ERR_CARD); + } + priv->n_cert_objs = rc; + + card->p15priv = priv; + return 0; +} + + +/* Release private data used in this module. */ +void +p15_release_private_data (CARD card) +{ + if (!card->p15priv) + return; + xfree (card->p15priv); + card->p15priv = NULL; +} + + + +/* See card.c for interface description */ +static int +p15_enum_keypairs (CARD card, int idx, + unsigned char *keygrip, char **keyid) +{ + int rc; + KsbaError krc; + struct p15private_s *priv; + struct sc_pkcs15_object *tmpobj; + int nobjs; + struct sc_pkcs15_prkey_info *pinfo; + struct sc_pkcs15_cert_info *certinfo; + struct sc_pkcs15_cert *certder; + KsbaCert cert; + + rc = init_private_data (card); + if (rc) + return rc; + priv = card->p15priv; + nobjs = priv->n_prkey_rsa_objs; + rc = 0; + if (idx >= nobjs) + return -1; + pinfo = priv->prkey_rsa_objs[idx]->data; + + /* now we need to read the certificate so that we can calculate the + keygrip */ + rc = sc_pkcs15_find_cert_by_id (card->p15card, &pinfo->id, &tmpobj); + if (rc) + { + log_info ("certificate for private key %d not found: %s\n", + idx, sc_strerror (rc)); + /* note, that we return the ID anyway */ + rc = gpg_error (GPG_ERR_MISSING_CERTIFICATE); + goto return_keyid; + } + certinfo = tmpobj->data; + rc = sc_pkcs15_read_certificate (card->p15card, certinfo, &certder); + if (rc) + { + log_info ("failed to read certificate for private key %d: %s\n", + idx, sc_strerror (rc)); + return gpg_error (GPG_ERR_CARD); + } + + cert = ksba_cert_new (); + if (!cert) + { + gpg_error_t tmperr = out_of_core (); + sc_pkcs15_free_certificate (certder); + return tmperr; + } + krc = ksba_cert_init_from_mem (cert, certder->data, certder->data_len); + sc_pkcs15_free_certificate (certder); + if (krc) + { + log_error ("failed to parse the certificate for private key %d: %s\n", + idx, ksba_strerror (krc)); + ksba_cert_release (cert); + return gpg_error (GPG_ERR_CARD); + } + if (card_help_get_keygrip (cert, keygrip)) + { + log_error ("failed to calculate the keygrip of private key %d\n", idx); + ksba_cert_release (cert); + return gpg_error (GPG_ERR_CARD); + } + ksba_cert_release (cert); + + rc = 0; + return_keyid: + if (keyid) + { + char *p; + int i; + + *keyid = p = xtrymalloc (9+pinfo->id.len*2+1); + if (!*keyid) + return out_of_core (); + p = stpcpy (p, "P15-5015."); + for (i=0; i < pinfo->id.len; i++, p += 2) + sprintf (p, "%02X", pinfo->id.value[i]); + *p = 0; + } + + return rc; +} + +/* See card.c for interface description */ +static int +p15_enum_certs (CARD card, int idx, char **certid, int *type) +{ + int rc; + struct p15private_s *priv; + struct sc_pkcs15_object *obj; + struct sc_pkcs15_cert_info *cinfo; + int nobjs; + + rc = init_private_data (card); + if (rc) + return rc; + priv = card->p15priv; + nobjs = priv->n_cert_objs; + rc = 0; + if (idx >= nobjs) + return -1; + obj = priv->cert_objs[idx]; + cinfo = obj->data; + + if (certid) + { + char *p; + int i; + + *certid = p = xtrymalloc (9+cinfo->id.len*2+1); + if (!*certid) + return out_of_core (); + p = stpcpy (p, "P15-5015."); + for (i=0; i < cinfo->id.len; i++, p += 2) + sprintf (p, "%02X", cinfo->id.value[i]); + *p = 0; + } + if (type) + { + if (!obj->df) + *type = 0; /* unknown */ + else if (obj->df->type == SC_PKCS15_CDF) + *type = 100; + else if (obj->df->type == SC_PKCS15_CDF_TRUSTED) + *type = 101; + else if (obj->df->type == SC_PKCS15_CDF_USEFUL) + *type = 102; + else + *type = 0; /* error -> unknown */ + } + + return rc; +} + + + +static int +idstr_to_id (const char *idstr, struct sc_pkcs15_id *id) +{ + const char *s; + int n; + + /* For now we only support the standard DF */ + if (strncmp (idstr, "P15-5015.", 9) ) + return gpg_error (GPG_ERR_INV_ID); + for (s=idstr+9, n=0; hexdigitp (s); s++, n++) + ; + if (*s || (n&1)) + return gpg_error (GPG_ERR_INV_ID); /*invalid or odd number of digits*/ + n /= 2; + if (!n || n > SC_PKCS15_MAX_ID_SIZE) + return gpg_error (GPG_ERR_INV_ID); /* empty or too large */ + for (s=idstr+9, n=0; *s; s += 2, n++) + id->value[n] = xtoi_2 (s); + id->len = n; + return 0; +} + + +/* See card.c for interface description */ +static int +p15_read_cert (CARD card, const char *certidstr, + unsigned char **cert, size_t *ncert) +{ + struct sc_pkcs15_object *tmpobj; + struct sc_pkcs15_id certid; + struct sc_pkcs15_cert_info *certinfo; + struct sc_pkcs15_cert *certder; + int rc; + + if (!card || !certidstr || !cert || !ncert) + return gpg_error (GPG_ERR_INV_VALUE); + if (!card->p15card) + return gpg_error (GPG_ERR_NO_PKCS15_APP); + + rc = idstr_to_id (certidstr, &certid); + if (rc) + return rc; + + rc = sc_pkcs15_find_cert_by_id (card->p15card, &certid, &tmpobj); + if (rc) + { + log_info ("certificate '%s' not found: %s\n", + certidstr, sc_strerror (rc)); + return -1; + } + certinfo = tmpobj->data; + rc = sc_pkcs15_read_certificate (card->p15card, certinfo, &certder); + if (rc) + { + log_info ("failed to read certificate '%s': %s\n", + certidstr, sc_strerror (rc)); + return gpg_error (GPG_ERR_CARD); + } + + *cert = xtrymalloc (certder->data_len); + if (!*cert) + { + gpg_error_t tmperr = out_of_core (); + sc_pkcs15_free_certificate (certder); + return tmperr; + } + memcpy (*cert, certder->data, certder->data_len); + *ncert = certder->data_len; + sc_pkcs15_free_certificate (certder); + return 0; +} + + + + + +static int +p15_prepare_key (CARD card, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, struct sc_pkcs15_object **r_keyobj) +{ + struct sc_pkcs15_id keyid; + struct sc_pkcs15_pin_info *pin; + struct sc_pkcs15_object *keyobj, *pinobj; + char *pinvalue; + int rc; + + rc = idstr_to_id (keyidstr, &keyid); + if (rc) + return rc; + + rc = sc_pkcs15_find_prkey_by_id (card->p15card, &keyid, &keyobj); + if (rc < 0) + { + log_error ("private key not found: %s\n", sc_strerror(rc)); + return gpg_error (GPG_ERR_NO_SECRET_KEY); + } + + rc = sc_pkcs15_find_pin_by_auth_id (card->p15card, + &keyobj->auth_id, &pinobj); + if (rc) + { + log_error ("failed to find PIN by auth ID: %s\n", sc_strerror (rc)); + return gpg_error (GPG_ERR_BAD_PIN_METHOD); + } + pin = pinobj->data; + + /* Fixme: pack this into a verification loop */ + /* Fixme: we might want to pass pin->min_length and + pin->stored_length */ + rc = pincb (pincb_arg, pinobj->label, &pinvalue); + if (rc) + { + log_info ("PIN callback returned error: %s\n", gnupg_strerror (rc)); + return rc; + } + + rc = sc_pkcs15_verify_pin (card->p15card, pin, + pinvalue, strlen (pinvalue)); + xfree (pinvalue); + if (rc) + { + log_info ("PIN verification failed: %s\n", sc_strerror (rc)); + return gpg_error (GPG_ERR_BAD_PIN); + } + + /* fixme: check wheter we need to release KEYOBJ in case of an error */ + *r_keyobj = keyobj; + return 0; +} + + +/* See card.c for interface description */ +static int +p15_sign (CARD card, const char *keyidstr, int hashalgo, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + unsigned int cryptflags; + struct sc_pkcs15_object *keyobj; + int rc; + unsigned char *outbuf = NULL; + size_t outbuflen; + + if (hashalgo != GCRY_MD_SHA1) + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + + rc = p15_prepare_key (card, keyidstr, pincb, pincb_arg, &keyobj); + if (rc) + return rc; + + cryptflags = SC_ALGORITHM_RSA_PAD_PKCS1; + + outbuflen = 1024; + outbuf = xtrymalloc (outbuflen); + if (!outbuf) + return out_of_core (); + + rc = sc_pkcs15_compute_signature (card->p15card, keyobj, + cryptflags, + indata, indatalen, + outbuf, outbuflen ); + if (rc < 0) + { + log_error ("failed to create signature: %s\n", sc_strerror (rc)); + rc = gpg_error (GPG_ERR_CARD); + } + else + { + *outdatalen = rc; + *outdata = outbuf; + outbuf = NULL; + rc = 0; + } + + xfree (outbuf); + return rc; +} + + +/* See card.c for description */ +static int +p15_decipher (CARD card, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + struct sc_pkcs15_object *keyobj; + int rc; + unsigned char *outbuf = NULL; + size_t outbuflen; + + rc = p15_prepare_key (card, keyidstr, pincb, pincb_arg, &keyobj); + if (rc) + return rc; + + if (card && card->scard && card->scard->driver + && !strcasecmp (card->scard->driver->short_name, "tcos")) + { + /* very ugly hack to force the use of a local key. We need this + until we have fixed the initialization code for TCOS cards */ + struct sc_pkcs15_prkey_info *prkey = keyobj->data; + if ( !(prkey->key_reference & 0x80)) + { + prkey->key_reference |= 0x80; + log_debug ("using TCOS hack to force the use of local keys\n"); + } + if (*keyidstr && keyidstr[strlen(keyidstr)-1] == '6') + { + prkey->key_reference |= 1; + log_debug ("warning: using even more TCOS hacks\n"); + } + } + + outbuflen = indatalen < 256? 256 : indatalen; + outbuf = xtrymalloc (outbuflen); + if (!outbuf) + return out_of_core (); + + rc = sc_pkcs15_decipher (card->p15card, keyobj, + 0, + indata, indatalen, + outbuf, outbuflen); + if (rc < 0) + { + log_error ("failed to decipher the data: %s\n", sc_strerror (rc)); + rc = gpg_error (GPG_ERR_CARD); + } + else + { + *outdatalen = rc; + *outdata = outbuf; + outbuf = NULL; + rc = 0; + } + + xfree (outbuf); + return rc; +} + + + +/* Bind our operations to the card */ +void +card_p15_bind (CARD card) +{ + card->fnc.enum_keypairs = p15_enum_keypairs; + card->fnc.enum_certs = p15_enum_certs; + card->fnc.read_cert = p15_read_cert; + card->fnc.sign = p15_sign; + card->fnc.decipher = p15_decipher; +} +#endif /*HAVE_OPENSC*/ diff --git a/scd/card.c b/scd/card.c new file mode 100644 index 000000000..02b7bfdbf --- /dev/null +++ b/scd/card.c @@ -0,0 +1,564 @@ +/* card.c - SCdaemon card functions + * Copyright (C) 2002 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_OPENSC +#include +#endif +#include + +#include "scdaemon.h" +#include "card-common.h" + +/* Map the SC error codes to the GNUPG ones */ +gpg_error_t +map_sc_err (int rc) +{ + gpg_err_code_t e; + + switch (rc) + { + case 0: e = 0; break; +#ifdef HAVE_OPENSC + case SC_ERROR_NOT_SUPPORTED: e = GPG_ERR_NOT_SUPPORTED; break; + case SC_ERROR_PKCS15_APP_NOT_FOUND: e = GPG_ERR_NO_PKCS15_APP; break; + case SC_ERROR_OUT_OF_MEMORY: e = GPG_ERR_ENOMEM; break; + case SC_ERROR_CARD_NOT_PRESENT: e = GPG_ERR_CARD_NOT_PRESENT; break; + case SC_ERROR_CARD_REMOVED: e = GPG_ERR_CARD_REMOVED; break; + case SC_ERROR_INVALID_CARD: e = GPG_ERR_INV_CARD; break; +#endif + default: e = GPG_ERR_CARD; break; + } + return gpg_err_make (GPG_ERR_SOURCE_UNKNOWN, e); +} + +/* Get the keygrip from CERT, return 0 on success */ +int +card_help_get_keygrip (KsbaCert cert, unsigned char *array) +{ + gcry_sexp_t s_pkey; + int rc; + KsbaSexp p; + size_t n; + + p = ksba_cert_get_public_key (cert); + if (!p) + return -1; /* oops */ + n = gcry_sexp_canon_len (p, 0, NULL, NULL); + if (!n) + return -1; /* libksba did not return a proper S-expression */ + rc = gcry_sexp_sscan ( &s_pkey, NULL, p, n); + xfree (p); + if (rc) + return -1; /* can't parse that S-expression */ + array = gcry_pk_get_keygrip (s_pkey, array); + gcry_sexp_release (s_pkey); + if (!array) + return -1; /* failed to calculate the keygrip */ + return 0; +} + + + + + + + +/* Create a new context for the card and figures out some basic + information of the card. Detects whgether a PKCS_15 application is + stored. + + Common errors: GPG_ERR_CARD_NOT_PRESENT */ +int +card_open (CARD *rcard) +{ +#ifdef HAVE_OPENSC + CARD card; + int rc; + + card = xtrycalloc (1, sizeof *card); + if (!card) + return out_of_core (); + card->reader = 0; + + rc = sc_establish_context (&card->ctx, "scdaemon"); + if (rc) + { + log_error ("failed to establish SC context: %s\n", sc_strerror (rc)); + rc = map_sc_err (rc); + goto leave; + } + if (card->reader >= card->ctx->reader_count) + { + log_error ("no card reader available\n"); + rc = gpg_error (GPG_ERR_CARD); + goto leave; + } + card->ctx->error_file = log_get_stream (); + card->ctx->debug = opt.debug_sc; + card->ctx->debug_file = log_get_stream (); + + if (sc_detect_card_presence (card->ctx->reader[card->reader], 0) != 1) + { + rc = gpg_error (GPG_ERR_CARD_NOT_PRESENT); + goto leave; + } + + rc = sc_connect_card (card->ctx->reader[card->reader], 0, &card->scard); + if (rc) + { + log_error ("failed to connect card in reader %d: %s\n", + card->reader, sc_strerror (rc)); + rc = map_sc_err (rc); + goto leave; + } + if (opt.verbose) + log_info ("connected to card in reader %d using driver `%s'\n", + card->reader, card->scard->driver->name); + + rc = sc_lock (card->scard); + if (rc) + { + log_error ("can't lock card in reader %d: %s\n", + card->reader, sc_strerror (rc)); + rc = map_sc_err (rc); + goto leave; + } + + + leave: + if (rc) + card_close (card); + else + *rcard = card; + + return rc; +#else + return gpg_error (GPG_ERR_NOT_SUPPORTED); +#endif +} + + +/* Close a card and release all resources */ +void +card_close (CARD card) +{ + if (card) + { +#ifdef HAVE_OPENSC + if (card->p15card) + { + sc_pkcs15_unbind (card->p15card); + card->p15card = NULL; + } + if (card->p15priv) + p15_release_private_data (card); + if (card->scard) + { + sc_unlock (card->scard); + sc_disconnect_card (card->scard, 0); + card->scard = NULL; + } + if (card->ctx) + { + sc_release_context (card->ctx); + card->ctx = NULL; + } +#endif + xfree (card); + } +} + +/* Locate a simple TLV encoded data object in BUFFER of LENGTH and + return a pointer to value as well as its length in NBYTES. Return + NULL if it was not found. Note, that the function does not check + whether the value fits into the provided buffer. */ +#ifdef HAVE_OPENSC +static const char * +find_simple_tlv (const unsigned char *buffer, size_t length, + int tag, size_t *nbytes) +{ + const char *s = buffer; + size_t n = length; + size_t len; + + for (;;) + { + buffer = s; + if (n < 2) + return NULL; /* buffer too short for tag and length. */ + len = s[1]; + s += 2; n -= 2; + if (len == 255) + { + if (n < 2) + return NULL; /* we expected 2 more bytes with the length. */ + len = (s[0] << 8) | s[1]; + s += 2; n -= 2; + } + if (*buffer == tag) + { + *nbytes = len; + return s; + } + if (len > n) + return NULL; /* buffer too short to skip to the next tag. */ + s += len; n -= len; + } +} +#endif /*HAVE_OPENSC*/ + +/* Find the ICC Serial Number within the provided BUFFER of LENGTH + (which should contain the GDO file) and return it as a hex encoded + string and allocated string in SERIAL. Return an error code when + the ICCSN was not found. */ +#ifdef HAVE_OPENSC +static int +find_iccsn (const unsigned char *buffer, size_t length, char **serial) +{ + size_t n; + const unsigned char *s; + char *p; + + s = find_simple_tlv (buffer, length, 0x5A, &n); + if (!s) + return gpg_error (GPG_ERR_CARD); + length -= s - buffer; + if (n > length) + { + /* Oops, it does not fit into the buffer. This is an invalid + encoding (or the buffer is too short. However, I have some + test cards with such an invalid encoding and therefore I use + this ugly workaround to return something I can further + experiment with. */ + if (n == 0x0D && length+1 == n) + { + log_debug ("enabling BMI testcard workaround\n"); + n--; + } + else + return gpg_error (GPG_ERR_CARD); /* Bad encoding; does + not fit into buffer. */ + } + if (!n) + return gpg_error (GPG_ERR_CARD); /* Well, that is too short. */ + + *serial = p = xtrymalloc (2*n+1); + if (!*serial) + return out_of_core (); + for (; n; n--, p += 2, s++) + sprintf (p, "%02X", *s); + *p = 0; + return 0; +} +#endif /*HAVE_OPENSC*/ + +/* Retrieve the serial number and the time of the last update of the + card. The serial number is returned as a malloced string (hex + encoded) in SERIAL and the time of update is returned in STAMP. + If no update time is available the returned value is 0. The serial + is mandatory for a PKCS_15 application and an error will be + returned if this value is not availbale. For non-PKCS-15 cards a + serial number is constructed by other means. Caller must free + SERIAL unless the function returns an error. */ +int +card_get_serial_and_stamp (CARD card, char **serial, time_t *stamp) +{ +#ifdef HAVE_OPENSC + int rc; + struct sc_path path; + struct sc_file *file; + unsigned char buf[256]; + int buflen; +#endif + + if (!card || !serial || !stamp) + return gpg_error (GPG_ERR_INV_VALUE); + + *serial = NULL; + *stamp = 0; /* not available */ + +#ifdef HAVE_OPENSC + if (!card->fnc.initialized) + { + card->fnc.initialized = 1; + /* The first use of this card tries to figure out the type of the card + and sets up the function pointers. */ + rc = sc_pkcs15_bind (card->scard, &card->p15card); + if (rc) + { + if (rc != SC_ERROR_PKCS15_APP_NOT_FOUND) + log_error ("binding of existing PKCS-15 failed in reader %d: %s\n", + card->reader, sc_strerror (rc)); + card->p15card = NULL; + rc = 0; + } + if (card->p15card) + card_p15_bind (card); + else + card_dinsig_bind (card); + card->fnc.initialized = 1; + } + + + /* We should lookup the iso 7812-1 and 8583-3 - argh ISO + practice is suppressing innovation - IETF rules! So we + always get the serialnumber from the 2F02 GDO file. */ + /* FIXME: in case we can't parse the 2F02 EF and we have a P15 card, + we should get the serial number from the respective P15 file */ + sc_format_path ("3F002F02", &path); + rc = sc_select_file (card->scard, &path, &file); + if (rc) + { + log_error ("sc_select_file failed: %s\n", sc_strerror (rc)); + return gpg_error (GPG_ERR_CARD); + } + if (file->type != SC_FILE_TYPE_WORKING_EF + || file->ef_structure != SC_FILE_EF_TRANSPARENT) + { + log_error ("wrong type or structure of GDO file\n"); + sc_file_free (file); + return gpg_error (GPG_ERR_CARD); + } + + if (!file->size || file->size >= DIM(buf) ) + { /* FIXME: Use a real parser */ + log_error ("unsupported size of GDO file (%d)\n", file->size); + sc_file_free (file); + return gpg_error (GPG_ERR_CARD); + } + buflen = file->size; + + rc = sc_read_binary (card->scard, 0, buf, buflen, 0); + sc_file_free (file); + if (rc < 0) + { + log_error ("error reading GDO file: %s\n", sc_strerror (rc)); + return gpg_error (GPG_ERR_CARD); + } + if (rc != buflen) + { + log_error ("short read on GDO file\n"); + return gpg_error (GPG_ERR_CARD); + } + + rc = find_iccsn (buf, buflen, serial); + if (gpg_err_code (rc) == GPG_ERR_CARD) + log_error ("invalid structure of GDO file\n"); + if (!rc && card->p15card && !strcmp (*serial, "D27600000000000000000000")) + { /* This is a German card with a silly serial number. Try to get + the serial number from the EF(TokenInfo). We indicate such a + serial number by the using the prefix: "FF0100". */ + const char *efser = card->p15card->serial_number; + char *p; + + if (!efser) + efser = ""; + + xfree (*serial); + *serial = NULL; + p = xtrymalloc (strlen (efser) + 7); + if (!p) + rc = out_of_core (); + else + { + strcpy (p, "FF0100"); + strcpy (p+6, efser); + *serial = p; + } + } + else if (!rc && **serial == 'F' && (*serial)[1] == 'F') + { /* The serial number starts with our special prefix. This + requires that we put our default prefix "FF0000" in front. */ + char *p = xtrymalloc (strlen (*serial) + 7); + if (!p) + { + xfree (*serial); + *serial = NULL; + rc = out_of_core (); + } + else + { + strcpy (p, "FF0000"); + strcpy (p+6, *serial); + xfree (*serial); + *serial = p; + } + } + return rc; +#else + return gpg_error (GPG_ERR_NOT_SUPPORTED); +#endif +} + + +/* Enumerate all keypairs on the card and return the Keygrip as well + as the internal identification of the key. KEYGRIP must be a + caller provided buffer with a size of 20 bytes which will receive + the KEYGRIP of the keypair. If KEYID is not NULL, it returns the + ID field of the key in allocated memory; this is a string without + spaces. The function returns -1 when all keys have been + enumerated. Note that the error GPG_ERR_MISSING_CERTIFICATE may be + returned if there is just the private key but no public key (ie.e a + certificate) available. Applications might want to continue + enumerating after this error.*/ +int +card_enum_keypairs (CARD card, int idx, + unsigned char *keygrip, + char **keyid) +{ + int rc; + + if (keyid) + *keyid = NULL; + + if (!card || !keygrip) + return gpg_error (GPG_ERR_INV_VALUE); + if (idx < 0) + return gpg_error (GPG_ERR_INV_INDEX); + if (!card->fnc.initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!card->fnc.enum_keypairs) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = card->fnc.enum_keypairs (card, idx, keygrip, keyid); + if (opt.verbose) + log_info ("card operation enum_keypairs result: %s\n", + gpg_strerror (rc)); + return rc; +} + + +/* Enumerate all trusted certificates available on the card, return + their ID in CERT and the type in CERTTYPE. Types of certificates + are: + 0 := Unknown + 100 := Regular X.509 cert + 101 := Trusted X.509 cert + 102 := Useful X.509 cert + */ +int +card_enum_certs (CARD card, int idx, char **certid, int *certtype) +{ + int rc; + + if (certid) + *certid = NULL; + + if (!card) + return gpg_error (GPG_ERR_INV_VALUE); + if (idx < 0) + return gpg_error (GPG_ERR_INV_INDEX); + if (!card->fnc.initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!card->fnc.enum_certs) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = card->fnc.enum_certs (card, idx, certid, certtype); + if (opt.verbose) + log_info ("card operation enum_certs result: %s\n", + gpg_strerror (rc)); + return rc; +} + + + +/* Read the certificate identified by CERTIDSTR which is the + hexadecimal encoded ID of the certificate, prefixed with the string + "3F005015.". The certificate is return in DER encoded form in CERT + and NCERT. */ +int +card_read_cert (CARD card, const char *certidstr, + unsigned char **cert, size_t *ncert) +{ + int rc; + + if (!card || !certidstr || !cert || !ncert) + return gpg_error (GPG_ERR_INV_VALUE); + if (!card->fnc.initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!card->fnc.read_cert) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = card->fnc.read_cert (card, certidstr, cert, ncert); + if (opt.verbose) + log_info ("card operation read_cert result: %s\n", gpg_strerror (rc)); + return rc; +} + + +/* Create the signature and return the allocated result in OUTDATA. + If a PIN is required the PINCB will be used to ask for the PIN; it + should return the PIN in an allocated buffer and put it into PIN. */ +int +card_sign (CARD card, const char *keyidstr, int hashalgo, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + int rc; + + if (!card || !indata || !indatalen || !outdata || !outdatalen || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!card->fnc.initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!card->fnc.sign) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = card->fnc.sign (card, keyidstr, hashalgo, + pincb, pincb_arg, + indata, indatalen, + outdata, outdatalen); + if (opt.verbose) + log_info ("card operation sign result: %s\n", gpg_strerror (rc)); + return rc; +} + + +/* Create the signature and return the allocated result in OUTDATA. + If a PIN is required the PINCB will be used to ask for the PIN; it + should return the PIN in an allocated buffer and put it into PIN. */ +int +card_decipher (CARD card, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + int rc; + + if (!card || !indata || !indatalen || !outdata || !outdatalen || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!card->fnc.initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!card->fnc.decipher) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = card->fnc.decipher (card, keyidstr, + pincb, pincb_arg, + indata, indatalen, + outdata, outdatalen); + if (opt.verbose) + log_info ("card operation decipher result: %s\n", gpg_strerror (rc)); + return rc; +} + diff --git a/scd/command.c b/scd/command.c new file mode 100644 index 000000000..c53af84f9 --- /dev/null +++ b/scd/command.c @@ -0,0 +1,1034 @@ +/* command.c - SCdaemon command handler + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "scdaemon.h" +#include "app-common.h" + +/* maximum length aloowed as a PIN; used for INQUIRE NEEDPIN */ +#define MAXLEN_PIN 100 + +#define set_error(e,t) assuan_set_error (ctx, ASSUAN_ ## e, (t)) + +/* Data used to associate an Assuan context with local server data */ +struct server_local_s { + ASSUAN_CONTEXT assuan_ctx; +}; + + +/* Check whether the option NAME appears in LINE */ +static int +has_option (const char *line, const char *name) +{ + const char *s; + int n = strlen (name); + + s = strstr (line, name); + return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n))); +} + + + + +/* Note, that this reset_notify is also used for cleanup purposes. */ +static void +reset_notify (ASSUAN_CONTEXT ctx) +{ + CTRL ctrl = assuan_get_pointer (ctx); + + if (ctrl->card_ctx) + { + card_close (ctrl->card_ctx); + ctrl->card_ctx = NULL; + xfree (ctrl->in_data.value); + ctrl->in_data.value = NULL; + } + if (ctrl->app_ctx) + { + /* FIXME: close the application. */ + xfree (ctrl->app_ctx); + ctrl->app_ctx = NULL; + } +} + + +static int +option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value) +{ + return 0; +} + + +/* If the card has not yet been opened, do it. Note that this + function returns an Assuan error, so don't map the error a second + time */ +static AssuanError +open_card (CTRL ctrl) +{ + if (ctrl->app_ctx) + return 0; /* Already initialized for one specific application. */ + if (ctrl->card_ctx) + return 0; /* Already initialized using a card context. */ + + ctrl->app_ctx = select_application (); + if (!ctrl->app_ctx) + { /* No application found - fall back to old mode. */ + int rc = card_open (&ctrl->card_ctx); + if (rc) + return map_to_assuan_status (rc); + } + return 0; +} + + +/* Do the percent and plus/space unescaping in place and return tghe + length of the valid buffer. */ +static size_t +percent_plus_unescape (unsigned char *string) +{ + unsigned char *p = string; + size_t n = 0; + + while (*string) + { + if (*string == '%' && string[1] && string[2]) + { + string++; + *p++ = xtoi_2 (string); + n++; + string+= 2; + } + else if (*string == '+') + { + *p++ = ' '; + n++; + string++; + } + else + { + *p++ = *string++; + n++; + } + } + + return n; +} + + + +/* SERIALNO + + Return the serial number of the card using a status reponse. This + functon should be used to check for the presence of a card. + + This function is special in that it can be used to reset the card. + Most other functions will return an error when a card change has + been detected and the use of this function is therefore required. + + Background: We want to keep the client clear of handling card + changes between operations; i.e. the client can assume that all + operations are done on the same card unless he calls this function. + */ +static int +cmd_serialno (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc = 0; + char *serial_and_stamp; + char *serial; + time_t stamp; + + if ((rc = open_card (ctrl))) + return rc; + + if (ctrl->app_ctx) + rc = app_get_serial_and_stamp (ctrl->app_ctx, &serial, &stamp); + else + rc = card_get_serial_and_stamp (ctrl->card_ctx, &serial, &stamp); + if (rc) + return map_to_assuan_status (rc); + rc = asprintf (&serial_and_stamp, "%s %lu", serial, (unsigned long)stamp); + xfree (serial); + if (rc < 0) + return ASSUAN_Out_Of_Core; + rc = 0; + assuan_write_status (ctx, "SERIALNO", serial_and_stamp); + free (serial_and_stamp); + return 0; +} + + + + +/* LEARN [--force] + + Learn all useful information of the currently inserted card. When + used without the force options, the command might do an INQUIRE + like this: + + INQUIRE KNOWNCARDP + + The client should just send an "END" if the processing should go on + or a "CANCEL" to force the function to terminate with a Cancel + error message. The response of this command is a list of status + lines formatted as this: + + S APPTYPE + + This returns the type of the application, currently the strings: + + P15 = PKCS-15 structure used + DINSIG = DIN SIG + OPENPGP = OpenPGP card + + are implemented. These strings are aliases for the AID + + S KEYPAIRINFO + + If there is no certificate yet stored on the card a single "X" is + returned as the keygrip. In addition to the keypair info, information + about all certificates stored on the card is also returned: + + S CERTINFO + + Where CERTTYPE is a number indicating the type of certificate: + 0 := Unknown + 100 := Regular X.509 cert + 101 := Trusted X.509 cert + 102 := Useful X.509 cert + + For certain cards, more information will be returned: + + S KEY-FPR + + For OpenPGP cards this returns the stored fingerprints of the + keys. This can be used check whether a key is available on the + card. NO may be 1, 2 or 3. + + S CA-FPR + + Similar to above, these are the fingerprints of keys assumed to be + ultimately trusted. + + S DISP-NAME + + The name of the card holder as stored on the card; percent + aescaping takes place, spaces are encoded as '+' + + S PUBKEY-URL + + The URL to be used for locating the entire public key. + +*/ +static int +cmd_learn (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc = 0; + int idx; + + if ((rc = open_card (ctrl))) + return rc; + + /* Unless the force option is used we try a shortcut by identifying + the card using a serial number and inquiring the client with + that. The client may choose to cancel the operation if he already + knows about this card */ + { + char *serial_and_stamp; + char *serial; + time_t stamp; + + if (ctrl->app_ctx) + rc = app_get_serial_and_stamp (ctrl->app_ctx, &serial, &stamp); + else + rc = card_get_serial_and_stamp (ctrl->card_ctx, &serial, &stamp); + if (rc) + return map_to_assuan_status (rc); + rc = asprintf (&serial_and_stamp, "%s %lu", serial, (unsigned long)stamp); + xfree (serial); + if (rc < 0) + return ASSUAN_Out_Of_Core; + rc = 0; + assuan_write_status (ctx, "SERIALNO", serial_and_stamp); + + if (!has_option (line, "--force")) + { + char *command; + + rc = asprintf (&command, "KNOWNCARDP %s", serial_and_stamp); + if (rc < 0) + { + free (serial_and_stamp); + return ASSUAN_Out_Of_Core; + } + rc = 0; + rc = assuan_inquire (ctx, command, NULL, NULL, 0); + free (command); /* (must use standard free here) */ + if (rc) + { + if (rc != ASSUAN_Canceled) + log_error ("inquire KNOWNCARDP failed: %s\n", + assuan_strerror (rc)); + free (serial_and_stamp); + return rc; + } + /* not canceled, so we have to proceeed */ + } + free (serial_and_stamp); + } + + /* Return information about the certificates. */ + if (ctrl->app_ctx) + rc = -1; /* This information is not yet available for applications. */ + for (idx=0; !rc; idx++) + { + char *certid; + int certtype; + + rc = card_enum_certs (ctrl->card_ctx, idx, &certid, &certtype); + if (!rc) + { + char *buf; + + buf = xtrymalloc (40 + 1 + strlen (certid) + 1); + if (!buf) + rc = out_of_core (); + else + { + sprintf (buf, "%d %s", certtype, certid); + assuan_write_status (ctx, "CERTINFO", buf); + xfree (buf); + } + } + xfree (certid); + } + if (rc == -1) + rc = 0; + + + /* Return information about the keys. */ + if (ctrl->app_ctx) + rc = -1; /* This information is not yet available for applications. */ + for (idx=0; !rc; idx++) + { + unsigned char keygrip[20]; + char *keyid; + int no_cert = 0; + + rc = card_enum_keypairs (ctrl->card_ctx, idx, keygrip, &keyid); + if (gpg_err_code (rc) == GPG_ERR_MISSING_CERT && keyid) + { + /* this does happen with an incomplete personalized + card; i.e. during the time we have stored the key on the + card but not stored the certificate; probably becuase it + has not yet been received back from the CA. Note that we + must release KEYID in this case. */ + rc = 0; + no_cert = 1; + } + if (!rc) + { + char *buf, *p; + + buf = p = xtrymalloc (40 + 1 + strlen (keyid) + 1); + if (!buf) + rc = out_of_core (); + else + { + int i; + + if (no_cert) + *p++ = 'X'; + else + { + for (i=0; i < 20; i++, p += 2) + sprintf (p, "%02X", keygrip[i]); + } + *p++ = ' '; + strcpy (p, keyid); + assuan_write_status (ctx, "KEYPAIRINFO", buf); + xfree (buf); + } + } + xfree (keyid); + } + if (rc == -1) + rc = 0; + + if (!rc && ctrl->app_ctx) + rc = app_write_learn_status (ctrl->app_ctx, ctrl); + + + return map_to_assuan_status (rc); +} + + + +/* READCERT + + */ +static int +cmd_readcert (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + unsigned char *cert; + size_t ncert; + + if ((rc = open_card (ctrl))) + return rc; + + if (ctrl->app_ctx) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + rc = card_read_cert (ctrl->card_ctx, line, &cert, &ncert); + if (rc) + { + log_error ("card_read_cert failed: %s\n", gpg_strerror (rc)); + } + if (!rc) + { + rc = assuan_send_data (ctx, cert, ncert); + xfree (cert); + if (rc) + return rc; + } + + return map_to_assuan_status (rc); +} + + +/* READKEY + + Return the public key for the given cert or key ID as an standard + S-Expression. */ +static int +cmd_readkey (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + unsigned char *cert = NULL; + size_t ncert, n; + KsbaCert kc = NULL; + KsbaSexp p; + + if ((rc = open_card (ctrl))) + return rc; + + if (ctrl->app_ctx) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + rc = card_read_cert (ctrl->card_ctx, line, &cert, &ncert); + if (rc) + { + log_error ("card_read_cert failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + kc = ksba_cert_new (); + if (!kc) + { + rc = out_of_core (); + xfree (cert); + goto leave; + } + rc = ksba_cert_init_from_mem (kc, cert, ncert); + if (rc) + { + log_error ("failed to parse the certificate: %s\n", ksba_strerror (rc)); + rc = map_ksba_err (rc); + goto leave; + } + + p = ksba_cert_get_public_key (kc); + if (!p) + { + rc = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + + n = gcry_sexp_canon_len (p, 0, NULL, NULL); + rc = assuan_send_data (ctx, p, n); + rc = map_assuan_err (rc); + xfree (p); + + + leave: + ksba_cert_release (kc); + xfree (cert); + return map_to_assuan_status (rc); +} + + + + +/* SETDATA + + The client should use this command to tell us the data he want to + sign. */ +static int +cmd_setdata (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int n; + char *p; + unsigned char *buf; + + /* parse the hexstring */ + for (p=line,n=0; hexdigitp (p); p++, n++) + ; + if (*p) + return set_error (Parameter_Error, "invalid hexstring"); + if (!n) + return set_error (Parameter_Error, "no data given"); + if ((n&1)) + return set_error (Parameter_Error, "odd number of digits"); + n /= 2; + buf = xtrymalloc (n); + if (!buf) + return ASSUAN_Out_Of_Core; + + ctrl->in_data.value = buf; + ctrl->in_data.valuelen = n; + for (p=line, n=0; n < ctrl->in_data.valuelen; p += 2, n++) + buf[n] = xtoi_2 (p); + return 0; +} + + + +static int +pin_cb (void *opaque, const char *info, char **retstr) +{ + ASSUAN_CONTEXT ctx = opaque; + char *command; + int rc; + char *value; + size_t valuelen; + + *retstr = NULL; + log_debug ("asking for PIN '%s'\n", info); + + rc = asprintf (&command, "NEEDPIN %s", info); + if (rc < 0) + return out_of_core (); + + /* FIXME: Write an inquire function which returns the result in + secure memory */ + rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN); + free (command); + if (rc) + return map_assuan_err (rc); + + if (!valuelen || value[valuelen-1]) + { + /* We require that the returned value is an UTF-8 string */ + xfree (value); + return gpg_error (GPG_ERR_INV_RESPONSE); + } + *retstr = value; + return 0; +} + + +/* PKSIGN + + */ +static int +cmd_pksign (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + unsigned char *outdata; + size_t outdatalen; + char *keyidstr; + + if ((rc = open_card (ctrl))) + return rc; + + /* We have to use a copy of the key ID because the function may use + the pin_cb which in turn uses the assuan line buffer and thus + overwriting the original line with the keyid */ + keyidstr = strdup (line); + if (!keyidstr) + return ASSUAN_Out_Of_Core; + + if (ctrl->app_ctx) + rc = app_sign (ctrl->app_ctx, + keyidstr, GCRY_MD_SHA1, + pin_cb, ctx, + ctrl->in_data.value, ctrl->in_data.valuelen, + &outdata, &outdatalen); + else + rc = card_sign (ctrl->card_ctx, + keyidstr, GCRY_MD_SHA1, + pin_cb, ctx, + ctrl->in_data.value, ctrl->in_data.valuelen, + &outdata, &outdatalen); + free (keyidstr); + if (rc) + { + log_error ("card_sign failed: %s\n", gpg_strerror (rc)); + } + else + { + rc = assuan_send_data (ctx, outdata, outdatalen); + xfree (outdata); + if (rc) + return rc; /* that is already an assuan error code */ + } + + return map_to_assuan_status (rc); +} + +/* PKAUTH + + */ +static int +cmd_pkauth (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + unsigned char *outdata; + size_t outdatalen; + char *keyidstr; + + if ((rc = open_card (ctrl))) + return rc; + + if (!ctrl->app_ctx) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + /* We have to use a copy of the key ID because the function may use + the pin_cb which in turn uses the assuan line buffer and thus + overwriting the original line with the keyid */ + keyidstr = strdup (line); + if (!keyidstr) + return ASSUAN_Out_Of_Core; + + rc = app_auth (ctrl->app_ctx, + keyidstr, + pin_cb, ctx, + ctrl->in_data.value, ctrl->in_data.valuelen, + &outdata, &outdatalen); + free (keyidstr); + if (rc) + { + log_error ("app_auth_sign failed: %s\n", gpg_strerror (rc)); + } + else + { + rc = assuan_send_data (ctx, outdata, outdatalen); + xfree (outdata); + if (rc) + return rc; /* that is already an assuan error code */ + } + + return map_to_assuan_status (rc); +} + +/* PKDECRYPT + + */ +static int +cmd_pkdecrypt (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + unsigned char *outdata; + size_t outdatalen; + char *keyidstr; + + if ((rc = open_card (ctrl))) + return rc; + + keyidstr = strdup (line); + if (!keyidstr) + return ASSUAN_Out_Of_Core; + if (ctrl->app_ctx) + rc = app_decipher (ctrl->app_ctx, + keyidstr, + pin_cb, ctx, + ctrl->in_data.value, ctrl->in_data.valuelen, + &outdata, &outdatalen); + else + rc = card_decipher (ctrl->card_ctx, + keyidstr, + pin_cb, ctx, + ctrl->in_data.value, ctrl->in_data.valuelen, + &outdata, &outdatalen); + free (keyidstr); + if (rc) + { + log_error ("card_create_signature failed: %s\n", gpg_strerror (rc)); + } + else + { + rc = assuan_send_data (ctx, outdata, outdatalen); + xfree (outdata); + if (rc) + return rc; /* that is already an assuan error code */ + } + + return map_to_assuan_status (rc); +} + + +/* SETATTR + + This command is used to store data on a a smartcard. The allowed + names and values are depend on the currently selected smartcard + application. NAME and VALUE must be percent and '+' escaped. + + However, the curent implementation assumes that Name is not escaped; + this works as long as noone uses arbitrary escaping. + + A PIN will be requested for most NAMEs. See the corresponding + setattr function of the actually used application (app-*.c) for + details. */ +static int +cmd_setattr (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + char *keyword; + int keywordlen; + size_t nbytes; + + if ((rc = open_card (ctrl))) + return rc; + + keyword = line; + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + if (*line) + *line++ = 0; + while (spacep (line)) + line++; + nbytes = percent_plus_unescape (line); + + rc = app_setattr (ctrl->app_ctx, keyword, pin_cb, ctx, line, nbytes); + + return map_to_assuan_status (rc); +} + +/* GENKEY [--force] + + Generate a key on-card identified by NO, which is application + specific. Return values are application specific. For OpenPGP + cards 2 status lines are returned: + + S KEY-FPR + S KEY-CREATED-AT + S KEY-DATA [p|n] + + + --force is required to overwriet an already existing key. The + KEY-CREATED-AT is required for further processing because it is + part of the hashed key material for the fingerprint. + + The public part of the key can also later be retrieved using the + READKEY command. + + */ +static int +cmd_genkey (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + char *keyno; + int force = has_option (line, "--force"); + + /* Skip over options. */ + while ( *line == '-' && line[1] == '-' ) + { + while (!spacep (line)) + line++; + while (spacep (line)) + line++; + } + if (!*line) + return set_error (Parameter_Error, "no key number given"); + keyno = line; + while (!spacep (line)) + line++; + *line = 0; + + if ((rc = open_card (ctrl))) + return rc; + + if (!ctrl->app_ctx) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + rc = app_genkey (ctrl->app_ctx, ctrl, keyno, force? 1:0, pin_cb, ctx); + + return map_to_assuan_status (rc); +} + + +/* RANDOM + + Get NBYTES of random from the card and send them back as data. +*/ +static int +cmd_random (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + size_t nbytes; + unsigned char *buffer; + + if (!*line) + return set_error (Parameter_Error, "number of requested bytes missing"); + nbytes = strtoul (line, NULL, 0); + + if ((rc = open_card (ctrl))) + return rc; + + if (!ctrl->app_ctx) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + buffer = xtrymalloc (nbytes); + if (!buffer) + return ASSUAN_Out_Of_Core; + + rc = app_get_challenge (ctrl->app_ctx, nbytes, buffer); + if (!rc) + { + rc = assuan_send_data (ctx, buffer, nbytes); + xfree (buffer); + return rc; /* that is already an assuan error code */ + } + xfree (buffer); + + return map_to_assuan_status (rc); +} + + +/* PASSWD [--reset] + + Change the PIN or reset thye retry counter of the card holder + verfication vector CHVNO. */ +static int +cmd_passwd (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + char *chvnostr; + int reset_mode = has_option (line, "--reset"); + + /* Skip over options. */ + while (*line == '-' && line[1] == '-') + { + while (!spacep (line)) + line++; + while (spacep (line)) + line++; + } + if (!*line) + return set_error (Parameter_Error, "no CHV number given"); + chvnostr = line; + while (!spacep (line)) + line++; + *line = 0; + + if ((rc = open_card (ctrl))) + return rc; + + if (!ctrl->app_ctx) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + rc = app_change_pin (ctrl->app_ctx, ctrl, chvnostr, reset_mode, pin_cb, ctx +); + if (rc) + log_error ("command passwd failed: %s\n", gpg_strerror (rc)); + return map_to_assuan_status (rc); +} + + + + +/* Tell the assuan library about our commands */ +static int +register_commands (ASSUAN_CONTEXT ctx) +{ + static struct { + const char *name; + int (*handler)(ASSUAN_CONTEXT, char *line); + } table[] = { + { "SERIALNO", cmd_serialno }, + { "LEARN", cmd_learn }, + { "READCERT", cmd_readcert }, + { "READKEY", cmd_readkey }, + { "SETDATA", cmd_setdata }, + { "PKSIGN", cmd_pksign }, + { "PKAUTH", cmd_pkauth }, + { "PKDECRYPT", cmd_pkdecrypt }, + { "INPUT", NULL }, + { "OUTPUT", NULL }, + { "SETATTR", cmd_setattr }, + { "GENKEY", cmd_genkey }, + { "RANDOM", cmd_random }, + { "PASSWD", cmd_passwd }, + { NULL } + }; + int i, rc; + + for (i=0; table[i].name; i++) + { + rc = assuan_register_command (ctx, table[i].name, table[i].handler); + if (rc) + return rc; + } + assuan_set_hello_line (ctx, "GNU Privacy Guard's Smartcard server ready"); + + assuan_register_reset_notify (ctx, reset_notify); + assuan_register_option_handler (ctx, option_handler); + return 0; +} + + +/* Startup the server. If LISTEN_FD is given as -1, this is simple + piper server, otherwise it is a regular server */ +void +scd_command_handler (int listen_fd) +{ + int rc; + ASSUAN_CONTEXT ctx; + struct server_control_s ctrl; + + memset (&ctrl, 0, sizeof ctrl); + scd_init_default_ctrl (&ctrl); + + if (listen_fd == -1) + { + int filedes[2]; + + filedes[0] = 0; + filedes[1] = 1; + rc = assuan_init_pipe_server (&ctx, filedes); + } + else + { + rc = assuan_init_socket_server (&ctx, listen_fd); + } + if (rc) + { + log_error ("failed to initialize the server: %s\n", + assuan_strerror(rc)); + scd_exit (2); + } + rc = register_commands (ctx); + if (rc) + { + log_error ("failed to register commands with Assuan: %s\n", + assuan_strerror(rc)); + scd_exit (2); + } + assuan_set_pointer (ctx, &ctrl); + ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local); + ctrl.server_local->assuan_ctx = ctx; + + if (DBG_ASSUAN) + assuan_set_log_stream (ctx, log_get_stream ()); + + for (;;) + { + rc = assuan_accept (ctx); + if (rc == -1) + { + break; + } + else if (rc) + { + log_info ("Assuan accept problem: %s\n", assuan_strerror (rc)); + break; + } + + rc = assuan_process (ctx); + if (rc) + { + log_info ("Assuan processing failed: %s\n", assuan_strerror (rc)); + continue; + } + } + reset_notify (ctx); /* used for cleanup */ + + assuan_deinit_server (ctx); +} + + +/* Send a line with status information via assuan and escape all given + buffers. The variable elements are pairs of (char *, size_t), + terminated with a (NULL, 0). */ +void +send_status_info (CTRL ctrl, const char *keyword, ...) +{ + va_list arg_ptr; + const unsigned char *value; + size_t valuelen; + char buf[950], *p; + size_t n; + ASSUAN_CONTEXT ctx = ctrl->server_local->assuan_ctx; + + va_start (arg_ptr, keyword); + + p = buf; + n = 0; + while ( (value = va_arg (arg_ptr, const unsigned char *)) ) + { + valuelen = va_arg (arg_ptr, size_t); + if (!valuelen) + continue; /* empty buffer */ + if (n) + { + *p++ = ' '; + n++; + } + for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, value++) + { + if (*value < ' ' || *value == '+') + { + sprintf (p, "%%%02X", *value); + p += 3; + } + else if (*value == ' ') + *p++ = '+'; + else + *p++ = *value; + } + } + *p = 0; + assuan_write_status (ctx, keyword, buf); + + va_end (arg_ptr); +} + diff --git a/scd/iso7816.c b/scd/iso7816.c new file mode 100644 index 000000000..8903d8a5c --- /dev/null +++ b/scd/iso7816.c @@ -0,0 +1,371 @@ +/* iso7816.c - ISO 7816 commands + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include + +#include "scdaemon.h" +#include "iso7816.h" +#include "apdu.h" + +#define CMD_SELECT_FILE 0xA4 +#define CMD_VERIFY 0x20 +#define CMD_CHANGE_REFERENCE_DATA 0x24 +#define CMD_RESET_RETRY_COUNTER 0x2C +#define CMD_GET_DATA 0xCA +#define CMD_PUT_DATA 0xDA +#define CMD_PSO 0x2A +#define CMD_INTERNAL_AUTHENTICATE 0x88 +#define CMD_GENERATE_KEYPAIR 0x47 +#define CMD_GET_CHALLENGE 0x84 + +static gpg_error_t +map_sw (int sw) +{ + gpg_err_code_t ec; + + switch (sw) + { + case SW_EEPROM_FAILURE: ec = GPG_ERR_HARDWARE; break; + case SW_WRONG_LENGTH: ec = GPG_ERR_INV_VALUE; break; + case SW_CHV_WRONG: ec = GPG_ERR_BAD_PIN; break; + case SW_CHV_BLOCKED: ec = GPG_ERR_PIN_BLOCKED; break; + case SW_USE_CONDITIONS: ec = GPG_ERR_USE_CONDITIONS; break; + case SW_NOT_SUPPORTED: ec = GPG_ERR_NOT_SUPPORTED; break; + case SW_BAD_PARAMETER: ec = GPG_ERR_INV_VALUE; break; + case SW_REF_NOT_FOUND: ec = GPG_ERR_NO_OBJ; break; + case SW_BAD_P0_P1: ec = GPG_ERR_INV_VALUE; break; + case SW_INS_NOT_SUP: ec = GPG_ERR_CARD; break; + case SW_CLA_NOT_SUP: ec = GPG_ERR_CARD; break; + case SW_SUCCESS: ec = 0; break; + + case SW_HOST_OUT_OF_CORE: ec = GPG_ERR_ENOMEM; break; + case SW_HOST_INV_VALUE: ec = GPG_ERR_INV_VALUE; break; + case SW_HOST_INCOMPLETE_CARD_RESPONSE: ec = GPG_ERR_CARD; break; + default: + if ((sw & 0x010000)) + ec = GPG_ERR_GENERAL; /* Should not happen. */ + else if ((sw & 0xff00) == SW_MORE_DATA) + ec = 0; /* This should actually never been seen here. */ + else + ec = GPG_ERR_CARD; + } + return gpg_error (ec); +} + +/* This function is specialized version of the SELECT FILE command. + SLOT is the card and reader as created for example by + apdu_open_reader (), AID is a buffer of size AIDLEN holding the + requested application ID. The function can't be used to enumerate + AIDs and won't return the AID on success. The return value is 0 + for okay or GNUPG error code. Note that ISO error codes are + internally mapped. */ +gpg_error_t +iso7816_select_application (int slot, const char *aid, size_t aidlen) +{ + int sw; + + sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, 4, 0, aidlen, aid); + return map_sw (sw); +} + + +/* Perform a VERIFY command on SLOT using the card holder verification + vector CHVNO with a CHV of lenght CHVLEN. Returns 0 on success. */ +gpg_error_t +iso7816_verify (int slot, int chvno, const char *chv, size_t chvlen) +{ + int sw; + + sw = apdu_send_simple (slot, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv); + return map_sw (sw); +} + +/* Perform a CHANGE_REFERENCE_DATA command on SLOT for the card holder + verification vector CHVNO. If the OLDCHV is NULL (and OLDCHVLEN + 0), a "change reference data" is done, otherwise an "exchange + reference data". The new reference data is expected in NEWCHV of + length NEWCHVLEN. */ +gpg_error_t +iso7816_change_reference_data (int slot, int chvno, + const char *oldchv, size_t oldchvlen, + const char *newchv, size_t newchvlen) +{ + int sw; + char *buf; + + if ((!oldchv && oldchvlen) + || (oldchv && !oldchvlen) + || !newchv || !newchvlen ) + return gpg_error (GPG_ERR_INV_VALUE); + + buf = xtrymalloc (oldchvlen + newchvlen); + if (!buf) + return out_of_core (); + if (oldchvlen) + memcpy (buf, oldchv, oldchvlen); + memcpy (buf+oldchvlen, newchv, newchvlen); + + sw = apdu_send_simple (slot, 0x00, CMD_CHANGE_REFERENCE_DATA, + oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf); + xfree (buf); + return map_sw (sw); + +} + +gpg_error_t +iso7816_reset_retry_counter (int slot, int chvno, + const char *newchv, size_t newchvlen) +{ + int sw; + + if (!newchv || !newchvlen ) + return gpg_error (GPG_ERR_INV_VALUE); + + sw = apdu_send_simple (slot, 0x00, CMD_RESET_RETRY_COUNTER, + 2, chvno, newchvlen, newchv); + return map_sw (sw); +} + + +/* Perform a GET DATA command requesting TAG and storing the result in + a newly allocated buffer at the address passed by RESULT. Return + the length of this data at the address of RESULTLEN. */ +gpg_error_t +iso7816_get_data (int slot, int tag, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + sw = apdu_send (slot, 0x00, CMD_GET_DATA, + ((tag >> 8) & 0xff), (tag & 0xff), -1, NULL, + result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + +/* Perform a PUT DATA command on card in SLOT. Write DATA of length + DATALEN to TAG. */ +gpg_error_t +iso7816_put_data (int slot, int tag, + const unsigned char *data, size_t datalen) +{ + int sw; + + sw = apdu_send_simple (slot, 0x00, CMD_PUT_DATA, + ((tag >> 8) & 0xff), (tag & 0xff), + datalen, data); + return map_sw (sw); +} + + +/* Perform the security operation COMPUTE DIGITAL SIGANTURE. On + success 0 is returned and the data is availavle in a newly + allocated buffer stored at RESULT with its length stored at + RESULTLEN. */ +gpg_error_t +iso7816_compute_ds (int slot, const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!data || !datalen || !result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + sw = apdu_send (slot, 0x00, CMD_PSO, 0x9E, 0x9A, datalen, data, + result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + +/* Perform the security operation DECIPHER. On + success 0 is returned and the plaintext is available in a newly + allocated buffer stored at RESULT with its length stored at + RESULTLEN. */ +gpg_error_t +iso7816_decipher (int slot, const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen) +{ + int sw; + unsigned char *buf; + + if (!data || !datalen || !result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + /* We need to prepend the padding indicator. */ + buf = xtrymalloc (datalen + 1); + if (!buf) + return out_of_core (); + *buf = 0; /* Padding indicator. */ + memcpy (buf+1, data, datalen); + sw = apdu_send (slot, 0x00, CMD_PSO, 0x80, 0x86, datalen+1, buf, + result, resultlen); + xfree (buf); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + +gpg_error_t +iso7816_internal_authenticate (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!data || !datalen || !result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + sw = apdu_send (slot, 0x00, CMD_INTERNAL_AUTHENTICATE, 0, 0, + datalen, data, result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + +static gpg_error_t +generate_keypair (int slot, int readonly, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!data || !datalen || !result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + sw = apdu_send (slot, 0x00, CMD_GENERATE_KEYPAIR, readonly? 0x81:0x80, 0, + datalen, data, result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + +gpg_error_t +iso7816_generate_keypair (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen) +{ + return generate_keypair (slot, 0, data, datalen, result, resultlen); +} + + +gpg_error_t +iso7816_read_public_key (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen) +{ + return generate_keypair (slot, 1, data, datalen, result, resultlen); +} + + + +gpg_error_t +iso7816_get_challenge (int slot, int length, unsigned char *buffer) +{ + int sw; + unsigned char *result; + size_t resultlen, n; + + if (!buffer || length < 1) + return gpg_error (GPG_ERR_INV_VALUE); + + do + { + result = NULL; + n = length > 254? 254 : length; + sw = apdu_send_le (slot, 0x00, CMD_GET_CHALLENGE, 0, 0, -1, NULL, + n, + &result, &resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (result); + return map_sw (sw); + } + if (resultlen > n) + resultlen = n; + memcpy (buffer, result, resultlen); + buffer += resultlen; + length -= resultlen; + xfree (result); + } + while (length > 0); + + return 0; +} diff --git a/scd/iso7816.h b/scd/iso7816.h new file mode 100644 index 000000000..d7e77a101 --- /dev/null +++ b/scd/iso7816.h @@ -0,0 +1,56 @@ +/* iso7816.h - ISO 7816 commands + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef ISO7816_H +#define ISO7816_H + +gpg_error_t iso7816_select_application (int slot, + const char *aid, size_t aidlen); +gpg_error_t iso7816_verify (int slot, + int chvno, const char *chv, size_t chvlen); +gpg_error_t iso7816_change_reference_data (int slot, int chvno, + const char *oldchv, size_t oldchvlen, + const char *newchv, size_t newchvlen); +gpg_error_t iso7816_reset_retry_counter (int slot, int chvno, + const char *newchv, size_t newchvlen); +gpg_error_t iso7816_get_data (int slot, int tag, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_put_data (int slot, int tag, + const unsigned char *data, size_t datalen); +gpg_error_t iso7816_compute_ds (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_decipher (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_internal_authenticate (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_generate_keypair (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_read_public_key (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_get_challenge (int slot, + int length, unsigned char *buffer); + + +#endif /*ISO7816_H*/ diff --git a/scd/sc-copykeys.c b/scd/sc-copykeys.c new file mode 100644 index 000000000..9caf39a8a --- /dev/null +++ b/scd/sc-copykeys.c @@ -0,0 +1,731 @@ +/* sc-copykeys.c - A tool to store keys on a smartcard. + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#define JNLIB_NEED_LOG_LOGV +#include "scdaemon.h" +#include + +#include "../common/ttyio.h" +#include "../common/simple-pwquery.h" +#include "apdu.h" /* for open_reader */ +#include "atr.h" +#include "app-common.h" + +#define _(a) (a) + + +enum cmd_and_opt_values +{ oVerbose = 'v', + oReaderPort = 500, + oDebug, + oDebugAll, + +aTest }; + + +static ARGPARSE_OPTS opts[] = { + + { 301, NULL, 0, "@Options:\n " }, + + { oVerbose, "verbose", 0, "verbose" }, + { oReaderPort, "reader-port", 1, "|N|connect to reader at port N"}, + { oDebug, "debug" ,4|16, "set debugging flags"}, + { oDebugAll, "debug-all" ,0, "enable full debugging"}, + {0} +}; + + +static void copykeys (APP app, const char *fname); + + +static const char * +my_strusage (int level) +{ + const char *p; + switch (level) + { + case 11: p = "sc-copykeys (GnuPG)"; + break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n"); + break; + case 1: + case 40: p = _("Usage: sc-copykeys [options] (-h for help)\n"); + break; + case 41: p = _("Syntax: sc-copykeys [options] " + "file-with-key\n" + "Copy keys to a smartcards\n"); + break; + + default: p = NULL; + } + return p; +} + +/* Used by gcry for logging */ +static void +my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr) +{ + /* translate the log levels */ + switch (level) + { + case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break; + case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break; + case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break; + case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break; + case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break; + case GCRY_LOG_BUG: level = JNLIB_LOG_BUG; break; + case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break; + default: level = JNLIB_LOG_ERROR; break; + } + log_logv (level, fmt, arg_ptr); +} + + +int +main (int argc, char **argv ) +{ + ARGPARSE_ARGS pargs; + int slot, rc; + int reader_port = 32768; /* First USB reader. */ + struct app_ctx_s appbuf; + + memset (&appbuf, 0, sizeof appbuf); + + set_strusage (my_strusage); + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + log_set_prefix ("sc-copykeys", 1); + + /* check that the libraries are suitable. Do it here because + the option parsing may need services of the library */ + if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) ) + { + log_fatal( _("libgcrypt is too old (need %s, have %s)\n"), + NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) ); + } + + gcry_set_log_handler (my_gcry_logger, NULL); + gcry_control (GCRYCTL_DISABLE_SECMEM, 0); /* FIXME - we want to use it */ + /* FIXME? gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);*/ + + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1; /* do not remove the args */ + while (arg_parse (&pargs, opts) ) + { + switch (pargs.r_opt) + { + case oVerbose: opt.verbose++; break; + case oDebug: opt.debug |= pargs.r.ret_ulong; break; + case oDebugAll: opt.debug = ~0; break; + default : pargs.err = 2; break; + } + } + if (log_get_errorcount(0)) + exit(2); + + if (argc != 1) + usage (1); + + slot = apdu_open_reader (reader_port); + if (slot == -1) + exit (1); + + /* FIXME: Use select_application. */ + appbuf.slot = slot; + rc = app_select_openpgp (&appbuf, &appbuf.serialno, &appbuf.serialnolen); + if (rc) + { + log_error ("selecting openpgp failed: %s\n", gpg_strerror (rc)); + exit (1); + } + appbuf.initialized = 1; + log_info ("openpgp application selected\n"); + + copykeys (&appbuf, *argv); + + + return 0; +} + + + +void +send_status_info (CTRL ctrl, const char *keyword, ...) +{ + /* DUMMY */ +} + + + +static char * +read_file (const char *fname, size_t *r_length) +{ + FILE *fp; + struct stat st; + char *buf; + size_t buflen; + + fp = fname? fopen (fname, "rb") : stdin; + if (!fp) + { + log_error ("can't open `%s': %s\n", + fname? fname: "[stdin]", strerror (errno)); + return NULL; + } + + if (fstat (fileno(fp), &st)) + { + log_error ("can't stat `%s': %s\n", + fname? fname: "[stdin]", strerror (errno)); + if (fname) + fclose (fp); + return NULL; + } + + buflen = st.st_size; + buf = xmalloc (buflen+1); + if (fread (buf, buflen, 1, fp) != 1) + { + log_error ("error reading `%s': %s\n", + fname? fname: "[stdin]", strerror (errno)); + if (fname) + fclose (fp); + xfree (buf); + return NULL; + } + if (fname) + fclose (fp); + + *r_length = buflen; + return buf; +} + + +static gcry_sexp_t +read_key (const char *fname) +{ + char *buf; + size_t buflen; + gcry_sexp_t private; + int rc; + + buf = read_file (fname, &buflen); + if (!buf) + return NULL; + + rc = gcry_sexp_new (&private, buf, buflen, 1); + if (rc) + { + log_error ("gcry_sexp_new failed: %s\n", gpg_strerror (rc)); + return NULL; + } + xfree (buf); + + return private; +} + + + +static gcry_mpi_t * +sexp_to_kparms (gcry_sexp_t sexp, unsigned long *created) +{ + gcry_sexp_t list, l2; + const char *name; + const char *s; + size_t n; + int i, idx; + const char *elems; + gcry_mpi_t *array; + + *created = 0; + list = gcry_sexp_find_token (sexp, "private-key", 0 ); + if(!list) + return NULL; + + /* quick hack to get the creation time. */ + l2 = gcry_sexp_find_token (list, "created", 0); + if (l2 && (name = gcry_sexp_nth_data (l2, 1, &n))) + { + char *tmp = xmalloc (n+1); + memcpy (tmp, name, n); + tmp[n] = 0; + *created = strtoul (tmp, NULL, 10); + xfree (tmp); + } + gcry_sexp_release (l2); + l2 = gcry_sexp_cadr (list); + gcry_sexp_release (list); + list = l2; + name = gcry_sexp_nth_data (list, 0, &n); + if(!name || n != 3 || memcmp (name, "rsa", 3)) + { + gcry_sexp_release (list); + return NULL; + } + + /* Parameter names used with RSA. */ + elems = "nedpqu"; + array = xcalloc (strlen(elems) + 1, sizeof *array); + for (idx=0, s=elems; *s; s++, idx++ ) + { + l2 = gcry_sexp_find_token (list, s, 1); + if (!l2) + { + for (i=0; i 32) + { + log_error ("public exponent too large (more than 32 bits)\n"); + goto failure; + } + nbits = gcry_mpi_get_nbits (rsa_p); + if (nbits != 512) + { + log_error ("length of first RSA prime is not 512\n"); + goto failure; + } + nbits = gcry_mpi_get_nbits (rsa_q); + if (nbits != 512) + { + log_error ("length of second RSA prime is not 512\n"); + goto failure; + } + + nbits = gcry_mpi_get_nbits (rsa_n); + if (nbits != 1024) + { + log_error ("length of RSA modulus is not 1024\n"); + goto failure; + } + + keyno = query_card (app); + if (!keyno) + goto failure; + + /* Build the private key template as described in section 4.3.3.6 of + the specs. + 0xC0 public exponent + 0xC1 prime p + 0xC2 prime q */ + template = tp = xmalloc (1+2 + 1+1+4 + 1+1+64 + 1+1+64); + *tp++ = 0xC0; + *tp++ = 4; + rc = gcry_mpi_print (GCRYMPI_FMT_USG, tp, 4, &n, rsa_e); + if (rc) + { + log_error ("mpi_print failed: %s\n", gpg_strerror (rc)); + goto failure; + } + assert (n <= 4); + memcpy (e, tp, n); + elen = n; + if (n != 4) + { + memmove (tp+4-n, tp, 4-n); + memset (tp, 0, 4-n); + } + tp += 4; + + *tp++ = 0xC1; + *tp++ = 64; + rc = gcry_mpi_print (GCRYMPI_FMT_USG, tp, 64, &n, rsa_p); + if (rc) + { + log_error ("mpi_print failed: %s\n", gpg_strerror (rc)); + goto failure; + } + assert (n == 64); + tp += 64; + + *tp++ = 0xC2; + *tp++ = 64; + rc = gcry_mpi_print (GCRYMPI_FMT_USG, tp, 64, &n, rsa_q); + if (rc) + { + log_error ("mpi_print failed: %s\n", gpg_strerror (rc)); + goto failure; + } + assert (n == 64); + tp += 64; + assert (tp - template == 138); + + /* (we need the modulus to calculate the fingerprint) */ + rc = gcry_mpi_print (GCRYMPI_FMT_USG, m, 128, &n, rsa_n); + if (rc) + { + log_error ("mpi_print failed: %s\n", gpg_strerror (rc)); + goto failure; + } + assert (n == 128); + mlen = 128; + + + rc = app_openpgp_storekey (app, keyno, + template, tp - template, + created_at, + m, mlen, + e, elen, + pincb, NULL); + + if (rc) + { + log_error ("error storing key: %s\n", gpg_strerror (rc)); + goto failure; + } + log_info ("key successfully stored\n"); + { + unsigned char *mm, *ee; + size_t mmlen, eelen; + int i; + + rc = app_openpgp_readkey (app, keyno, &mm, &mmlen, &ee, &eelen); + if (rc) + { + log_error ("error reading key back: %s\n", gpg_strerror (rc)); + goto failure; + } + + /* Strip leading zeroes. */ + for (i=0; i < mmlen && !mm[i]; i++) + ; + mmlen -= i; + memmove (mm, mm+i, mmlen); + for (i=0; i < eelen && !ee[i]; i++) + ; + eelen -= i; + memmove (ee, ee+i, eelen); + + if (eelen != elen || mmlen != mlen) + { + log_error ("key parameter length mismatch (n=%u/%u, e=%u/%u)\n", + (unsigned int)mlen, (unsigned int)mmlen, + (unsigned int)elen, (unsigned int)eelen); + xfree (mm); + xfree (ee); + goto failure; + } + + if (memcmp (m, mm, mlen)) + { + log_error ("key parameter n mismatch\n"); + log_printhex ("original n: ", m, mlen); + log_printhex (" copied n: ", mm, mlen); + xfree (mm); + xfree (ee); + goto failure; + } + if (memcmp (e, ee, elen)) + { + log_error ("key parameter e mismatch\n"); + log_printhex ("original e: ", e, elen); + log_printhex (" copied e: ", ee, elen); + xfree (mm); + xfree (ee); + goto failure; + } + xfree (mm); + xfree (ee); + } + + + gcry_mpi_release (rsa_e); + gcry_mpi_release (rsa_p); + gcry_mpi_release (rsa_q); + gcry_mpi_release (rsa_n); + return; + + failure: + gcry_mpi_release (rsa_e); + gcry_mpi_release (rsa_p); + gcry_mpi_release (rsa_q); + gcry_mpi_release (rsa_n); + exit (1); +} + + diff --git a/scd/sc-investigate.c b/scd/sc-investigate.c new file mode 100644 index 000000000..e8f0eb83c --- /dev/null +++ b/scd/sc-investigate.c @@ -0,0 +1,209 @@ +/* sc-investigate.c - A tool to look around on smartcards. + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include + +#include +#include +#include +#include + +#define JNLIB_NEED_LOG_LOGV +#include "scdaemon.h" +#include + +#include "apdu.h" /* for open_reader */ +#include "atr.h" +#include "app-common.h" + +#define _(a) (a) + + +enum cmd_and_opt_values +{ oVerbose = 'v', + oReaderPort = 500, + oDebug, + oDebugAll, + + oGenRandom, + +aTest }; + + +static ARGPARSE_OPTS opts[] = { + + { 301, NULL, 0, "@Options:\n " }, + + { oVerbose, "verbose", 0, "verbose" }, + { oReaderPort, "reader-port", 1, "|N|connect to reader at port N"}, + { oDebug, "debug" ,4|16, "set debugging flags"}, + { oDebugAll, "debug-all" ,0, "enable full debugging"}, + { oGenRandom, "gen-random", 4, "|N|generate N bytes of random"}, + {0} +}; + +static const char * +my_strusage (int level) +{ + const char *p; + switch (level) + { + case 11: p = "sc-investigate (GnuPG)"; + break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n"); + break; + case 1: + case 40: p = _("Usage: sc-investigate [options] (-h for help)\n"); + break; + case 41: p = _("Syntax: sc-investigate [options] [args]]\n" + "Have a look at smartcards\n"); + break; + + default: p = NULL; + } + return p; +} + +/* Used by gcry for logging */ +static void +my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr) +{ + /* translate the log levels */ + switch (level) + { + case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break; + case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break; + case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break; + case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break; + case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break; + case GCRY_LOG_BUG: level = JNLIB_LOG_BUG; break; + case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break; + default: level = JNLIB_LOG_ERROR; break; + } + log_logv (level, fmt, arg_ptr); +} + + +int +main (int argc, char **argv ) +{ + ARGPARSE_ARGS pargs; + int slot, rc; + int reader_port = 32768; /* First USB reader. */ + struct app_ctx_s appbuf; + unsigned long gen_random = 0; + + memset (&appbuf, 0, sizeof appbuf); + + set_strusage (my_strusage); + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + log_set_prefix ("sc-investigate", 1); + + /* check that the libraries are suitable. Do it here because + the option parsing may need services of the library */ + if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) ) + { + log_fatal( _("libgcrypt is too old (need %s, have %s)\n"), + NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) ); + } + + gcry_set_log_handler (my_gcry_logger, NULL); + /* FIXME? gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);*/ + + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1; /* do not remove the args */ + while (arg_parse (&pargs, opts) ) + { + switch (pargs.r_opt) + { + case oVerbose: opt.verbose++; break; + case oDebug: opt.debug |= pargs.r.ret_ulong; break; + case oDebugAll: opt.debug = ~0; break; + case oGenRandom: gen_random = pargs.r.ret_ulong; break; + default : pargs.err = 2; break; + } + } + if (log_get_errorcount(0)) + exit(2); + + if (opt.verbose < 2) + opt.verbose = 2; /* hack to let select_openpgp print some info. */ + + if (argc) + usage (1); + + slot = apdu_open_reader (reader_port); + if (slot == -1) + exit (1); + + if (!gen_random) + { + rc = atr_dump (slot, stdout); + if (rc) + log_error ("can't dump ATR: %s\n", gpg_strerror (rc)); + } + + appbuf.slot = slot; + rc = app_select_openpgp (&appbuf, NULL, NULL); + if (rc) + log_error ("selecting openpgp failed: %s\n", gpg_strerror (rc)); + else + { + appbuf.initialized = 1; + log_info ("openpgp application selected\n"); + + if (gen_random) + { + size_t nbytes; + unsigned char *buffer; + + buffer = xmalloc (4096); + do + { + nbytes = gen_random > 4096? 4096 : gen_random; + rc = app_get_challenge (&appbuf, nbytes, buffer); + if (rc) + log_error ("app_get_challenge failed: %s\n",gpg_strerror (rc)); + else + { + if (fwrite (buffer, nbytes, 1, stdout) != 1) + log_error ("writing to stdout failed: %s\n", + strerror (errno)); + gen_random -= nbytes; + } + } + while (gen_random && !log_get_errorcount (0)); + xfree (buffer); + } + } + + return log_get_errorcount (0)? 2:0; +} + + + +void +send_status_info (CTRL ctrl, const char *keyword, ...) +{ + /* DUMMY */ +} diff --git a/scd/scdaemon.c b/scd/scdaemon.c new file mode 100644 index 000000000..8e0ef37c9 --- /dev/null +++ b/scd/scdaemon.c @@ -0,0 +1,638 @@ +/* scdaemon.c - The GnuPG Smartcard Daemon + * Copyright (C) 2001, 2002 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define JNLIB_NEED_LOG_LOGV +#include "scdaemon.h" +#include +#include + +#include /* malloc hooks */ + +#include "i18n.h" +#include "sysutils.h" + + + +enum cmd_and_opt_values +{ aNull = 0, + oCsh = 'c', + oQuiet = 'q', + oSh = 's', + oVerbose = 'v', + + oNoVerbose = 500, + oOptions, + oDebug, + oDebugAll, + oDebugWait, + oDebugSC, + oNoGreeting, + oNoOptions, + oHomedir, + oNoDetach, + oNoGrab, + oLogFile, + oServer, + oDaemon, + oBatch, + oReaderPort, + +aTest }; + + + +static ARGPARSE_OPTS opts[] = { + + { 301, NULL, 0, N_("@Options:\n ") }, + + { oServer, "server", 0, N_("run in server mode (foreground)") }, + { oDaemon, "daemon", 0, N_("run in daemon mode (background)") }, + { oVerbose, "verbose", 0, N_("verbose") }, + { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, + { oSh, "sh", 0, N_("sh-style command output") }, + { oCsh, "csh", 0, N_("csh-style command output") }, + { oOptions, "options" , 2, N_("read options from file")}, + { oDebug, "debug" ,4|16, N_("set debugging flags")}, + { oDebugAll, "debug-all" ,0, N_("enable full debugging")}, + { oDebugWait,"debug-wait",1, "@"}, + { oDebugSC, "debug-sc", 1, N_("|N|set OpenSC debug level to N")}, + { oNoDetach, "no-detach" ,0, N_("do not detach from the console")}, + { oLogFile, "log-file" ,2, N_("use a log file for the server")}, + { oReaderPort, "reader-port", 1, N_("|N|connect to reader at port N")}, + + {0} +}; + + +static volatile int caught_fatal_sig = 0; + +/* It is possible that we are currently running under setuid permissions */ +static int maybe_setuid = 1; + +/* Name of the communication socket */ +static char socket_name[128]; + +static const char * +my_strusage (int level) +{ + const char *p; + switch (level) + { + case 11: p = "scdaemon (GnuPG)"; + break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n"); + break; + case 1: + case 40: p = _("Usage: scdaemon [options] (-h for help)"); + break; + case 41: p = _("Syntax: scdaemon [options] [command [args]]\n" + "Smartcard daemon for GnuPG\n"); + break; + + default: p = NULL; + } + return p; +} + + + +static void +i18n_init (void) +{ +#ifdef USE_SIMPLE_GETTEXT + set_gettext_file( PACKAGE ); +#else +#ifdef ENABLE_NLS + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); +#endif +#endif +} + + + +/* Used by gcry for logging */ +static void +my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr) +{ + /* translate the log levels */ + switch (level) + { + case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break; + case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break; + case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break; + case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break; + case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break; + case GCRY_LOG_BUG: level = JNLIB_LOG_BUG; break; + case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break; + default: level = JNLIB_LOG_ERROR; break; + } + log_logv (level, fmt, arg_ptr); +} + + +static void +cleanup (void) +{ + if (*socket_name) + { + char *p; + + remove (socket_name); + p = strrchr (socket_name, '/'); + if (p) + { + *p = 0; + rmdir (socket_name); + *p = '/'; + } + *socket_name = 0; + } +} + + +static RETSIGTYPE +cleanup_sh (int sig) +{ + if (caught_fatal_sig) + raise (sig); + caught_fatal_sig = 1; + + /* gcry_control( GCRYCTL_TERM_SECMEM );*/ + cleanup (); + +#ifndef HAVE_DOSISH_SYSTEM + { /* reset action to default action and raise signal again */ + struct sigaction nact; + nact.sa_handler = SIG_DFL; + sigemptyset( &nact.sa_mask ); + nact.sa_flags = 0; + sigaction( sig, &nact, NULL); + } +#endif + raise( sig ); +} + +int +main (int argc, char **argv ) +{ + ARGPARSE_ARGS pargs; + int orig_argc; + int may_coredump; + char **orig_argv; + FILE *configfp = NULL; + char *configname = NULL; + const char *shell; + unsigned configlineno; + int parse_debug = 0; + int default_config =1; + int greeting = 0; + int nogreeting = 0; + int pipe_server = 0; + int is_daemon = 0; + int nodetach = 0; + int csh_style = 0; + char *logfile = NULL; + int debug_wait = 0; + int reader_port = 32768; /* First USB reader. */ + + set_strusage (my_strusage); + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + /* Please note that we may running SUID(ROOT), so be very CAREFUL + when adding any stuff between here and the call to INIT_SECMEM() + somewhere after the option parsing */ + log_set_prefix ("scdaemon", 1|4); + i18n_init (); + + /* check that the libraries are suitable. Do it here because + the option parsing may need services of the library */ + if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) ) + { + log_fatal( _("libgcrypt is too old (need %s, have %s)\n"), + NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) ); + } + + ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free); + assuan_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free); + gcry_set_log_handler (my_gcry_logger, NULL); + gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); + + may_coredump = disable_core_dumps (); + + shell = getenv ("SHELL"); + if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") ) + csh_style = 1; + + /* FIXME: Using this homedir option does only make sense when not + running as a system service. We might want to check for this by + looking at the uid or ebtter use an explict option for this */ + opt.homedir = getenv("GNUPGHOME"); + if (!opt.homedir || !*opt.homedir) + opt.homedir = GNUPG_DEFAULT_HOMEDIR; + + /* check whether we have a config file on the commandline */ + orig_argc = argc; + orig_argv = argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */ + while (arg_parse( &pargs, opts)) + { + if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll) + parse_debug++; + else if (pargs.r_opt == oOptions) + { /* yes there is one, so we do not try the default one, but + read the option file when it is encountered at the + commandline */ + default_config = 0; + } + else if (pargs.r_opt == oNoOptions) + default_config = 0; /* --no-options */ + else if (pargs.r_opt == oHomedir) + opt.homedir = pargs.r.ret_str; + } + + /* initialize the secure memory. */ + gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); + maybe_setuid = 0; + + /* + Now we are working under our real uid + */ + + + if (default_config) + configname = make_filename (opt.homedir, "scdaemon.conf", NULL ); + + argc = orig_argc; + argv = orig_argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1; /* do not remove the args */ + next_pass: + if (configname) + { + configlineno = 0; + configfp = fopen (configname, "r"); + if (!configfp) + { + if (default_config) + { + if( parse_debug ) + log_info (_("NOTE: no default option file `%s'\n"), + configname ); + } + else + { + log_error (_("option file `%s': %s\n"), + configname, strerror(errno) ); + exit(2); + } + xfree (configname); + configname = NULL; + } + if (parse_debug && configname ) + log_info (_("reading options from `%s'\n"), configname ); + default_config = 0; + } + + while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) ) + { + switch (pargs.r_opt) + { + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + case oBatch: opt.batch=1; break; + + case oDebug: opt.debug |= pargs.r.ret_ulong; break; + case oDebugAll: opt.debug = ~0; break; + case oDebugWait: debug_wait = pargs.r.ret_int; break; + case oDebugSC: opt.debug_sc = pargs.r.ret_int; break; + + case oOptions: + /* config files may not be nested (silently ignore them) */ + if (!configfp) + { + xfree(configname); + configname = xstrdup(pargs.r.ret_str); + goto next_pass; + } + break; + case oNoGreeting: nogreeting = 1; break; + case oNoVerbose: opt.verbose = 0; break; + case oNoOptions: break; /* no-options */ + case oHomedir: opt.homedir = pargs.r.ret_str; break; + case oNoDetach: nodetach = 1; break; + case oLogFile: logfile = pargs.r.ret_str; break; + case oCsh: csh_style = 1; break; + case oSh: csh_style = 0; break; + case oServer: pipe_server = 1; break; + case oDaemon: is_daemon = 1; break; + + case oReaderPort: reader_port = pargs.r.ret_int; break; + + default : pargs.err = configfp? 1:2; break; + } + } + if (configfp) + { + fclose( configfp ); + configfp = NULL; + xfree(configname); + configname = NULL; + goto next_pass; + } + xfree (configname); + configname = NULL; + if (log_get_errorcount(0)) + exit(2); + if (nogreeting ) + greeting = 0; + + if (greeting) + { + fprintf (stderr, "%s %s; %s\n", + strusage(11), strusage(13), strusage(14) ); + fprintf (stderr, "%s\n", strusage(15) ); + } +#ifdef IS_DEVELOPMENT_VERSION + log_info ("NOTE: this is a development version!\n"); +#endif + + + if (atexit (cleanup)) + { + log_error ("atexit failed\n"); + cleanup (); + exit (1); + } + + + if (debug_wait && pipe_server) + { + log_debug ("waiting for debugger - my pid is %u .....\n", + (unsigned int)getpid()); + sleep (debug_wait); + log_debug ("... okay\n"); + } + + /* now start with logging to a file if this is desired */ + if (logfile) + { + log_set_file (logfile); + log_set_prefix (NULL, 1|2|4); + } + + + if (pipe_server) + { /* this is the simple pipe based server */ + scd_command_handler (-1); + } + else if (!is_daemon) + { + log_info (_("please use the option `--daemon'" + " to run the program in the background\n")); + } + else + { /* regular server mode */ + int fd; + pid_t pid; + int i; + int len; + struct sockaddr_un serv_addr; + char *p; + + /* fixme: if there is already a running gpg-agent we should + share the same directory - and vice versa */ + *socket_name = 0; + snprintf (socket_name, DIM(socket_name)-1, + "/tmp/gpg-XXXXXX/S.scdaemon"); + socket_name[DIM(socket_name)-1] = 0; + p = strrchr (socket_name, '/'); + if (!p) + BUG (); + *p = 0;; + if (!mkdtemp(socket_name)) + { + log_error ("can't create directory `%s': %s\n", + socket_name, strerror(errno) ); + exit (1); + } + *p = '/'; + + if (strchr (socket_name, ':') ) + { + log_error ("colons are not allowed in the socket name\n"); + exit (1); + } + if (strlen (socket_name)+1 >= sizeof serv_addr.sun_path ) + { + log_error ("name of socket to long\n"); + exit (1); + } + + + fd = socket (AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + { + log_error ("can't create socket: %s\n", strerror(errno) ); + exit (1); + } + + memset (&serv_addr, 0, sizeof serv_addr); + serv_addr.sun_family = AF_UNIX; + strcpy (serv_addr.sun_path, socket_name); + len = (offsetof (struct sockaddr_un, sun_path) + + strlen(serv_addr.sun_path) + 1); + + if (bind (fd, (struct sockaddr*)&serv_addr, len) == -1) + { + log_error ("error binding socket to `%s': %s\n", + serv_addr.sun_path, strerror (errno) ); + close (fd); + exit (1); + } + + if (listen (fd, 5 ) == -1) + { + log_error ("listen() failed: %s\n", strerror (errno)); + close (fd); + exit (1); + } + + if (opt.verbose) + log_info ("listening on socket `%s'\n", socket_name ); + + + fflush (NULL); + pid = fork (); + if (pid == (pid_t)-1) + { + log_fatal ("fork failed: %s\n", strerror (errno) ); + exit (1); + } + else if (pid) + { /* we are the parent */ + char *infostr; + + close (fd); + + /* create the info string: :: */ + if (asprintf (&infostr, "SCDAEMON_INFO=%s:%lu:1", + socket_name, (ulong)pid ) < 0) + { + log_error ("out of core\n"); + kill (pid, SIGTERM); + exit (1); + } + *socket_name = 0; /* don't let cleanup() remove the socket - + the child should do this from now on */ + if (argc) + { /* run the program given on the commandline */ + if (putenv (infostr)) + { + log_error ("failed to set environment: %s\n", + strerror (errno) ); + kill (pid, SIGTERM ); + exit (1); + } + execvp (argv[0], argv); + log_error ("failed to run the command: %s\n", strerror (errno)); + kill (pid, SIGTERM); + exit (1); + } + else + { + /* print the environment string, so that the caller can use + shell's eval to set it */ + if (csh_style) + { + *strchr (infostr, '=') = ' '; + printf ( "setenv %s\n", infostr); + } + else + { + printf ( "%s; export SCDAEMON_INFO;\n", infostr); + } + free (infostr); + exit (0); + } + /* NOTREACHED */ + } /* end parent */ + + /* this is the child */ + + /* detach from tty and put process into a new session */ + if (!nodetach ) + { /* close stdin, stdout and stderr unless it is the log stream */ + for (i=0; i <= 2; i++) + { + if ( log_get_fd () != i) + close (i); + } + if (setsid() == -1) + { + log_error ("setsid() failed: %s\n", strerror(errno) ); + cleanup (); + exit (1); + } + } + + /* setup signals */ + { + struct sigaction oact, nact; + + nact.sa_handler = cleanup_sh; + sigemptyset (&nact.sa_mask); + nact.sa_flags = 0; + + sigaction (SIGHUP, NULL, &oact); + if (oact.sa_handler != SIG_IGN) + sigaction (SIGHUP, &nact, NULL); + sigaction( SIGTERM, NULL, &oact ); + if (oact.sa_handler != SIG_IGN) + sigaction (SIGTERM, &nact, NULL); + nact.sa_handler = SIG_IGN; + sigaction (SIGPIPE, &nact, NULL); + sigaction (SIGINT, &nact, NULL); + } + + if (chdir("/")) + { + log_error ("chdir to / failed: %s\n", strerror (errno)); + exit (1); + } + + scd_command_handler (fd); + + close (fd); + } + + return 0; +} + +void +scd_exit (int rc) +{ + #if 0 +#warning no update_random_seed_file + update_random_seed_file(); + #endif +#if 0 + /* at this time a bit annoying */ + if (opt.debug & DBG_MEMSTAT_VALUE) + { + gcry_control( GCRYCTL_DUMP_MEMORY_STATS ); + gcry_control( GCRYCTL_DUMP_RANDOM_STATS ); + } + if (opt.debug) + gcry_control (GCRYCTL_DUMP_SECMEM_STATS ); +#endif + gcry_control (GCRYCTL_TERM_SECMEM ); + rc = rc? rc : log_get_errorcount(0)? 2 : 0; + exit (rc); +} + + +void +scd_init_default_ctrl (CTRL ctrl) +{ + +} + diff --git a/scd/scdaemon.h b/scd/scdaemon.h new file mode 100644 index 000000000..b21e19f8c --- /dev/null +++ b/scd/scdaemon.h @@ -0,0 +1,127 @@ +/* scdaemon.h - Global definitions for the SCdaemon + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef SCDAEMON_H +#define SCDAEMON_H + +#ifdef GPG_ERR_SOURCE_DEFAULT +#error GPG_ERR_SOURCE_DEFAULT already defined +#endif +#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_SCD +#include +#include + +#include +#include +#include "../common/util.h" +#include "../common/errors.h" + +/* Convenience funcion to be used instead of returning the old + GNUPG_Out_Of_Core. */ +static __inline__ gpg_error_t +out_of_core (void) +{ + return gpg_error (gpg_err_code_from_errno (errno)); +} + + +#define MAX_DIGEST_LEN 24 + +/* A large struct name "opt" to keep global flags */ +struct { + unsigned int debug; /* debug flags (DBG_foo_VALUE) */ + int debug_sc; /* OpenSC debug level */ + int verbose; /* verbosity level */ + int quiet; /* be as quiet as possible */ + int dry_run; /* don't change any persistent data */ + int batch; /* batch mode */ + const char *homedir; /* configuration directory name */ +} opt; + + +#define DBG_COMMAND_VALUE 1 /* debug commands i/o */ +#define DBG_MPI_VALUE 2 /* debug mpi details */ +#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ +#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */ +#define DBG_CACHE_VALUE 64 /* debug the caching */ +#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */ +#define DBG_HASHING_VALUE 512 /* debug hashing operations */ +#define DBG_ASSUAN_VALUE 1024 +#define DBG_CARD_IO_VALUE 2048 + +#define DBG_COMMAND (opt.debug & DBG_COMMAND_VALUE) +#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) +#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE) +#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE) +#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE) +#define DBG_ASSUAN (opt.debug & DBG_ASSUAN_VALUE) +#define DBG_CARD_IO (opt.debug & DBG_CARD_IO_VALUE) + +struct server_local_s; +struct card_ctx_s; +struct app_ctx_s; + +struct server_control_s { + struct server_local_s *server_local; + struct card_ctx_s *card_ctx; + struct app_ctx_s *app_ctx; + struct { + unsigned char *value; + int valuelen; + } in_data; /* helper to store the value we are going to sign */ + +}; + +typedef struct server_control_s *CTRL; +typedef struct card_ctx_s *CARD; +typedef struct app_ctx_s *APP; + +/*-- scdaemon.c --*/ +void scd_exit (int rc); +void scd_init_default_ctrl (CTRL ctrl); + +/*-- command.c --*/ +void scd_command_handler (int); +void send_status_info (CTRL ctrl, const char *keyword, ...); + +/*-- card.c --*/ +int card_open (CARD *rcard); +void card_close (CARD card); +int card_get_serial_and_stamp (CARD card, char **serial, time_t *stamp); +int card_enum_keypairs (CARD card, int idx, + unsigned char *keygrip, + char **keyid); +int card_enum_certs (CARD card, int idx, char **certid, int *certtype); +int card_read_cert (CARD card, const char *certidstr, + unsigned char **cert, size_t *ncert); +int card_sign (CARD card, + const char *keyidstr, int hashalgo, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ); +int card_decipher (CARD card, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen); + + +#endif /*SCDAEMON_H*/ diff --git a/sm/ChangeLog b/sm/ChangeLog new file mode 100644 index 000000000..59a6b3271 --- /dev/null +++ b/sm/ChangeLog @@ -0,0 +1,816 @@ +2003-07-31 Werner Koch + + * Makefile.am (gpgsm_LDADD): Added INTLLIBS. + +2003-07-29 Werner Koch + + * gpgsm.c (main): Add secmem features and set the random seed file. + (gpgsm_exit): Update the random seed file and enable debug output. + +2003-07-27 Werner Koch + + Adjusted for gcry_mpi_print and gcry_mpi_scan API change. + +2003-06-24 Werner Koch + + * server.c (gpgsm_status_with_err_code): New. + * verify.c (gpgsm_verify): Use it here instead of the old + tokenizing version. + + * verify.c (strtimestamp): Renamed to strtimestamp_r + + Adjusted for changes in the libgcrypt API. Some more fixes for the + libgpg-error stuff. + +2003-06-04 Werner Koch + + * call-agent.c (init_membuf,put_membuf,get_membuf): Removed. + Include new membuf header and changed used type. + + Renamed error codes from INVALID to INV and removed _ERROR suffixes. + +2003-06-03 Werner Koch + + Changed all error codes in all files to the new libgpg-error scheme. + + * gpgsm.h: Include gpg-error.h . + * Makefile.am: Link with libgpg-error. + +2003-04-29 Werner Koch + + * Makefile.am: Use libassuan. Don't override LDFLAGS anymore. + * server.c (register_commands): Adjust for new Assuan semantics. + +2002-12-03 Werner Koch + + * call-agent.c (gpgsm_agent_passwd): New. + * gpgsm.c (main): New command --passwd and --call-protect-tool + (run_protect_tool): New. + +2002-11-25 Werner Koch + + * verify.c (gpgsm_verify): Handle content-type attribute. + +2002-11-13 Werner Koch + + * call-agent.c (start_agent): Try to use $GPG_TTY instead of + ttyname. Changed ttyname to test stdin becuase it can be assumed + that output redirection is more common that input redirection. + +2002-11-12 Werner Koch + + * gpgsm.c: New command --call-dirmngr. + * call-dirmngr.c (gpgsm_dirmngr_run_command) + (run_command_inq_cb,run_command_cb) + (run_command_status_cb): New. + +2002-11-11 Werner Koch + + * certcheck.c (gpgsm_check_cms_signature): Don't double free + s_sig but free s_pkey at leave. + +2002-11-10 Werner Koch + + * gpgsm.c: Removed duplicate --list-secret-key entry. + +2002-09-19 Werner Koch + + * certcheck.c (gpgsm_check_cert_sig): Add cert hash debugging. + + * certchain.c (find_up): Print info when the cert was not found + by the autorithyKeyIdentifier. + +2002-09-03 Werner Koch + + * gpgsm.c (main): Disable the internal libgcrypt locking. + +2002-08-21 Werner Koch + + * import.c (print_imported_summary): Cleaned up. Print new + not_imported value. + (check_and_store): Update non_imported counter. + (print_import_problem): New. + (check_and_store): Print error status message. + * server.c (get_status_string): Added STATUS_IMPORT_PROBLEM. + +2002-08-20 Werner Koch + + * gpgsm.c (main): Use the log file only in server mode. + + * import.c (print_imported_summary): New. + (check_and_store): Update the counters, take new argument. + (import_one): Factored out core of gpgsm_import. + (gpgsm_import): Print counters. + (gpgsm_import_files): New. + * gpgsm.c (main): Use the new function for import. + +2002-08-19 Werner Koch + + * decrypt.c (gpgsm_decrypt): Return a better error status token. + * verify.c (gpgsm_verify): Don't error on messages with no signing + time or no message digest. This is only the case for messages + without any signed attributes. + +2002-08-16 Werner Koch + + * certpath.c: Renamed to .. + * certchain.c: this. Renamed all all other usages of "path" in the + context of certificates to "chain". + + * call-agent.c (learn_cb): Special treatment when the issuer + certificate is missing. + +2002-08-10 Werner Koch + + * Makefile.am (INCLUDES): Add definition for localedir. + + * keylist.c (list_cert_colon): Print the short fingerprint in the + key ID field. + * fingerprint.c (gpgsm_get_short_fingerprint): New. + * verify.c (gpgsm_verify): Print more verbose info for a good + signature. + +2002-08-09 Werner Koch + + * decrypt.c (prepare_decryption): Hack to detected already + unpkcsedone keys. + + * gpgsm.c (emergency_cleanup): New. + (main): Initialize the signal handler. + + * sign.c (gpgsm_sign): Reset the hash context for subsequent + signers and release it at the end. + +2002-08-05 Werner Koch + + * server.c (cmd_signer): New command "SIGNER" + (register_commands): Register it. + (cmd_sign): Pass the signer list to gpgsm_sign. + * certlist.c (gpgsm_add_to_certlist): Add SECRET argument, check + for secret key if set and changed all callers. + * sign.c (gpgsm_sign): New argument SIGNERLIST and implemt + multiple signers. + * gpgsm.c (main): Support more than one -u. + + * server.c (cmd_recipient): Return reason code 1 for No_Public_Key + which is actually what gets returned from add_to_certlist. + +2002-07-26 Werner Koch + + * certcheck.c (gpgsm_check_cert_sig): Implement proper cleanup. + (gpgsm_check_cms_signature): Ditto. + +2002-07-22 Werner Koch + + * keydb.c (keydb_add_resource): Register a lock file. + (lock_all, unlock_all): Implemented. + + * delete.c: New. + * gpgsm.c: Made --delete-key work. + * server.c (cmd_delkeys): New. + (register_commands): New command DELKEYS. + + * decrypt.c (gpgsm_decrypt): Print a convenience note when RC2 is + used and a STATUS_ERROR with the algorithm oid. + +2002-07-03 Werner Koch + + * server.c (gpgsm_status2): Insert a blank between all optional + arguments when using assuan. + * server.c (cmd_recipient): No more need for extra blank in constants. + * import.c (print_imported_status): Ditto. + * gpgsm.c (main): Ditto. + +2002-07-02 Werner Koch + + * verify.c (gpgsm_verify): Extend the STATUS_BADSIG line with + the fingerprint. + + * certpath.c (check_cert_policy): Don't use log_error to print a + warning. + + * keydb.c (keydb_store_cert): Add optional ar EXISTED and changed + all callers. + * call-agent.c (learn_cb): Print info message only for real imports. + + * import.c (gpgsm_import): Moved duplicated code to ... + (check_and_store): new function. Added magic to import the entire + chain. Print status only for real imports and moved printing code + to .. + (print_imported_status): New. + + * call-dirmngr.c (gpgsm_dirmngr_isvalid): print status of dirmngr + call in very verbose mode. + + * gpgsm.c (main): Use the same error codes for STATUS_INV_RECP as + with the server mode. + +2002-06-29 Werner Koch + + * gpgsm.c: New option --auto-issuer-key-retrieve. + * certpath.c (find_up): Try to retrieve an issuer key from an + external source and from the ephemeral key DB. + (find_up_store_certs_cb): New. + + * keydb.c (keydb_set_ephemeral): Does now return the old + state. Call the backend only when required. + + * call-dirmngr.c (start_dirmngr): Use GNUPG_DEFAULT_DIRMNGR. + (lookup_status_cb): Issue status only when CTRL is not NULL. + (gpgsm_dirmngr_lookup): Document that CTRL is optional. + + * call-agent.c (start_agent): Use GNUPG_DEFAULT_AGENT. + +2002-06-28 Werner Koch + + * server.c (cmd_recipient): Add more reason codes. + +2002-06-27 Werner Koch + + * certpath.c (gpgsm_basic_cert_check): Use + --debug-no-path-validation to also bypass this basic check. + + * gpgsm.c (main): Use GNUPG_DEFAULT_HOMEDIR constant. + + * call-agent.c (start_agent): Create and pass the list of FD to + keep in the child to assuan. + * call-dirmngr.c (start_dirmngr): Ditto. + +2002-06-26 Werner Koch + + * import.c (gpgsm_import): Print an STATUS_IMPORTED. + + * gpgsm.c: --debug-no-path-validation does not take an argument. + +2002-06-25 Werner Koch + + * certdump.c (print_dn_part): Always print a leading slash, + removed NEED_DELIM arg and changed caller. + + * export.c (gpgsm_export): Print LFs to FP and not stdout. + (print_short_info): Ditto. Make use of gpgsm_print_name. + + * server.c (cmd_export): Use output-fd instead of data lines; this + was actually the specified way. + +2002-06-24 Werner Koch + + * gpgsm.c: Removed duped help entry for --list-keys. + + * gpgsm.c, gpgsm.h: New option --debug-no-path-validation. + + * certpath.c (gpgsm_validate_path): Use it here instead of the + debug flag hack. + + * certpath.c (check_cert_policy): Return No_Policy_Match if the + policy file could not be opened. + +2002-06-20 Werner Koch + + * certlist.c (gpgsm_add_to_certlist): Fixed locating of a + certificate with the required key usage. + + * gpgsm.c (main): Fixed a segv when using --outfile without an + argument. + + * keylist.c (print_capabilities): Also check for non-repudiation + and data encipherment. + * certlist.c (cert_usage_p): Test for signing and encryption was + swapped. Add a case for certification usage, handle + non-repudiation and data encipherment. + (gpgsm_cert_use_cert_p): New. + (gpgsm_add_to_certlist): Added a CTRL argument and changed all + callers to pass it. + * certpath.c (gpgsm_validate_path): Use it here to print a status + message. Added a CTRL argument and changed all callers to pass it. + * decrypt.c (gpgsm_decrypt): Print a status message for wrong key + usage. + * verify.c (gpgsm_verify): Ditto. + * keydb.c (classify_user_id): Allow a colon delimited fingerprint. + +2002-06-19 Werner Koch + + * call-agent.c (learn_cb): Use log_info instead of log_error on + successful import. + + * keydb.c (keydb_set_ephemeral): New. + (keydb_store_cert): New are ephemeral, changed all callers. + * keylist.c (list_external_cb): Store cert as ephemeral. + * export.c (gpgsm_export): Kludge to export epehmeral certificates. + + * gpgsm.c (main): New command --list-external-keys. + +2002-06-17 Werner Koch + + * certreqgen.c (read_parameters): Improved error handling. + (gpgsm_genkey): Print error message. + +2002-06-13 Werner Koch + + * gpgsm.c (main): New option --log-file. + +2002-06-12 Werner Koch + + * call-dirmngr.c (lookup_status_cb): New. + (gpgsm_dirmngr_lookup): Use the status CB. Add new arg CTRL and + changed caller to pass it. + + * gpgsm.c (open_fwrite): New. + (main): Allow --output for --verify. + + * sign.c (hash_and_copy_data): New. + (gpgsm_sign): Implemented normal (non-detached) signatures. + * gpgsm.c (main): Ditto. + + * certpath.c (gpgsm_validate_path): Special error handling for + no policy match. + +2002-06-10 Werner Koch + + * server.c (get_status_string): Add STATUS_ERROR. + + * certpath.c (gpgsm_validate_path): Tweaked the error checking to + return error codes in a more sensitive way. + * verify.c (gpgsm_verify): Send status TRUST_NEVER also for a bad + CA certificate and when the certificate has been revoked. Issue + TRUST_FULLY even when the cert has expired. Append an error token + to these status lines. Issue the new generic error status when a + cert was not found and when leaving the function. + +2002-06-04 Werner Koch + + * gpgsm.c (main): New command --list-sigs + * keylist.c (list_cert_std): New. Use it whenever colon mode is + not used. + (list_cert_chain): New. + +2002-05-31 Werner Koch + + * gpgsm.c (main): Don't print the "go ahead" message for an + invalid command. + +2002-05-23 Werner Koch + + * import.c (gpgsm_import): Add error messages. + +2002-05-21 Werner Koch + + * keylist.c (list_internal_keys): Renamed from gpgsm_list_keys. + (list_external_keys): New. + (gpgsm_list_keys): Dispatcher for above. + * call-dirmngr.c (lookup_cb,pattern_from_strlist) + (gpgsm_dirmngr_lookup): New. + * server.c (option_handler): Handle new option --list-mode. + (do_listkeys): Handle options and actually use the mode argument. + (get_status_string): New code TRUNCATED. + + * import.c (gpgsm_import): Try to identify the type of input and + handle certs-only messages. + +2002-05-14 Werner Koch + + * gpgsm.c: New option --faked-system-time + * sign.c (gpgsm_sign): And use it here. + * certpath.c (gpgsm_validate_path): Ditto. + +2002-05-03 Werner Koch + + * certpath.c (gpgsm_validate_path): Added EXPTIME arg and changed + all callers. + * verify.c (gpgsm_verify): Tweaked usage of log_debug and + log_error. Return EXPSIG status and add expiretime to VALIDSIG. + +2002-04-26 Werner Koch + + * gpgsm.h (DBG_AGENT,DBG_AGENT_VALUE): Replaced by DBG_ASSUAN_*. + Changed all users. + + * call-agent.c (start_agent): Be more silent without -v. + * call-dirmngr.c (start_dirmngr): Ditto. + +2002-04-25 Werner Koch + + * call-agent.c (start_agent): Make copies of old locales and check + for setlocale. + +2002-04-25 Marcus Brinkmann + + * call-agent.c (start_agent): Fix error handling logic so the + locale is always correctly reset. + +2002-04-25 Marcus Brinkmann + + * server.c (option_handler): Accept display, ttyname, ttytype, + lc_ctype and lc_messages options. + * gpgsm.c (main): Allocate memory for these options. + * gpgsm.h (struct opt): Make corresponding members non-const. + +2002-04-24 Marcus Brinkmann + + * gpgsm.h (struct opt): New members display, ttyname, ttytype, + lc_ctype, lc_messages. + * gpgsm.c (enum cmd_and_opt_values): New members oDisplay, + oTTYname, oTTYtype, oLCctype, oLCmessages. + (opts): New entries for these options. + (main): Handle these new options. + * call-agent.c (start_agent): Set the various display and tty + parameter after resetting. + +2002-04-18 Werner Koch + + * certreqgen.c (gpgsm_genkey): Write status output on success. + +2002-04-15 Werner Koch + + * gpgsm.c (main): Check ksba version. + + * certpath.c (find_up): New to use the authorithKeyIdentifier. + Use it in all other functions to locate the signing cert.. + +2002-04-11 Werner Koch + + * certlist.c (cert_usable_p): New. + (gpgsm_cert_use_sign_p,gpgsm_cert_use_encrypt_p): New. + (gpgsm_cert_use_verify_p,gpgsm_cert_use_decrypt_p): New. + (gpgsm_add_to_certlist): Check the key usage. + * sign.c (gpgsm_sign): Ditto. + * verify.c (gpgsm_verify): Print a message wehn an unsuitable + certificate was used. + * decrypt.c (gpgsm_decrypt): Ditto + * keylist.c (print_capabilities): Determine values from the cert. + +2002-03-28 Werner Koch + + * keylist.c (list_cert_colon): Fixed listing of crt record; the + issuer is not at the right place. Print a chainingID. + * certpath.c (gpgsm_walk_cert_chain): Be a bit more silent on + common errors. + +2002-03-21 Werner Koch + + * export.c: New. + * gpgsm.c: Add command --export. + * server.c (cmd_export): New. + +2002-03-13 Werner Koch + + * decrypt.c (gpgsm_decrypt): Allow multiple recipients. + +2002-03-12 Werner Koch + + * certpath.c (check_cert_policy): Print the policy list. + + * verify.c (gpgsm_verify): Detect certs-only message. + +2002-03-11 Werner Koch + + * import.c (gpgsm_import): Print a notice about imported certificates + when in verbose mode. + + * gpgsm.c (main): Print INV_RECP status. + * server.c (cmd_recipient): Ditto. + + * server.c (gpgsm_status2): New. Allows for a list of strings. + (gpgsm_status): Divert to gpgsm_status2. + + * encrypt.c (gpgsm_encrypt): Don't use a default key when no + recipients are given. Print a NO_RECP status. + +2002-03-06 Werner Koch + + * server.c (cmd_listkeys, cmd_listsecretkeys): Divert to + (do_listkeys): new. Add pattern parsing. + + * keylist.c (gpgsm_list_keys): Handle selection pattern. + + * gpgsm.c: New command --learn-card + * call-agent.c (learn_cb,gpgsm_agent_learn): New. + + * gpgsm.c (main): Print error messages for non-implemented commands. + + * base64.c (base64_reader_cb): Use case insensitive compare of the + Content-Type string to detect plain base-64. + +2002-03-05 Werner Koch + + * gpgsm.c, gpgsm.h: Add local_user. + * sign.c (gpgsm_get_default_cert): New. + (get_default_signer): Use the new function if local_user is not + set otherwise used that value. + * encrypt.c (get_default_recipient): Removed. + (gpgsm_encrypt): Use gpgsm_get_default_cert. + + * verify.c (gpgsm_verify): Better error text for a bad signature + found by comparing the hashs. + +2002-02-27 Werner Koch + + * call-dirmngr.c, call-agent.c: Add 2 more arguments to all uses + of assuan_transact. + +2002-02-25 Werner Koch + + * server.c (option_handler): Allow to use -2 for "send all certs + except the root cert". + * sign.c (add_certificate_list): Implement it here. + * certpath.c (gpgsm_is_root_cert): New. + +2002-02-19 Werner Koch + + * certpath.c (check_cert_policy): New. + (gpgsm_validate_path): And call it from here. + * gpgsm.c (main): New options --policy-file, + --disable-policy-checks and --enable-policy-checks. + * gpgsm.h (opt): Added policy_file, no_policy_checks. + +2002-02-18 Werner Koch + + * certpath.c (gpgsm_validate_path): Ask the agent to add the + certificate into the trusted list. + * call-agent.c (gpgsm_agent_marktrusted): New. + +2002-02-07 Werner Koch + + * certlist.c (gpgsm_add_to_certlist): Check that the specified + name identifies a certificate unambiguously. + (gpgsm_find_cert): Ditto. + + * server.c (cmd_listkeys): Check that the data stream is available. + (cmd_listsecretkeys): Ditto. + (has_option): New. + (cmd_sign): Fix ambiguousity in option recognition. + + * gpgsm.c (main): Enable --logger-fd. + + * encrypt.c (gpgsm_encrypt): Increased buffer size for better + performance. + + * call-agent.c (gpgsm_agent_pksign): Check the S-Exp received from + the agent. + + * keylist.c (list_cert_colon): Filter out control characters. + +2002-02-06 Werner Koch + + * decrypt.c (gpgsm_decrypt): Bail out after an decryption error. + + * server.c (reset_notify): Close input and output FDs. + (cmd_encrypt,cmd_decrypt,cmd_verify,cmd_sign.cmd_import) + (cmd_genkey): Close the FDs and release the recipient list even in + the error case. + +2002-02-01 Marcus Brinkmann + + * sign.c (gpgsm_sign): Do not release certificate twice. + +2002-01-29 Werner Koch + + * call-agent.c (gpgsm_agent_havekey): New. + * keylist.c (list_cert_colon): New arg HAVE_SECRET, print "crs" + when we know that the secret key is available. + (gpgsm_list_keys): New arg MODE, check whether a secret key is + available. Changed all callers. + * gpgsm.c (main): New command --list-secret-keys. + * server.c (cmd_listsecretkeys): New. + (cmd_listkeys): Return secret keys with "crs" record. + +2002-01-28 Werner Koch + + * certreqgen.c (create_request): Store the email address in the req. + +2002-01-25 Werner Koch + + * gpgsm.c (main): Disable core dumps. + + * sign.c (add_certificate_list): New. + (gpgsm_sign): Add the certificates to the CMS object. + * certpath.c (gpgsm_walk_cert_chain): New. + * gpgsm.h (server_control_s): Add included_certs. + * gpgsm.c: Add option --include-certs. + (gpgsm_init_default_ctrl): New. + (main): Call it. + * server.c (gpgsm_server): Ditto. + (option_handler): Support --include-certs. + +2002-01-23 Werner Koch + + * certpath.c (gpgsm_validate_path): Print the DN of a missing issuer. + * certdump.c (gpgsm_dump_string): New. + (print_dn): Replaced by above. + +2002-01-22 Werner Koch + + * certpath.c (unknown_criticals): New. + (allowed_ca): New. + (gpgsm_validate_path): Check validity, CA attribute, path length + and unknown critical extensions. + +2002-01-21 Werner Koch + + * gpgsm.c: Add option --enable-crl-checks. + + * call-agent.c (start_agent): Implemented socket based access. + * call-dirmngr.c (start_dirmngr): Ditto. + +2002-01-20 Werner Koch + + * server.c (option_handler): New. + (gpgsm_server): Register it with assuan. + +2002-01-19 Werner Koch + + * server.c (gpgsm_server): Use assuan_deinit_server and setup + assuan logging if enabled. + * call-agent.c (inq_ciphertext_cb): Don't show the session key in + an Assuan log file. + + * gpgsm.c (my_strusage): Take bugreport address from configure.ac + +2002-01-15 Werner Koch + + * import.c (gpgsm_import): Just do a basic cert check before + storing it. + * certpath.c (gpgsm_basic_cert_check): New. + + * keydb.c (keydb_store_cert): New. + * import.c (store_cert): Removed and change all caller to use + the new function. + * verify.c (store_cert): Ditto. + + * certlist.c (gpgsm_add_to_certlist): Validate the path + + * certpath.c (gpgsm_validate_path): Check the trust list. + * call-agent.c (gpgsm_agent_istrusted): New. + +2002-01-14 Werner Koch + + * call-dirmngr.c (inq_certificate): Changed for new interface semantic. + * certlist.c (gpgsm_find_cert): New. + +2002-01-13 Werner Koch + + * fingerprint.c (gpgsm_get_certid): Print the serial and not the + hash after the dot. + +2002-01-11 Werner Koch + + * call-dirmngr.c: New. + * certpath.c (gpgsm_validate_path): Check the CRL here. + * fingerprint.c (gpgsm_get_certid): New. + * gpgsm.c: New options --dirmngr-program and --disable-crl-checks. + +2002-01-10 Werner Koch + + * base64.c (gpgsm_create_writer): Allow to set the object name + +2002-01-08 Werner Koch + + * keydb.c (spacep): Removed because it is now in util.c + + * server.c (cmd_genkey): New. + * certreqgen.c: New. The parameter handling code has been taken + from gnupg/g10/keygen.c version 1.0.6. + * call-agent.c (gpgsm_agent_genkey): New. + +2002-01-02 Werner Koch + + * server.c (rc_to_assuan_status): Removed and changed all callers + to use map_to_assuan_status. + +2001-12-20 Werner Koch + + * verify.c (gpgsm_verify): Implemented non-detached signature + verification. Add OUT_FP arg, initialize a writer and changed all + callers. + * server.c (cmd_verify): Pass an out_fp if one has been set. + + * base64.c (base64_reader_cb): Try to detect an S/MIME body part. + + * certdump.c (print_sexp): Renamed to gpgsm_dump_serial, made + global. + (print_time): Renamed to gpgsm_dump_time, made global. + (gpgsm_dump_serial): Take a real S-Expression as argument and + print the first item. + * keylist.c (list_cert_colon): Ditto. + * keydb.c (keydb_search_issuer_sn): Ditto. + * decrypt.c (print_integer_sexp): Removed and made callers + use gpgsm_dump_serial. + * verify.c (print_time): Removed, made callers use gpgsm_dump_time. + +2001-12-19 Marcus Brinkmann + + * call-agent.c (start_agent): Add new argument to assuan_pipe_connect. + +2001-12-18 Werner Koch + + * verify.c (print_integer_sexp): Renamed from print_integer and + print the serial number according to the S-Exp rules. + * decrypt.c (print_integer_sexp): Ditto. + +2001-12-17 Werner Koch + + * keylist.c (list_cert_colon): Changed for new return value of + get_serial. + * keydb.c (keydb_search_issuer_sn): Ditto. + * certcheck.c (gpgsm_check_cert_sig): Likewise for other S-Exp + returingin functions. + * fingerprint.c (gpgsm_get_keygrip): Ditto. + * encrypt.c (encrypt_dek): Ditto + * certcheck.c (gpgsm_check_cms_signature): Ditto + * decrypt.c (prepare_decryption): Ditto. + * call-agent.c (gpgsm_agent_pkdecrypt): Removed arg ciphertextlen, + use KsbaSexp type and calculate the length. + + * certdump.c (print_sexp): Remaned from print_integer, changed caller. + + * Makefile.am: Use the LIBGCRYPT and LIBKSBA variables. + + * fingerprint.c (gpgsm_get_keygrip): Use the new + gcry_pk_get_keygrip to calculate the grip - note the algorithm and + therefore the grip values changed. + +2001-12-15 Werner Koch + + * certcheck.c (gpgsm_check_cms_signature): Removed the faked-key + kludge. + (gpgsm_create_cms_signature): Removed the commented fake key + code. This makes the function pretty simple. + + * gpgsm.c (main): Renamed the default key database to "keyring.kbx". + + * decrypt.c (gpgsm_decrypt): Write STATUS_DECRYPTION_*. + * sign.c (gpgsm_sign): Write a STATUS_SIG_CREATED. + +2001-12-14 Werner Koch + + * keylist.c (list_cert_colon): Kludge to show an email address + encoded in the subject's DN. + + * verify.c (gpgsm_verify): Add hash debug helpers + * sign.c (gpgsm_sign): Ditto. + + * base64.c (base64_reader_cb): Reset the linelen when we need to + skip the line and adjusted test; I somehow forgot about DeMorgan. + + * server.c (cmd_encrypt,cmd_decrypt,cmd_sign,cmd_verify) + (cmd_import): Close the FDs on success. + (close_message_fd): New. + (input_notify): Setting autodetect_encoding to 0 after initializing + it to 0 is pretty pointless. Easy to fix. + + * gpgsm.c (main): New option --debug-wait n, so that it is + possible to attach gdb when used in server mode. + + * sign.c (get_default_signer): Use keydb_classify_name here. + +2001-12-14 Marcus Brinkmann + + * call-agent.c (LINELENGTH): Removed. + (gpgsm_agent_pksign): Use ASSUAN_LINELENGTH, not LINELENGTH. + (gpgsm_agent_pkdecrypt): Likewise. + +2001-12-13 Werner Koch + + * keylist.c (list_cert_colon): Print alternative names of subject + and a few other values. + +2001-12-12 Werner Koch + + * gpgsm.c (main): New options --assume-{armor,base64,binary}. + * base64.c (base64_reader_cb): Fixed non-autodetection mode. + +2001-12-04 Werner Koch + + * call-agent.c (read_from_agent): Check for inquire responses. + (request_reply): Handle them using a new callback arg, changed all + callers. + (gpgsm_agent_pkdecrypt): New. + +2001-11-27 Werner Koch + + * base64.c: New. Changed all other functions to use this instead + of direct creation of ksba_reader/writer. + * gpgsm.c (main): Set ctrl.auto_encoding unless --no-armor is used. + +2001-11-26 Werner Koch + + * gpgsm.c: New option --agent-program + * call-agent.c (start_agent): Allow to override the default path + to the agent. + + * keydb.c (keydb_add_resource): Create keybox + + * keylist.c (gpgsm_list_keys): Fixed non-server keylisting. + + * server.c (rc_to_assuan_status): New. Use it for all commands. + + + Copyright 2001, 2002, 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. diff --git a/sm/Makefile.am b/sm/Makefile.am new file mode 100644 index 000000000..6345573e5 --- /dev/null +++ b/sm/Makefile.am @@ -0,0 +1,55 @@ +# 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 2 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +## Process this file with automake to produce Makefile.in + +localedir = $(datadir)/locale +INCLUDES = -I../intl -DLOCALEDIR=\"$(localedir)\" + +bin_PROGRAMS = gpgsm + +AM_CPPFLAGS = -I$(top_srcdir)/common -I$(top_srcdir)/intl \ + $(LIBGCRYPT_CFLAGS) $(LIBASSUAN_CFLAGS) $(KSBA_CFLAGS) + +gpgsm_SOURCES = \ + gpgsm.c gpgsm.h \ + misc.c \ + keydb.c keydb.h \ + server.c \ + call-agent.c \ + call-dirmngr.c \ + fingerprint.c \ + base64.c \ + certlist.c \ + certdump.c \ + certcheck.c \ + certchain.c \ + keylist.c \ + verify.c \ + sign.c \ + encrypt.c \ + decrypt.c \ + import.c \ + export.c \ + delete.c \ + certreqgen.c + + +gpgsm_LDADD = ../jnlib/libjnlib.a ../kbx/libkeybox.a ../common/libcommon.a \ + $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(KSBA_LIBS) -lgpg-error \ + @INTLLIBS@ diff --git a/sm/call-agent.c b/sm/call-agent.c new file mode 100644 index 000000000..4d26e3450 --- /dev/null +++ b/sm/call-agent.c @@ -0,0 +1,713 @@ +/* call-agent.c - divert operations to the agent + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LOCALE_H +#include +#endif + +#include "gpgsm.h" +#include +#include +#include "i18n.h" +#include "keydb.h" /* fixme: Move this to import.c */ +#include "../common/membuf.h" + + +static ASSUAN_CONTEXT agent_ctx = NULL; +static int force_pipe_server = 0; + +struct cipher_parm_s { + ASSUAN_CONTEXT ctx; + const char *ciphertext; + size_t ciphertextlen; +}; + +struct genkey_parm_s { + ASSUAN_CONTEXT ctx; + const char *sexp; + size_t sexplen; +}; + +struct learn_parm_s { + int error; + ASSUAN_CONTEXT ctx; + membuf_t *data; +}; + + + +/* Try to connect to the agent via socket or fork it off and work by + pipes. Handle the server's initial greeting */ +static int +start_agent (void) +{ + int rc = 0; + char *infostr, *p; + ASSUAN_CONTEXT ctx; + char *dft_display = NULL; + char *dft_ttyname = NULL; + char *dft_ttytype = NULL; + char *old_lc = NULL; + char *dft_lc = NULL; + + if (agent_ctx) + return 0; /* fixme: We need a context for each thread or serialize + the access to the agent (which is suitable given that + the agent is not MT */ + + infostr = force_pipe_server? NULL : getenv ("GPG_AGENT_INFO"); + if (!infostr) + { + const char *pgmname; + const char *argv[3]; + int no_close_list[3]; + int i; + + if (opt.verbose) + log_info (_("no running gpg-agent - starting one\n")); + + if (fflush (NULL)) + { + gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("error flushing pending output: %s\n", strerror (errno)); + return tmperr; + } + + if (!opt.agent_program || !*opt.agent_program) + opt.agent_program = GNUPG_DEFAULT_AGENT; + if ( !(pgmname = strrchr (opt.agent_program, '/'))) + pgmname = opt.agent_program; + else + pgmname++; + + argv[0] = pgmname; + argv[1] = "--server"; + argv[2] = NULL; + + i=0; + if (log_get_fd () != -1) + no_close_list[i++] = log_get_fd (); + no_close_list[i++] = fileno (stderr); + no_close_list[i] = -1; + + /* connect to the agent and perform initial handshaking */ + rc = assuan_pipe_connect (&ctx, opt.agent_program, (char**)argv, + no_close_list); + } + else + { + int prot; + int pid; + + infostr = xstrdup (infostr); + if ( !(p = strchr (infostr, ':')) || p == infostr) + { + log_error (_("malformed GPG_AGENT_INFO environment variable\n")); + xfree (infostr); + force_pipe_server = 1; + return start_agent (); + } + *p++ = 0; + pid = atoi (p); + while (*p && *p != ':') + p++; + prot = *p? atoi (p+1) : 0; + if (prot != 1) + { + log_error (_("gpg-agent protocol version %d is not supported\n"), + prot); + xfree (infostr); + force_pipe_server = 1; + return start_agent (); + } + + rc = assuan_socket_connect (&ctx, infostr, pid); + xfree (infostr); + if (rc == ASSUAN_Connect_Failed) + { + log_error (_("can't connect to the agent - trying fall back\n")); + force_pipe_server = 1; + return start_agent (); + } + } + + if (rc) + { + log_error ("can't connect to the agent: %s\n", assuan_strerror (rc)); + return gpg_error (GPG_ERR_NO_AGENT); + } + agent_ctx = ctx; + + if (DBG_ASSUAN) + log_debug ("connection to agent established\n"); + + rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return map_assuan_err (rc); + + dft_display = getenv ("DISPLAY"); + if (opt.display || dft_display) + { + char *optstr; + if (asprintf (&optstr, "OPTION display=%s", + opt.display ? opt.display : dft_display) < 0) + return OUT_OF_CORE (errno); + rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + free (optstr); + if (rc) + return map_assuan_err (rc); + } + if (!opt.ttyname) + { + dft_ttyname = getenv ("GPG_TTY"); + if ((!dft_ttyname || !*dft_ttyname) && ttyname (0)) + dft_ttyname = ttyname (0); + } + if (opt.ttyname || dft_ttyname) + { + char *optstr; + if (asprintf (&optstr, "OPTION ttyname=%s", + opt.ttyname ? opt.ttyname : dft_ttyname) < 0) + return OUT_OF_CORE (errno); + rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + free (optstr); + if (rc) + return map_assuan_err (rc); + } + dft_ttytype = getenv ("TERM"); + if (opt.ttytype || (dft_ttyname && dft_ttytype)) + { + char *optstr; + if (asprintf (&optstr, "OPTION ttytype=%s", + opt.ttyname ? opt.ttytype : dft_ttytype) < 0) + return OUT_OF_CORE (errno); + rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + free (optstr); + if (rc) + return map_assuan_err (rc); + } +#if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) + old_lc = setlocale (LC_CTYPE, NULL); + if (old_lc) + { + old_lc = strdup (old_lc); + if (!old_lc) + return OUT_OF_CORE (errno); + } + dft_lc = setlocale (LC_CTYPE, ""); +#endif + if (opt.lc_ctype || (dft_ttyname && dft_lc)) + { + char *optstr; + if (asprintf (&optstr, "OPTION lc-ctype=%s", + opt.lc_ctype ? opt.lc_ctype : dft_lc) < 0) + rc = OUT_OF_CORE (errno); + else + { + rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + free (optstr); + if (rc) + rc = map_assuan_err (rc); + } + } +#if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) + if (old_lc) + { + setlocale (LC_CTYPE, old_lc); + free (old_lc); + } +#endif + if (rc) + return rc; +#if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) + old_lc = setlocale (LC_MESSAGES, NULL); + if (old_lc) + { + old_lc = strdup (old_lc); + if (!old_lc) + return OUT_OF_CORE (errno); + } + dft_lc = setlocale (LC_MESSAGES, ""); +#endif + if (opt.lc_messages || (dft_ttyname && dft_lc)) + { + char *optstr; + if (asprintf (&optstr, "OPTION lc-messages=%s", + opt.lc_messages ? opt.lc_messages : dft_lc) < 0) + rc = OUT_OF_CORE (errno); + else + { + rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + free (optstr); + if (rc) + rc = map_assuan_err (rc); + } + } +#if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) + if (old_lc) + { + setlocale (LC_MESSAGES, old_lc); + free (old_lc); + } +#endif + + return rc; +} + + +static AssuanError +membuf_data_cb (void *opaque, const void *buffer, size_t length) +{ + membuf_t *data = opaque; + + if (buffer) + put_membuf (data, buffer, length); + return 0; +} + + + + +/* Call the agent to do a sign operation using the key identified by + the hex string KEYGRIP. */ +int +gpgsm_agent_pksign (const char *keygrip, + unsigned char *digest, size_t digestlen, int digestalgo, + char **r_buf, size_t *r_buflen ) +{ + int rc, i; + char *p, line[ASSUAN_LINELENGTH]; + membuf_t data; + size_t len; + + *r_buf = NULL; + rc = start_agent (); + if (rc) + return rc; + + if (digestlen*2 + 50 > DIM(line)) + return gpg_error (GPG_ERR_GENERAL); + + rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return map_assuan_err (rc); + + snprintf (line, DIM(line)-1, "SIGKEY %s", keygrip); + line[DIM(line)-1] = 0; + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return map_assuan_err (rc); + + sprintf (line, "SETHASH %d ", digestalgo); + p = line + strlen (line); + for (i=0; i < digestlen ; i++, p += 2 ) + sprintf (p, "%02X", digest[i]); + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return map_assuan_err (rc); + + init_membuf (&data, 1024); + rc = assuan_transact (agent_ctx, "PKSIGN", + membuf_data_cb, &data, NULL, NULL, NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return map_assuan_err (rc); + } + *r_buf = get_membuf (&data, r_buflen); + + if (!gcry_sexp_canon_len (*r_buf, *r_buflen, NULL, NULL)) + { + xfree (*r_buf); *r_buf = NULL; + return gpg_error (GPG_ERR_INV_VALUE); + } + + return *r_buf? 0 : OUT_OF_CORE (errno); +} + + + + +/* Handle a CIPHERTEXT inquiry. Note, we only send the data, + assuan_transact talkes care of flushing and writing the end */ +static AssuanError +inq_ciphertext_cb (void *opaque, const char *keyword) +{ + struct cipher_parm_s *parm = opaque; + AssuanError rc; + + assuan_begin_confidential (parm->ctx); + rc = assuan_send_data (parm->ctx, parm->ciphertext, parm->ciphertextlen); + assuan_end_confidential (parm->ctx); + return rc; +} + + +/* Call the agent to do a decrypt operation using the key identified by + the hex string KEYGRIP. */ +int +gpgsm_agent_pkdecrypt (const char *keygrip, + KsbaConstSexp ciphertext, + char **r_buf, size_t *r_buflen ) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + struct cipher_parm_s cipher_parm; + size_t n, len; + char *buf, *endp; + size_t ciphertextlen; + + if (!keygrip || strlen(keygrip) != 40 || !ciphertext || !r_buf || !r_buflen) + return gpg_error (GPG_ERR_INV_VALUE); + *r_buf = NULL; + + ciphertextlen = gcry_sexp_canon_len (ciphertext, 0, NULL, NULL); + if (!ciphertextlen) + return gpg_error (GPG_ERR_INV_VALUE); + + rc = start_agent (); + if (rc) + return rc; + + rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return map_assuan_err (rc); + + assert ( DIM(line) >= 50 ); + snprintf (line, DIM(line)-1, "SETKEY %s", keygrip); + line[DIM(line)-1] = 0; + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return map_assuan_err (rc); + + init_membuf (&data, 1024); + cipher_parm.ctx = agent_ctx; + cipher_parm.ciphertext = ciphertext; + cipher_parm.ciphertextlen = ciphertextlen; + rc = assuan_transact (agent_ctx, "PKDECRYPT", + membuf_data_cb, &data, + inq_ciphertext_cb, &cipher_parm, NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return map_assuan_err (rc); + } + + put_membuf (&data, "", 1); /* make sure it is 0 terminated */ + buf = get_membuf (&data, &len); + if (!buf) + return gpg_error (GPG_ERR_ENOMEM); + /* FIXME: We would better a return a full S-exp and not just a part */ + assert (len); + len--; /* remove the terminating 0 */ + n = strtoul (buf, &endp, 10); + if (!n || *endp != ':') + return gpg_error (GPG_ERR_INV_SEXP); + endp++; + if (endp-buf+n > len) + return gpg_error (GPG_ERR_INV_SEXP); /* oops len does not + match internal len*/ + memmove (buf, endp, n); + *r_buflen = n; + *r_buf = buf; + return 0; +} + + + + + +/* Handle a KEYPARMS inquiry. Note, we only send the data, + assuan_transact takes care of flushing and writing the end */ +static AssuanError +inq_genkey_parms (void *opaque, const char *keyword) +{ + struct genkey_parm_s *parm = opaque; + AssuanError rc; + + rc = assuan_send_data (parm->ctx, parm->sexp, parm->sexplen); + return rc; +} + + + +/* Call the agent to generate a newkey */ +int +gpgsm_agent_genkey (KsbaConstSexp keyparms, KsbaSexp *r_pubkey) +{ + int rc; + struct genkey_parm_s gk_parm; + membuf_t data; + size_t len; + char *buf; + + *r_pubkey = NULL; + rc = start_agent (); + if (rc) + return rc; + + rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return map_assuan_err (rc); + + init_membuf (&data, 1024); + gk_parm.ctx = agent_ctx; + gk_parm.sexp = keyparms; + gk_parm.sexplen = gcry_sexp_canon_len (keyparms, 0, NULL, NULL); + if (!gk_parm.sexplen) + return gpg_error (GPG_ERR_INV_VALUE); + rc = assuan_transact (agent_ctx, "GENKEY", + membuf_data_cb, &data, + inq_genkey_parms, &gk_parm, NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return map_assuan_err (rc); + } + buf = get_membuf (&data, &len); + if (!buf) + return gpg_error (GPG_ERR_ENOMEM); + if (!gcry_sexp_canon_len (buf, len, NULL, NULL)) + { + xfree (buf); + return gpg_error (GPG_ERR_INV_SEXP); + } + *r_pubkey = buf; + return 0; +} + + +/* Ask the agent whether the certificate is in the list of trusted + keys */ +int +gpgsm_agent_istrusted (KsbaCert cert) +{ + int rc; + char *fpr; + char line[ASSUAN_LINELENGTH]; + + rc = start_agent (); + if (rc) + return rc; + + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + if (!fpr) + { + log_error ("error getting the fingerprint\n"); + return gpg_error (GPG_ERR_GENERAL); + } + + snprintf (line, DIM(line)-1, "ISTRUSTED %s", fpr); + line[DIM(line)-1] = 0; + xfree (fpr); + + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + return map_assuan_err (rc); +} + +/* Ask the agent to mark CERT as a trusted Root-CA one */ +int +gpgsm_agent_marktrusted (KsbaCert cert) +{ + int rc; + char *fpr, *dn; + char line[ASSUAN_LINELENGTH]; + + rc = start_agent (); + if (rc) + return rc; + + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + if (!fpr) + { + log_error ("error getting the fingerprint\n"); + return gpg_error (GPG_ERR_GENERAL); + } + + dn = ksba_cert_get_issuer (cert, 0); + if (!dn) + { + xfree (fpr); + return gpg_error (GPG_ERR_GENERAL); + } + snprintf (line, DIM(line)-1, "MARKTRUSTED %s S %s", fpr, dn); + line[DIM(line)-1] = 0; + ksba_free (dn); + xfree (fpr); + + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + return map_assuan_err (rc); +} + + + +/* Ask the agent whether the a corresponding secret key is available + for the given keygrip */ +int +gpgsm_agent_havekey (const char *hexkeygrip) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + + rc = start_agent (); + if (rc) + return rc; + + if (!hexkeygrip || strlen (hexkeygrip) != 40) + return gpg_error (GPG_ERR_INV_VALUE); + + snprintf (line, DIM(line)-1, "HAVEKEY %s", hexkeygrip); + line[DIM(line)-1] = 0; + + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + return map_assuan_err (rc); +} + + +static AssuanError +learn_cb (void *opaque, const void *buffer, size_t length) +{ + struct learn_parm_s *parm = opaque; + size_t len; + char *buf; + KsbaCert cert; + int rc; + + if (parm->error) + return 0; + + if (buffer) + { + put_membuf (parm->data, buffer, length); + return 0; + } + /* END encountered - process what we have */ + buf = get_membuf (parm->data, &len); + if (!buf) + { + parm->error = gpg_error (GPG_ERR_ENOMEM); + return 0; + } + + + /* FIXME: this should go into import.c */ + cert = ksba_cert_new (); + if (!cert) + { + parm->error = gpg_error (GPG_ERR_ENOMEM); + return 0; + } + rc = ksba_cert_init_from_mem (cert, buf, len); + if (rc) + { + log_error ("failed to parse a certificate: %s\n", ksba_strerror (rc)); + ksba_cert_release (cert); + parm->error = map_ksba_err (rc); + return 0; + } + + rc = gpgsm_basic_cert_check (cert); + if (gpg_err_code (rc) == GPG_ERR_MISSING_CERT) + { /* For later use we store it in the ephemeral database. */ + log_info ("issuer certificate missing - storing as ephemeral\n"); + keydb_store_cert (cert, 1, NULL); + } + else if (rc) + log_error ("invalid certificate: %s\n", gpg_strerror (rc)); + else + { + int existed; + + if (!keydb_store_cert (cert, 0, &existed)) + { + if (opt.verbose > 1 && existed) + log_info ("certificate already in DB\n"); + else if (opt.verbose && !existed) + log_info ("certificate imported\n"); + } + } + + ksba_cert_release (cert); + init_membuf (parm->data, 4096); + return 0; +} + +/* Call the agent to learn about a smartcard */ +int +gpgsm_agent_learn () +{ + int rc; + struct learn_parm_s learn_parm; + membuf_t data; + size_t len; + + rc = start_agent (); + if (rc) + return rc; + + init_membuf (&data, 4096); + learn_parm.error = 0; + learn_parm.ctx = agent_ctx; + learn_parm.data = &data; + rc = assuan_transact (agent_ctx, "LEARN --send", + learn_cb, &learn_parm, + NULL, NULL, NULL, NULL); + xfree (get_membuf (&data, &len)); + if (rc) + return map_assuan_err (rc); + return learn_parm.error; +} + + +/* Ask the agent to change the passphrase of the key identified by HEXKEYGRIP. */ +int +gpgsm_agent_passwd (const char *hexkeygrip) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + + rc = start_agent (); + if (rc) + return rc; + + if (!hexkeygrip || strlen (hexkeygrip) != 40) + return gpg_error (GPG_ERR_INV_VALUE); + + snprintf (line, DIM(line)-1, "PASSWD %s", hexkeygrip); + line[DIM(line)-1] = 0; + + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + return map_assuan_err (rc); +} + diff --git a/sm/call-dirmngr.c b/sm/call-dirmngr.c new file mode 100644 index 000000000..b182b246c --- /dev/null +++ b/sm/call-dirmngr.c @@ -0,0 +1,632 @@ +/* call-dirmngr.c - communication with the dromngr + * Copyright (C) 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpgsm.h" +#include +#include + +#include "i18n.h" + +struct membuf { + size_t len; + size_t size; + char *buf; + int out_of_core; +}; + + + +static ASSUAN_CONTEXT dirmngr_ctx = NULL; +static int force_pipe_server = 0; + +struct inq_certificate_parm_s { + ASSUAN_CONTEXT ctx; + KsbaCert cert; +}; + +struct lookup_parm_s { + CTRL ctrl; + ASSUAN_CONTEXT ctx; + void (*cb)(void *, KsbaCert); + void *cb_value; + struct membuf data; + int error; +}; + +struct run_command_parm_s { + ASSUAN_CONTEXT ctx; +}; + + +/* A simple implementation of a dynamic buffer. Use init_membuf() to + create a buffer, put_membuf to append bytes and get_membuf to + release and return the buffer. Allocation errors are detected but + only returned at the final get_membuf(), this helps not to clutter + the code with out of core checks. */ + +static void +init_membuf (struct membuf *mb, int initiallen) +{ + mb->len = 0; + mb->size = initiallen; + mb->out_of_core = 0; + mb->buf = xtrymalloc (initiallen); + if (!mb->buf) + mb->out_of_core = 1; +} + +static void +put_membuf (struct membuf *mb, const void *buf, size_t len) +{ + if (mb->out_of_core) + return; + + if (mb->len + len >= mb->size) + { + char *p; + + mb->size += len + 1024; + p = xtryrealloc (mb->buf, mb->size); + if (!p) + { + mb->out_of_core = 1; + return; + } + mb->buf = p; + } + memcpy (mb->buf + mb->len, buf, len); + mb->len += len; +} + +static void * +get_membuf (struct membuf *mb, size_t *len) +{ + char *p; + + if (mb->out_of_core) + { + xfree (mb->buf); + mb->buf = NULL; + return NULL; + } + + p = mb->buf; + *len = mb->len; + mb->buf = NULL; + mb->out_of_core = 1; /* don't allow a reuse */ + return p; +} + + + + + +/* Try to connect to the agent via socket or fork it off and work by + pipes. Handle the server's initial greeting */ +static int +start_dirmngr (void) +{ + int rc; + char *infostr, *p; + ASSUAN_CONTEXT ctx; + + if (dirmngr_ctx) + return 0; /* fixme: We need a context for each thread or serialize + the access to the dirmngr */ + + infostr = force_pipe_server? NULL : getenv ("DIRMNGR_INFO"); + if (!infostr) + { + const char *pgmname; + const char *argv[3]; + int no_close_list[3]; + int i; + + if (opt.verbose) + log_info (_("no running dirmngr - starting one\n")); + + if (fflush (NULL)) + { + gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("error flushing pending output: %s\n", strerror (errno)); + return tmperr; + } + + if (!opt.dirmngr_program || !*opt.dirmngr_program) + opt.dirmngr_program = GNUPG_DEFAULT_DIRMNGR; + if ( !(pgmname = strrchr (opt.dirmngr_program, '/'))) + pgmname = opt.dirmngr_program; + else + pgmname++; + + argv[0] = pgmname; + argv[1] = "--server"; + argv[2] = NULL; + + i=0; + if (log_get_fd () != -1) + no_close_list[i++] = log_get_fd (); + no_close_list[i++] = fileno (stderr); + no_close_list[i] = -1; + + /* connect to the agent and perform initial handshaking */ + rc = assuan_pipe_connect (&ctx, opt.dirmngr_program, (char**)argv, + no_close_list); + } + else + { + int prot; + int pid; + + infostr = xstrdup (infostr); + if ( !(p = strchr (infostr, ':')) || p == infostr) + { + log_error (_("malformed DIRMNGR_INFO environment variable\n")); + xfree (infostr); + force_pipe_server = 1; + return start_dirmngr (); + } + *p++ = 0; + pid = atoi (p); + while (*p && *p != ':') + p++; + prot = *p? atoi (p+1) : 0; + if (prot != 1) + { + log_error (_("dirmngr protocol version %d is not supported\n"), + prot); + xfree (infostr); + force_pipe_server = 1; + return start_dirmngr (); + } + + rc = assuan_socket_connect (&ctx, infostr, pid); + xfree (infostr); + if (rc == ASSUAN_Connect_Failed) + { + log_error (_("can't connect to the dirmngr - trying fall back\n")); + force_pipe_server = 1; + return start_dirmngr (); + } + } + + if (rc) + { + log_error ("can't connect to the dirmngr: %s\n", assuan_strerror (rc)); + return gpg_error (GPG_ERR_NO_DIRMNGR); + } + dirmngr_ctx = ctx; + + if (DBG_ASSUAN) + log_debug ("connection to dirmngr established\n"); + return 0; +} + + + +/* Handle a SENDCERT inquiry. */ +static AssuanError +inq_certificate (void *opaque, const char *line) +{ + struct inq_certificate_parm_s *parm = opaque; + AssuanError rc; + const unsigned char *der; + size_t derlen; + + if (!(!strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8]))) + { + log_error ("unsupported inquiry `%s'\n", line); + return ASSUAN_Inquire_Unknown; + } + line += 8; + + if (!*line) + { /* send the current certificate */ + der = ksba_cert_get_image (parm->cert, &derlen); + if (!der) + rc = ASSUAN_Inquire_Error; + else + rc = assuan_send_data (parm->ctx, der, derlen); + } + else + { /* send the given certificate */ + int err; + KsbaCert cert; + + err = gpgsm_find_cert (line, &cert); + if (err) + { + log_error ("certificate not found: %s\n", gpg_strerror (err)); + rc = ASSUAN_Inquire_Error; + } + else + { + der = ksba_cert_get_image (cert, &derlen); + if (!der) + rc = ASSUAN_Inquire_Error; + else + rc = assuan_send_data (parm->ctx, der, derlen); + ksba_cert_release (cert); + } + } + + return rc; +} + + + +/* Call the directory manager to check whether the certificate is valid + Returns 0 for valid or usually one of the errors: + + GPG_ERR_CERTIFICATE_REVOKED + GPG_ERR_NO_CRL_KNOWN + GPG_ERR_CRL_TOO_OLD + */ +int +gpgsm_dirmngr_isvalid (KsbaCert cert) +{ + int rc; + char *certid; + char line[ASSUAN_LINELENGTH]; + struct inq_certificate_parm_s parm; + + rc = start_dirmngr (); + if (rc) + return rc; + + certid = gpgsm_get_certid (cert); + if (!certid) + { + log_error ("error getting the certificate ID\n"); + return gpg_error (GPG_ERR_GENERAL); + } + + if (opt.verbose > 1) + { + char *fpr = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA1); + log_info ("asking dirmngr about %s\n", fpr); + xfree (fpr); + } + + parm.ctx = dirmngr_ctx; + parm.cert = cert; + + snprintf (line, DIM(line)-1, "ISVALID %s", certid); + line[DIM(line)-1] = 0; + xfree (certid); + + rc = assuan_transact (dirmngr_ctx, line, NULL, NULL, + inq_certificate, &parm, NULL, NULL); + if (opt.verbose > 1) + log_info ("response of dirmngr: %s\n", rc? assuan_strerror (rc): "okay"); + return map_assuan_err (rc); +} + + + +/* Lookup helpers*/ +static AssuanError +lookup_cb (void *opaque, const void *buffer, size_t length) +{ + struct lookup_parm_s *parm = opaque; + size_t len; + char *buf; + KsbaCert cert; + int rc; + + if (parm->error) + return 0; + + if (buffer) + { + put_membuf (&parm->data, buffer, length); + return 0; + } + /* END encountered - process what we have */ + buf = get_membuf (&parm->data, &len); + if (!buf) + { + parm->error = gpg_error (GPG_ERR_ENOMEM); + return 0; + } + + cert = ksba_cert_new (); + if (!cert) + { + parm->error = gpg_error (GPG_ERR_ENOMEM); + return 0; + } + rc = ksba_cert_init_from_mem (cert, buf, len); + if (rc) + { + log_error ("failed to parse a certificate: %s\n", ksba_strerror (rc)); + } + else + { + parm->cb (parm->cb_value, cert); + } + + ksba_cert_release (cert); + init_membuf (&parm->data, 4096); + return 0; +} + +/* Return a properly escaped pattern from NAMES. The only error + return is NULL to indicate a malloc failure. */ +static char * +pattern_from_strlist (STRLIST names) +{ + STRLIST sl; + int n; + const char *s; + char *pattern, *p; + + for (n=0, sl=names; sl; sl = sl->next) + { + for (s=sl->d; *s; s++, n++) + { + if (*s == '%' || *s == ' ' || *s == '+') + n += 2; + } + n++; + } + + p = pattern = xtrymalloc (n+1); + if (!pattern) + return NULL; + + for (n=0, sl=names; sl; sl = sl->next) + { + for (s=sl->d; *s; s++) + { + switch (*s) + { + case '%': + *p++ = '%'; + *p++ = '2'; + *p++ = '5'; + break; + case ' ': + *p++ = '%'; + *p++ = '2'; + *p++ = '0'; + break; + case '+': + *p++ = '%'; + *p++ = '2'; + *p++ = 'B'; + break; + default: + *p++ = *s; + break; + } + } + *p++ = ' '; + } + if (p == pattern) + *pattern = 0; /* is empty */ + else + p[-1] = '\0'; /* remove trailing blank */ + + return pattern; +} + +static AssuanError +lookup_status_cb (void *opaque, const char *line) +{ + struct lookup_parm_s *parm = opaque; + + if (!strncmp (line, "TRUNCATED", 9) && (line[9]==' ' || !line[9])) + { + if (parm->ctrl) + { + for (line +=9; *line == ' '; line++) + ; + gpgsm_status (parm->ctrl, STATUS_TRUNCATED, line); + } + } + return 0; +} + + +/* Run the Directroy Managers lookup command using the pattern + compiled from the strings given in NAMES. The caller must provide + the callback CB which will be passed cert by cert. Note that CTRL + is optional. */ +int +gpgsm_dirmngr_lookup (CTRL ctrl, STRLIST names, + void (*cb)(void*, KsbaCert), void *cb_value) +{ + int rc; + char *pattern; + char line[ASSUAN_LINELENGTH]; + struct lookup_parm_s parm; + size_t len; + + rc = start_dirmngr (); + if (rc) + return rc; + + pattern = pattern_from_strlist (names); + if (!pattern) + return OUT_OF_CORE (errno); + snprintf (line, DIM(line)-1, "LOOKUP %s", pattern); + line[DIM(line)-1] = 0; + xfree (pattern); + + parm.ctrl = ctrl; + parm.ctx = dirmngr_ctx; + parm.cb = cb; + parm.cb_value = cb_value; + parm.error = 0; + init_membuf (&parm.data, 4096); + + rc = assuan_transact (dirmngr_ctx, line, lookup_cb, &parm, + NULL, NULL, lookup_status_cb, &parm); + xfree (get_membuf (&parm.data, &len)); + if (rc) + return map_assuan_err (rc); + return parm.error; +} + + + +/* Run Command helpers*/ + +/* Fairly simple callback to write all output of dirmngr to stdout. */ +static AssuanError +run_command_cb (void *opaque, const void *buffer, size_t length) +{ + if (buffer) + { + if ( fwrite (buffer, length, 1, stdout) != 1 ) + log_error ("error writing to stdout: %s\n", strerror (errno)); + } + return 0; +} + +/* Handle inquiries from the dirmngr COMMAND. */ +static AssuanError +run_command_inq_cb (void *opaque, const char *line) +{ + struct run_command_parm_s *parm = opaque; + AssuanError rc = 0; + + if ( !strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8]) ) + { /* send the given certificate */ + int err; + KsbaCert cert; + const unsigned char *der; + size_t derlen; + + line += 8; + if (!*line) + return ASSUAN_Inquire_Error; + + err = gpgsm_find_cert (line, &cert); + if (err) + { + log_error ("certificate not found: %s\n", gpg_strerror (err)); + rc = ASSUAN_Inquire_Error; + } + else + { + der = ksba_cert_get_image (cert, &derlen); + if (!der) + rc = ASSUAN_Inquire_Error; + else + rc = assuan_send_data (parm->ctx, der, derlen); + ksba_cert_release (cert); + } + } + else if ( !strncmp (line, "PRINTINFO", 9) && (line[9] == ' ' || !line[9]) ) + { /* Simply show the message given in the argument. */ + line += 9; + log_info ("dirmngr: %s\n", line); + } + else + { + log_error ("unsupported inquiry `%s'\n", line); + rc = ASSUAN_Inquire_Unknown; + } + + return rc; +} + +static AssuanError +run_command_status_cb (void *opaque, const char *line) +{ + if (opt.verbose) + { + log_info ("dirmngr status: %s\n", line); + } + return 0; +} + + + +/* Pass COMMAND to dirmngr and print all output generated by Dirmngr + to stdout. A couple of inquiries are defined (see above). ARGC + arguments in ARGV are given to the Dirmngr. Spaces, plus and + percent characters within the argument strings are percent escaped + so that blanks can act as delimiters. */ +int +gpgsm_dirmngr_run_command (CTRL ctrl, const char *command, + int argc, char **argv) +{ + int rc; + int i; + const char *s; + char *line, *p; + size_t len; + struct run_command_parm_s parm; + + rc = start_dirmngr (); + if (rc) + return rc; + + parm.ctx = dirmngr_ctx; + + len = strlen (command) + 1; + for (i=0; i < argc; i++) + len += 1 + 3*strlen (argv[i]); /* enough space for percent escaping */ + line = xtrymalloc (len); + if (!line) + return OUT_OF_CORE (errno); + + p = stpcpy (line, command); + for (i=0; i < argc; i++) + { + *p++ = ' '; + for (s=argv[i]; *s; s++) + { + if (!isascii (*s)) + *p++ = *s; + else if (*s == ' ') + *p++ = '+'; + else if (!isprint (*s) || *s == '+') + { + sprintf (p, "%%%02X", *s); + p += 3; + } + else + *p++ = *s; + } + } + *p = 0; + + rc = assuan_transact (dirmngr_ctx, line, + run_command_cb, NULL, + run_command_inq_cb, &parm, + run_command_status_cb, NULL); + xfree (line); + log_info ("response of dirmngr: %s\n", rc? assuan_strerror (rc): "okay"); + return map_assuan_err (rc); +} diff --git a/sm/certchain.c b/sm/certchain.c new file mode 100644 index 000000000..6323c725e --- /dev/null +++ b/sm/certchain.c @@ -0,0 +1,793 @@ +/* certchain.c - certificate chain validation + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpgsm.h" +#include +#include + +#include "keydb.h" +#include "i18n.h" + +static int +unknown_criticals (KsbaCert cert) +{ + static const char *known[] = { + "2.5.29.15", /* keyUsage */ + "2.5.29.19", /* basic Constraints */ + "2.5.29.32", /* certificatePolicies */ + NULL + }; + int rc = 0, i, idx, crit; + const char *oid; + KsbaError err; + + for (idx=0; !(err=ksba_cert_get_extension (cert, idx, + &oid, &crit, NULL, NULL));idx++) + { + if (!crit) + continue; + for (i=0; known[i] && strcmp (known[i],oid); i++) + ; + if (!known[i]) + { + log_error (_("critical certificate extension %s is not supported\n"), + oid); + rc = gpg_error (GPG_ERR_UNSUPPORTED_CERT); + } + } + if (err && err != -1) + rc = map_ksba_err (err); + + return rc; +} + +static int +allowed_ca (KsbaCert cert, int *chainlen) +{ + KsbaError err; + int flag; + + err = ksba_cert_is_ca (cert, &flag, chainlen); + if (err) + return map_ksba_err (err); + if (!flag) + { + log_error (_("issuer certificate is not marked as a CA\n")); + return gpg_error (GPG_ERR_BAD_CA_CERT); + } + return 0; +} + + +static int +check_cert_policy (KsbaCert cert) +{ + KsbaError err; + char *policies; + FILE *fp; + int any_critical; + + err = ksba_cert_get_cert_policies (cert, &policies); + if (err == KSBA_No_Data) + return 0; /* no policy given */ + if (err) + return map_ksba_err (err); + + /* STRING is a line delimited list of certifiate policies as stored + in the certificate. The line itself is colon delimited where the + first field is the OID of the policy and the second field either + N or C for normal or critical extension */ + + if (opt.verbose > 1) + log_info ("certificate's policy list: %s\n", policies); + + /* The check is very minimal but won't give false positives */ + any_critical = !!strstr (policies, ":C"); + + if (!opt.policy_file) + { + xfree (policies); + if (any_critical) + { + log_error ("critical marked policy without configured policies\n"); + return gpg_error (GPG_ERR_NO_POLICY_MATCH); + } + return 0; + } + + fp = fopen (opt.policy_file, "r"); + if (!fp) + { + log_error ("failed to open `%s': %s\n", + opt.policy_file, strerror (errno)); + xfree (policies); + return gpg_error (GPG_ERR_NO_POLICY_MATCH); + } + + for (;;) + { + int c; + char *p, line[256]; + char *haystack, *allowed; + + /* read line */ + do + { + if (!fgets (line, DIM(line)-1, fp) ) + { + gpg_error_t tmperr; + + xfree (policies); + if (feof (fp)) + { + fclose (fp); + /* with no critical policies this is only a warning */ + if (!any_critical) + { + log_info (_("note: certificate policy not allowed\n")); + return 0; + } + log_error (_("certificate policy not allowed\n")); + return gpg_error (GPG_ERR_NO_POLICY_MATCH); + } + tmperr = gpg_error (gpg_err_code_from_errno (errno)); + fclose (fp); + return tmperr; + } + + if (!*line || line[strlen(line)-1] != '\n') + { + /* eat until end of line */ + while ( (c=getc (fp)) != EOF && c != '\n') + ; + fclose (fp); + xfree (policies); + return gpg_error (*line? GPG_ERR_LINE_TOO_LONG + : GPG_ERR_INCOMPLETE_LINE); + } + + /* Allow for empty lines and spaces */ + for (p=line; spacep (p); p++) + ; + } + while (!*p || *p == '\n' || *p == '#'); + + /* parse line */ + for (allowed=line; spacep (allowed); allowed++) + ; + p = strpbrk (allowed, " :\n"); + if (!*p || p == allowed) + { + fclose (fp); + xfree (policies); + return gpg_error (GPG_ERR_CONFIGURATION); + } + *p = 0; /* strip the rest of the line */ + /* See whether we find ALLOWED (which is an OID) in POLICIES */ + for (haystack=policies; (p=strstr (haystack, allowed)); haystack = p+1) + { + if ( !(p == policies || p[-1] == '\n') ) + continue; /* does not match the begin of a line */ + if (p[strlen (allowed)] != ':') + continue; /* the length does not match */ + /* Yep - it does match so return okay */ + fclose (fp); + xfree (policies); + return 0; + } + } +} + + +static void +find_up_store_certs_cb (void *cb_value, KsbaCert cert) +{ + if (keydb_store_cert (cert, 1, NULL)) + log_error ("error storing issuer certificate as ephemeral\n"); + ++*(int*)cb_value; +} + + +static int +find_up (KEYDB_HANDLE kh, KsbaCert cert, const char *issuer) +{ + KsbaName authid; + KsbaSexp authidno; + int rc = -1; + + if (!ksba_cert_get_auth_key_id (cert, NULL, &authid, &authidno)) + { + const char *s = ksba_name_enum (authid, 0); + if (s && *authidno) + { + rc = keydb_search_issuer_sn (kh, s, authidno); + if (rc) + keydb_search_reset (kh); + if (rc == -1) + { /* And try the ephemeral DB. */ + int old = keydb_set_ephemeral (kh, 1); + if (!old) + { + rc = keydb_search_issuer_sn (kh, s, authidno); + if (rc) + keydb_search_reset (kh); + } + keydb_set_ephemeral (kh, old); + } + } + /* print a note so that the user does not feel too helpless when + an issuer certificate was found and gpgsm prints BAD + signature becuase it is not the correct one. */ + if (rc == -1) + { + log_info ("issuer certificate (#"); + gpgsm_dump_serial (authidno); + log_printf ("/"); + gpgsm_dump_string (s); + log_printf (") not found\n"); + } + else if (rc) + log_error ("failed to find authorityKeyIdentifier: rc=%d\n", rc); + ksba_name_release (authid); + xfree (authidno); + /* Fixme: don't know how to do dirmngr lookup with serial+issuer. */ + } + + if (rc) /* not found via authorithyKeyIdentifier, try regular issuer name */ + rc = keydb_search_subject (kh, issuer); + if (rc == -1) + { + /* Not found, lets see whether we have one in the ephemeral key DB. */ + int old = keydb_set_ephemeral (kh, 1); + if (!old) + { + keydb_search_reset (kh); + rc = keydb_search_subject (kh, issuer); + } + keydb_set_ephemeral (kh, old); + } + + if (rc == -1 && opt.auto_issuer_key_retrieve) + { + STRLIST names = NULL; + int count = 0; + char *pattern; + const char *s; + + if (opt.verbose) + log_info (_("looking up issuer at external location\n")); + /* dirmngr is confused about unknown attributes so has a quick + and ugly hack we locate the CN and use this and the + following. Fixme: we should have far better parsing in the + dirmngr. */ + s = strstr (issuer, "CN="); + if (!s || s == issuer || s[-1] != ',') + s = issuer; + + pattern = xtrymalloc (strlen (s)+2); + if (!pattern) + return OUT_OF_CORE (errno); + strcpy (stpcpy (pattern, "/"), s); + add_to_strlist (&names, pattern); + xfree (pattern); + rc = gpgsm_dirmngr_lookup (NULL, names, find_up_store_certs_cb, &count); + free_strlist (names); + if (opt.verbose) + log_info (_("number of issuers matching: %d\n"), count); + if (rc) + { + log_error ("external key lookup failed: %s\n", gpg_strerror (rc)); + rc = -1; + } + else if (!count) + rc = -1; + else + { + int old; + /* The issuers are currently stored in the ephemeral key + DB, so we temporary switch to ephemeral mode. */ + old = keydb_set_ephemeral (kh, 1); + keydb_search_reset (kh); + rc = keydb_search_subject (kh, issuer); + keydb_set_ephemeral (kh, old); + } + } + return rc; +} + + +/* Return the next certificate up in the chain starting at START. + Returns -1 when there are no more certificates. */ +int +gpgsm_walk_cert_chain (KsbaCert start, KsbaCert *r_next) +{ + int rc = 0; + char *issuer = NULL; + char *subject = NULL; + KEYDB_HANDLE kh = keydb_new (0); + + *r_next = NULL; + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + issuer = ksba_cert_get_issuer (start, 0); + subject = ksba_cert_get_subject (start, 0); + if (!issuer) + { + log_error ("no issuer found in certificate\n"); + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + if (!subject) + { + log_error ("no subject found in certificate\n"); + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + + if (!strcmp (issuer, subject)) + { + rc = -1; /* we are at the root */ + goto leave; + } + + rc = find_up (kh, start, issuer); + if (rc) + { + /* it is quite common not to have a certificate, so better don't + print an error here */ + if (rc != -1 && opt.verbose > 1) + log_error ("failed to find issuer's certificate: rc=%d\n", rc); + rc = gpg_error (GPG_ERR_MISSING_CERT); + goto leave; + } + + rc = keydb_get_cert (kh, r_next); + if (rc) + { + log_error ("failed to get cert: rc=%d\n", rc); + rc = gpg_error (GPG_ERR_GENERAL); + } + + leave: + xfree (issuer); + xfree (subject); + keydb_release (kh); + return rc; +} + + +/* Check whether the CERT is a root certificate. Returns True if this + is the case. */ +int +gpgsm_is_root_cert (KsbaCert cert) +{ + char *issuer; + char *subject; + int yes; + + issuer = ksba_cert_get_issuer (cert, 0); + subject = ksba_cert_get_subject (cert, 0); + yes = (issuer && subject && !strcmp (issuer, subject)); + xfree (issuer); + xfree (subject); + return yes; +} + + +/* Validate a chain and optionally return the nearest expiration time + in R_EXPTIME */ +int +gpgsm_validate_chain (CTRL ctrl, KsbaCert cert, time_t *r_exptime) +{ + int rc = 0, depth = 0, maxdepth; + char *issuer = NULL; + char *subject = NULL; + KEYDB_HANDLE kh = keydb_new (0); + KsbaCert subject_cert = NULL, issuer_cert = NULL; + time_t current_time = gnupg_get_time (); + time_t exptime = 0; + int any_expired = 0; + int any_revoked = 0; + int any_no_crl = 0; + int any_crl_too_old = 0; + int any_no_policy_match = 0; + + if (r_exptime) + *r_exptime = 0; + + if (opt.no_chain_validation) + { + log_info ("WARNING: bypassing certificate chain validation\n"); + return 0; + } + + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + if (DBG_X509) + gpgsm_dump_cert ("subject", cert); + + subject_cert = cert; + maxdepth = 50; + + for (;;) + { + xfree (issuer); + xfree (subject); + issuer = ksba_cert_get_issuer (subject_cert, 0); + subject = ksba_cert_get_subject (subject_cert, 0); + + if (!issuer) + { + log_error ("no issuer found in certificate\n"); + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + + { + time_t not_before, not_after; + + not_before = ksba_cert_get_validity (subject_cert, 0); + not_after = ksba_cert_get_validity (subject_cert, 1); + if (not_before == (time_t)(-1) || not_after == (time_t)(-1)) + { + log_error ("certificate with invalid validity\n"); + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + + if (not_after) + { + if (!exptime) + exptime = not_after; + else if (not_after < exptime) + exptime = not_after; + } + + if (not_before && current_time < not_before) + { + log_error ("certificate too young; valid from "); + gpgsm_dump_time (not_before); + log_printf ("\n"); + rc = gpg_error (GPG_ERR_CERT_TOO_YOUNG); + goto leave; + } + if (not_after && current_time > not_after) + { + log_error ("certificate has expired at "); + gpgsm_dump_time (not_after); + log_printf ("\n"); + any_expired = 1; + } + } + + rc = unknown_criticals (subject_cert); + if (rc) + goto leave; + + if (!opt.no_policy_check) + { + rc = check_cert_policy (subject_cert); + if (gpg_err_code (rc) == GPG_ERR_NO_POLICY_MATCH) + { + any_no_policy_match = 1; + rc = 1; + } + else if (rc) + goto leave; + } + + if (!opt.no_crl_check) + { + rc = gpgsm_dirmngr_isvalid (subject_cert); + if (rc) + { + switch (rc) + { + case GPG_ERR_CERT_REVOKED: + log_error (_("the certificate has been revoked\n")); + any_revoked = 1; + break; + case GPG_ERR_NO_CRL_KNOWN: + log_error (_("no CRL found for certificate\n")); + any_no_crl = 1; + break; + case GPG_ERR_CRL_TOO_OLD: + log_error (_("the available CRL is too old\n")); + log_info (_("please make sure that the " + "\"dirmngr\" is properly installed\n")); + any_crl_too_old = 1; + break; + default: + log_error (_("checking the CRL failed: %s\n"), + gpg_strerror (rc)); + goto leave; + } + rc = 0; + } + } + + if (subject && !strcmp (issuer, subject)) + { + if (gpgsm_check_cert_sig (subject_cert, subject_cert) ) + { + log_error ("selfsigned certificate has a BAD signatures\n"); + rc = gpg_error (depth? GPG_ERR_BAD_CERT_CHAIN + : GPG_ERR_BAD_CERT); + goto leave; + } + rc = allowed_ca (subject_cert, NULL); + if (rc) + goto leave; + + rc = gpgsm_agent_istrusted (subject_cert); + if (!rc) + ; + else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED) + { + int rc2; + + char *fpr = gpgsm_get_fingerprint_string (subject_cert, + GCRY_MD_SHA1); + log_info (_("root certificate is not marked trusted\n")); + log_info (_("fingerprint=%s\n"), fpr? fpr : "?"); + xfree (fpr); + rc2 = gpgsm_agent_marktrusted (subject_cert); + if (!rc2) + { + log_info (_("root certificate has now" + " been marked as trusted\n")); + rc = 0; + } + else + { + gpgsm_dump_cert ("issuer", subject_cert); + log_info ("after checking the fingerprint, you may want " + "to enter it manually into " + "\"~/.gnupg-test/trustlist.txt\"\n"); + } + } + else + { + log_error (_("checking the trust list failed: %s\n"), + gpg_strerror (rc)); + } + + break; /* okay, a self-signed certicate is an end-point */ + } + + depth++; + if (depth > maxdepth) + { + log_error (_("certificate chain too long\n")); + rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN); + goto leave; + } + + /* find the next cert up the tree */ + keydb_search_reset (kh); + rc = find_up (kh, subject_cert, issuer); + if (rc) + { + if (rc == -1) + { + log_info ("issuer certificate (#/"); + gpgsm_dump_string (issuer); + log_printf (") not found\n"); + } + else + log_error ("failed to find issuer's certificate: rc=%d\n", rc); + rc = gpg_error (GPG_ERR_MISSING_CERT); + goto leave; + } + + ksba_cert_release (issuer_cert); issuer_cert = NULL; + rc = keydb_get_cert (kh, &issuer_cert); + if (rc) + { + log_error ("failed to get cert: rc=%d\n", rc); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + if (DBG_X509) + { + log_debug ("got issuer's certificate:\n"); + gpgsm_dump_cert ("issuer", issuer_cert); + } + + if (gpgsm_check_cert_sig (issuer_cert, subject_cert) ) + { + log_error ("certificate has a BAD signatures\n"); + rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN); + goto leave; + } + + { + int chainlen; + rc = allowed_ca (issuer_cert, &chainlen); + if (rc) + goto leave; + if (chainlen >= 0 && (depth - 1) > chainlen) + { + log_error (_("certificate chain longer than allowed by CA (%d)\n"), + chainlen); + rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN); + goto leave; + } + } + + rc = gpgsm_cert_use_cert_p (issuer_cert); + if (rc) + { + char numbuf[50]; + sprintf (numbuf, "%d", rc); + gpgsm_status2 (ctrl, STATUS_ERROR, "certcert.issuer.keyusage", + numbuf, NULL); + rc = 0; + } + + if (opt.verbose) + log_info ("certificate is good\n"); + + keydb_search_reset (kh); + subject_cert = issuer_cert; + issuer_cert = NULL; + } + + if (opt.no_policy_check) + log_info ("policies not checked due to --disable-policy-checks option\n"); + if (opt.no_crl_check) + log_info ("CRLs not checked due to --disable-crl-checks option\n"); + + if (!rc) + { /* If we encountered an error somewhere during the checks, set + the error code to the most critical one */ + if (any_revoked) + rc = gpg_error (GPG_ERR_CERT_REVOKED); + else if (any_no_crl) + rc = gpg_error (GPG_ERR_NO_CRL_KNOWN); + else if (any_crl_too_old) + rc = gpg_error (GPG_ERR_CRL_TOO_OLD); + else if (any_no_policy_match) + rc = gpg_error (GPG_ERR_NO_POLICY_MATCH); + else if (any_expired) + rc = gpg_error (GPG_ERR_CERT_EXPIRED); + } + + leave: + if (r_exptime) + *r_exptime = exptime; + xfree (issuer); + keydb_release (kh); + ksba_cert_release (issuer_cert); + if (subject_cert != cert) + ksba_cert_release (subject_cert); + return rc; +} + + +/* Check that the given certificate is valid but DO NOT check any + constraints. We assume that the issuers certificate is already in + the DB and that this one is valid; which it should be because it + has been checked using this function. */ +int +gpgsm_basic_cert_check (KsbaCert cert) +{ + int rc = 0; + char *issuer = NULL; + char *subject = NULL; + KEYDB_HANDLE kh = keydb_new (0); + KsbaCert issuer_cert = NULL; + + if (opt.no_chain_validation) + { + log_info ("WARNING: bypassing basic certificate checks\n"); + return 0; + } + + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + issuer = ksba_cert_get_issuer (cert, 0); + subject = ksba_cert_get_subject (cert, 0); + if (!issuer) + { + log_error ("no issuer found in certificate\n"); + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + + if (subject && !strcmp (issuer, subject)) + { + if (gpgsm_check_cert_sig (cert, cert) ) + { + log_error ("selfsigned certificate has a BAD signatures\n"); + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + } + else + { + /* find the next cert up the tree */ + keydb_search_reset (kh); + rc = find_up (kh, cert, issuer); + if (rc) + { + if (rc == -1) + { + log_info ("issuer certificate (#/"); + gpgsm_dump_string (issuer); + log_printf (") not found\n"); + } + else + log_error ("failed to find issuer's certificate: rc=%d\n", rc); + rc = gpg_error (GPG_ERR_MISSING_CERT); + goto leave; + } + + ksba_cert_release (issuer_cert); issuer_cert = NULL; + rc = keydb_get_cert (kh, &issuer_cert); + if (rc) + { + log_error ("failed to get cert: rc=%d\n", rc); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + if (gpgsm_check_cert_sig (issuer_cert, cert) ) + { + log_error ("certificate has a BAD signatures\n"); + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + if (opt.verbose) + log_info ("certificate is good\n"); + } + + leave: + xfree (issuer); + keydb_release (kh); + ksba_cert_release (issuer_cert); + return rc; +} + diff --git a/sm/certcheck.c b/sm/certcheck.c new file mode 100644 index 000000000..35509c67e --- /dev/null +++ b/sm/certcheck.c @@ -0,0 +1,300 @@ +/* certcheck.c - check one certificate + * Copyright (C) 2001, 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpgsm.h" +#include +#include + +#include "keydb.h" +#include "i18n.h" + + +static int +do_encode_md (gcry_md_hd_t md, int algo, unsigned int nbits, + gcry_mpi_t *r_val) +{ + int nframe = (nbits+7) / 8; + byte *frame; + int i, n; + byte asn[100]; + size_t asnlen; + size_t len; + + asnlen = DIM(asn); + if (gcry_md_algo_info (algo, GCRYCTL_GET_ASNOID, asn, &asnlen)) + { + log_error ("No object identifier for algo %d\n", algo); + return gpg_error (GPG_ERR_INTERNAL); + } + + len = gcry_md_get_algo_dlen (algo); + + if ( len + asnlen + 4 > nframe ) + { + log_error ("can't encode a %d bit MD into a %d bits frame\n", + (int)(len*8), (int)nbits); + return gpg_error (GPG_ERR_INTERNAL); + } + + /* We encode the MD in this way: + * + * 0 A PAD(n bytes) 0 ASN(asnlen bytes) MD(len bytes) + * + * PAD consists of FF bytes. + */ + frame = xtrymalloc (nframe); + if (!frame) + return OUT_OF_CORE (errno); + n = 0; + frame[n++] = 0; + frame[n++] = 1; /* block type */ + i = nframe - len - asnlen -3 ; + assert ( i > 1 ); + memset ( frame+n, 0xff, i ); n += i; + frame[n++] = 0; + memcpy ( frame+n, asn, asnlen ); n += asnlen; + memcpy ( frame+n, gcry_md_read(md, algo), len ); n += len; + assert ( n == nframe ); + if (DBG_X509) + { + int j; + log_debug ("encoded hash:"); + for (j=0; j < nframe; j++) + log_printf (" %02X", frame[j]); + log_printf ("\n"); + } + + gcry_mpi_scan (r_val, GCRYMPI_FMT_USG, frame, n, &nframe); + xfree (frame); + return 0; +} + + +/* + Check the signature on CERT using the ISSUER-CERT. This function + does only test the cryptographic signature and nothing else. It is + assumed that the ISSUER_CERT is valid. */ +int +gpgsm_check_cert_sig (KsbaCert issuer_cert, KsbaCert cert) +{ + const char *algoid; + gcry_md_hd_t md; + int rc, algo; + gcry_mpi_t frame; + KsbaSexp p; + size_t n; + gcry_sexp_t s_sig, s_hash, s_pkey; + + algo = gcry_md_map_name ( (algoid=ksba_cert_get_digest_algo (cert))); + if (!algo) + { + log_error ("unknown hash algorithm `%s'\n", algoid? algoid:"?"); + return gpg_error (GPG_ERR_GENERAL); + } + rc = gcry_md_open (&md, algo, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + return rc; + } + if (DBG_HASHING) + gcry_md_start_debug (md, "hash.cert"); + + rc = ksba_cert_hash (cert, 1, HASH_FNC, md); + if (rc) + { + log_error ("ksba_cert_hash failed: %s\n", ksba_strerror (rc)); + gcry_md_close (md); + return map_ksba_err (rc); + } + gcry_md_final (md); + + p = ksba_cert_get_sig_val (cert); + n = gcry_sexp_canon_len (p, 0, NULL, NULL); + if (!n) + { + log_error ("libksba did not return a proper S-Exp\n"); + gcry_md_close (md); + ksba_free (p); + return gpg_error (GPG_ERR_BUG); + } + if (DBG_X509) + { + int j; + log_debug ("signature value:"); + for (j=0; j < n; j++) + log_printf (" %02X", p[j]); + log_printf ("\n"); + } + + rc = gcry_sexp_sscan ( &s_sig, NULL, p, n); + ksba_free (p); + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + gcry_md_close (md); + return rc; + } + + p = ksba_cert_get_public_key (issuer_cert); + n = gcry_sexp_canon_len (p, 0, NULL, NULL); + if (!n) + { + log_error ("libksba did not return a proper S-Exp\n"); + gcry_md_close (md); + ksba_free (p); + gcry_sexp_release (s_sig); + return gpg_error (GPG_ERR_BUG); + } + rc = gcry_sexp_sscan ( &s_pkey, NULL, p, n); + ksba_free (p); + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + gcry_md_close (md); + gcry_sexp_release (s_sig); + return rc; + } + + rc = do_encode_md (md, algo, gcry_pk_get_nbits (s_pkey), &frame); + if (rc) + { + gcry_md_close (md); + gcry_sexp_release (s_sig); + gcry_sexp_release (s_pkey); + return rc; + } + + /* put hash into the S-Exp s_hash */ + if ( gcry_sexp_build (&s_hash, NULL, "%m", frame) ) + BUG (); + gcry_mpi_release (frame); + + + rc = gcry_pk_verify (s_sig, s_hash, s_pkey); + if (DBG_CRYPTO) + log_debug ("gcry_pk_verify: %s\n", gpg_strerror (rc)); + gcry_md_close (md); + gcry_sexp_release (s_sig); + gcry_sexp_release (s_hash); + gcry_sexp_release (s_pkey); + return rc; +} + + + +int +gpgsm_check_cms_signature (KsbaCert cert, KsbaConstSexp sigval, + gcry_md_hd_t md, int algo) +{ + int rc; + KsbaSexp p; + gcry_mpi_t frame; + gcry_sexp_t s_sig, s_hash, s_pkey; + size_t n; + + n = gcry_sexp_canon_len (sigval, 0, NULL, NULL); + if (!n) + { + log_error ("libksba did not return a proper S-Exp\n"); + return gpg_error (GPG_ERR_BUG); + } + rc = gcry_sexp_sscan (&s_sig, NULL, sigval, n); + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + return rc; + } + + p = ksba_cert_get_public_key (cert); + n = gcry_sexp_canon_len (p, 0, NULL, NULL); + if (!n) + { + log_error ("libksba did not return a proper S-Exp\n"); + ksba_free (p); + gcry_sexp_release (s_sig); + return gpg_error (GPG_ERR_BUG); + } + if (DBG_X509) + log_printhex ("public key: ", p, n); + + rc = gcry_sexp_sscan ( &s_pkey, NULL, p, n); + ksba_free (p); + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + gcry_sexp_release (s_sig); + return rc; + } + + + rc = do_encode_md (md, algo, gcry_pk_get_nbits (s_pkey), &frame); + if (rc) + { + gcry_sexp_release (s_sig); + gcry_sexp_release (s_pkey); + return rc; + } + /* put hash into the S-Exp s_hash */ + if ( gcry_sexp_build (&s_hash, NULL, "%m", frame) ) + BUG (); + gcry_mpi_release (frame); + + rc = gcry_pk_verify (s_sig, s_hash, s_pkey); + if (DBG_CRYPTO) + log_debug ("gcry_pk_verify: %s\n", gpg_strerror (rc)); + gcry_sexp_release (s_sig); + gcry_sexp_release (s_hash); + gcry_sexp_release (s_pkey); + return rc; +} + + + +int +gpgsm_create_cms_signature (KsbaCert cert, gcry_md_hd_t md, int mdalgo, + char **r_sigval) +{ + int rc; + char *grip; + size_t siglen; + + grip = gpgsm_get_keygrip_hexstring (cert); + if (!grip) + return gpg_error (GPG_ERR_BAD_CERT); + + rc = gpgsm_agent_pksign (grip, gcry_md_read(md, mdalgo), + gcry_md_get_algo_dlen (mdalgo), mdalgo, + r_sigval, &siglen); + xfree (grip); + return rc; +} + + + diff --git a/sm/certdump.c b/sm/certdump.c new file mode 100644 index 000000000..703e07186 --- /dev/null +++ b/sm/certdump.c @@ -0,0 +1,457 @@ +/* certdump.c - Dump a certificate for debugging + * Copyright (C) 2001 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpgsm.h" +#include +#include + +#include "keydb.h" +#include "i18n.h" + +struct dn_array_s { + char *key; + char *value; +}; + + +/* print the first element of an S-Expression */ +void +gpgsm_print_serial (FILE *fp, KsbaConstSexp p) +{ + unsigned long n; + KsbaConstSexp endp; + + if (!p) + fputs (_("none"), fp); + else if (*p != '(') + fputs ("[Internal error - not an S-expression]", fp); + else + { + p++; + n = strtoul (p, (char**)&endp, 10); + p = endp; + if (*p!=':') + fputs ("[Internal Error - invalid S-expression]", fp); + else + { + for (p++; n; n--, p++) + fprintf (fp, "%02X", *p); + } + } +} + + +void +gpgsm_dump_serial (KsbaConstSexp p) +{ + unsigned long n; + KsbaConstSexp endp; + + if (!p) + log_printf ("none"); + else if (*p != '(') + log_printf ("ERROR - not an S-expression"); + else + { + p++; + n = strtoul (p, (char**)&endp, 10); + p = endp; + if (*p!=':') + log_printf ("ERROR - invalid S-expression"); + else + { + for (p++; n; n--, p++) + log_printf ("%02X", *p); + } + } +} + +void +gpgsm_print_time (FILE *fp, time_t t) +{ + if (!t) + fputs (_("none"), fp); + else if ( t == (time_t)(-1) ) + fputs ("[Error - Invalid time]", fp); + else + { + struct tm *tp; + + tp = gmtime (&t); + fprintf (fp, "%04d-%02d-%02d %02d:%02d:%02d Z", + 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, + tp->tm_hour, tp->tm_min, tp->tm_sec); + assert (!tp->tm_isdst); + } +} + +void +gpgsm_dump_time (time_t t) +{ + + if (!t) + log_printf (_("[none]")); + else if ( t == (time_t)(-1) ) + log_printf (_("[error]")); + else + { + struct tm *tp; + + tp = gmtime (&t); + log_printf ("%04d-%02d-%02d %02d:%02d:%02d", + 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, + tp->tm_hour, tp->tm_min, tp->tm_sec); + assert (!tp->tm_isdst); + } +} + + + + +void +gpgsm_dump_string (const char *string) +{ + + if (!string) + log_printf ("[error]"); + else + { + const unsigned char *s; + + for (s=string; *s; s++) + { + if (*s < ' ' || (*s >= 0x7f && *s <= 0xa0)) + break; + } + if (!*s && *string != '[') + log_printf ("%s", string); + else + { + log_printf ( "[ "); + log_printhex (NULL, string, strlen (string)); + log_printf ( " ]"); + } + } +} + + +void +gpgsm_dump_cert (const char *text, KsbaCert cert) +{ + KsbaSexp sexp; + unsigned char *p; + char *dn; + time_t t; + + log_debug ("BEGIN Certificate `%s':\n", text? text:""); + if (cert) + { + sexp = ksba_cert_get_serial (cert); + log_debug (" serial: "); + gpgsm_dump_serial (sexp); + ksba_free (sexp); + log_printf ("\n"); + + t = ksba_cert_get_validity (cert, 0); + log_debug (" notBefore: "); + gpgsm_dump_time (t); + log_printf ("\n"); + t = ksba_cert_get_validity (cert, 1); + log_debug (" notAfter: "); + gpgsm_dump_time (t); + log_printf ("\n"); + + dn = ksba_cert_get_issuer (cert, 0); + log_debug (" issuer: "); + gpgsm_dump_string (dn); + ksba_free (dn); + log_printf ("\n"); + + dn = ksba_cert_get_subject (cert, 0); + log_debug (" subject: "); + gpgsm_dump_string (dn); + ksba_free (dn); + log_printf ("\n"); + + log_debug (" hash algo: %s\n", ksba_cert_get_digest_algo (cert)); + + p = gpgsm_get_fingerprint_string (cert, 0); + log_debug (" SHA1 Fingerprint: %s\n", p); + xfree (p); + } + log_debug ("END Certificate\n"); +} + + + +/* helper for the rfc2253 string parser */ +static const unsigned char * +parse_dn_part (struct dn_array_s *array, const unsigned char *string) +{ + const unsigned char *s, *s1; + size_t n; + unsigned char *p; + + /* parse attributeType */ + for (s = string+1; *s && *s != '='; s++) + ; + if (!*s) + return NULL; /* error */ + n = s - string; + if (!n) + return NULL; /* empty key */ + array->key = p = xtrymalloc (n+1); + if (!array->key) + return NULL; + memcpy (p, string, n); + p[n] = 0; + trim_trailing_spaces (p); + if ( !strcmp (p, "1.2.840.113549.1.9.1") ) + strcpy (p, "EMail"); + string = s + 1; + + if (*string == '#') + { /* hexstring */ + string++; + for (s=string; hexdigitp (s); s++) + s++; + n = s - string; + if (!n || (n & 1)) + return NULL; /* empty or odd number of digits */ + n /= 2; + array->value = p = xtrymalloc (n+1); + if (!p) + return NULL; + for (s1=string; n; s1 += 2, n--) + *p++ = xtoi_2 (s1); + *p = 0; + } + else + { /* regular v3 quoted string */ + for (n=0, s=string; *s; s++) + { + if (*s == '\\') + { /* pair */ + s++; + if (*s == ',' || *s == '=' || *s == '+' + || *s == '<' || *s == '>' || *s == '#' || *s == ';' + || *s == '\\' || *s == '\"' || *s == ' ') + n++; + else if (hexdigitp (s) && hexdigitp (s+1)) + { + s++; + n++; + } + else + return NULL; /* invalid escape sequence */ + } + else if (*s == '\"') + return NULL; /* invalid encoding */ + else if (*s == ',' || *s == '=' || *s == '+' + || *s == '<' || *s == '>' || *s == '#' || *s == ';' ) + break; + else + n++; + } + + array->value = p = xtrymalloc (n+1); + if (!p) + return NULL; + for (s=string; n; s++, n--) + { + if (*s == '\\') + { + s++; + if (hexdigitp (s)) + { + *p++ = xtoi_2 (s); + s++; + } + else + *p++ = *s; + } + else + *p++ = *s; + } + *p = 0; + } + return s; +} + + +/* Parse a DN and return an array-ized one. This is not a validating + parser and it does not support any old-stylish syntax; KSBA is + expected to return only rfc2253 compatible strings. */ +static struct dn_array_s * +parse_dn (const unsigned char *string) +{ + struct dn_array_s *array; + size_t arrayidx, arraysize; + int i; + + arraysize = 7; /* C,ST,L,O,OU,CN,email */ + arrayidx = 0; + array = xtrymalloc ((arraysize+1) * sizeof *array); + if (!array) + return NULL; + while (*string) + { + while (*string == ' ') + string++; + if (!*string) + break; /* ready */ + if (arrayidx >= arraysize) + { + struct dn_array_s *a2; + + arraysize += 5; + a2 = xtryrealloc (array, (arraysize+1) * sizeof *array); + if (!a2) + goto failure; + array = a2; + } + array[arrayidx].key = NULL; + array[arrayidx].value = NULL; + string = parse_dn_part (array+arrayidx, string); + arrayidx++; + if (!string) + goto failure; + while (*string == ' ') + string++; + if (*string && *string != ',' && *string != ';' && *string != '+') + goto failure; /* invalid delimiter */ + if (*string) + string++; + } + array[arrayidx].key = NULL; + array[arrayidx].value = NULL; + return array; + + failure: + for (i=0; i < arrayidx; i++) + { + xfree (array[i].key); + xfree (array[i].value); + } + xfree (array); + return NULL; +} + + +static void +print_dn_part (FILE *fp, struct dn_array_s *dn, const char *key) +{ + int any = 0; + + for (; dn->key; dn++) + { + if (!strcmp (dn->key, key) && dn->value && *dn->value) + { + putc ('/', fp); + if (any) + fputs (" + ", fp); + else + fprintf (fp, "%s=", key); + print_sanitized_utf8_string (fp, dn->value, '/'); + any = 1; + } + } +} + +/* Print all parts of a DN in a "standard" sequence. We first print + all the known parts, followed by the uncommon ones */ +static void +print_dn_parts (FILE *fp, struct dn_array_s *dn) +{ + const char *stdpart[] = { + "CN", "OU", "O", "STREET", "L", "ST", "C", "EMail", NULL + }; + int i; + + for (i=0; stdpart[i]; i++) + print_dn_part (fp, dn, stdpart[i]); + + /* now print the rest without any specific ordering */ + for (; dn->key; dn++) + { + for (i=0; stdpart[i]; i++) + { + if (!strcmp (dn->key, stdpart[i])) + break; + } + if (!stdpart[i]) + print_dn_part (fp, dn, dn->key); + } +} + + + +void +gpgsm_print_name (FILE *fp, const char *name) +{ + const unsigned char *s; + int i; + + s = name; + if (!s) + { + fputs (_("[Error - No name]"), fp); + } + else if (*s == '<') + { + const unsigned char *s2 = strchr (s+1, '>'); + if (s2) + print_sanitized_utf8_buffer (fp, s + 1, s2 - s - 1, 0); + } + else if (*s == '(') + fputs (_("[Error - unknown encoding]"), fp); + else if (!((*s >= '0' && *s < '9') + || (*s >= 'A' && *s <= 'Z') + || (*s >= 'a' && *s <= 'z'))) + fputs (_("[Error - invalid encoding]"), fp); + else + { + struct dn_array_s *dn = parse_dn (s); + if (!dn) + fputs (_("[Error - invalid DN]"), fp); + else + { + print_dn_parts (fp, dn); + for (i=0; dn[i].key; i++) + { + xfree (dn[i].key); + xfree (dn[i].value); + } + xfree (dn); + } + } +} + + + diff --git a/sm/certlist.c b/sm/certlist.c new file mode 100644 index 000000000..eedc99025 --- /dev/null +++ b/sm/certlist.c @@ -0,0 +1,315 @@ +/* certlist.c - build list of certificates + * Copyright (C) 2001, 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpgsm.h" +#include +#include + +#include "keydb.h" +#include "i18n.h" + +/* Return 0 if the cert is usable for encryption. A MODE of 0 checks + for signing a MODE of 1 checks for encryption, a MODE of 2 checks + for verification and a MODE of 3 for decryption (just for + debugging) */ +static int +cert_usage_p (KsbaCert cert, int mode) +{ + KsbaError err; + unsigned int use; + + err = ksba_cert_get_key_usage (cert, &use); + if (err == KSBA_No_Data) + { + if (opt.verbose && mode < 2) + log_info (mode? + _("no key usage specified - accepted for encryption\n"): + _("no key usage specified - accepted for signing\n")); + return 0; + } + if (err) + { + log_error (_("error getting key usage information: %s\n"), + ksba_strerror (err)); + return map_ksba_err (err); + } + + if (mode == 4) + { + if ((use & (KSBA_KEYUSAGE_KEY_CERT_SIGN))) + return 0; + log_info ( _("certificate should have not been used certification\n")); + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); + } + + if ((use & ((mode&1)? + (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT): + (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION))) + ) + return 0; + log_info (mode==3? _("certificate should have not been used for encryption\n"): + mode==2? _("certificate should have not been used for signing\n"): + mode==1? _("certificate is not usable for encryption\n"): + _("certificate is not usable for signing\n")); + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); +} + + +/* Return 0 if the cert is usable for signing */ +int +gpgsm_cert_use_sign_p (KsbaCert cert) +{ + return cert_usage_p (cert, 0); +} + + +/* Return 0 if the cert is usable for encryption */ +int +gpgsm_cert_use_encrypt_p (KsbaCert cert) +{ + return cert_usage_p (cert, 1); +} + +int +gpgsm_cert_use_verify_p (KsbaCert cert) +{ + return cert_usage_p (cert, 2); +} + +int +gpgsm_cert_use_decrypt_p (KsbaCert cert) +{ + return cert_usage_p (cert, 3); +} + +int +gpgsm_cert_use_cert_p (KsbaCert cert) +{ + return cert_usage_p (cert, 4); +} + + +static int +same_subject_issuer (const char *subject, const char *issuer, KsbaCert cert) +{ + char *subject2 = ksba_cert_get_subject (cert, 0); + char *issuer2 = ksba_cert_get_subject (cert, 0); + int tmp; + + tmp = (subject && subject2 + && !strcmp (subject, subject2) + && issuer && issuer2 + && !strcmp (issuer, issuer2)); + xfree (subject2); + xfree (issuer2); + return tmp; +} + + + +/* Add a certificate to a list of certificate and make sure that it is + a valid certificate. With SECRET set to true a secret key must be + avaibale for the certificate. */ +int +gpgsm_add_to_certlist (CTRL ctrl, const char *name, int secret, + CERTLIST *listaddr) +{ + int rc; + KEYDB_SEARCH_DESC desc; + KEYDB_HANDLE kh = NULL; + KsbaCert cert = NULL; + + rc = keydb_classify_name (name, &desc); + if (!rc) + { + kh = keydb_new (0); + if (!kh) + rc = gpg_error (GPG_ERR_ENOMEM); + else + { + int wrong_usage = 0; + char *subject = NULL; + char *issuer = NULL; + + get_next: + rc = keydb_search (kh, &desc, 1); + if (!rc) + rc = keydb_get_cert (kh, &cert); + if (!rc) + { + rc = secret? gpgsm_cert_use_sign_p (cert) + : gpgsm_cert_use_encrypt_p (cert); + if (gpg_err_code (rc) == GPG_ERR_WRONG_KEY_USAGE) + { + /* There might be another certificate with the + correct usage, so we try again */ + if (!wrong_usage) + { /* save the first match */ + wrong_usage = rc; + subject = ksba_cert_get_subject (cert, 0); + issuer = ksba_cert_get_subject (cert, 0); + ksba_cert_release (cert); + cert = NULL; + goto get_next; + } + else if (same_subject_issuer (subject, issuer, cert)) + { + wrong_usage = rc; + ksba_cert_release (cert); + cert = NULL; + goto get_next; + } + else + wrong_usage = rc; + + } + } + /* we want the error code from the first match in this case */ + if (rc && wrong_usage) + rc = wrong_usage; + + if (!rc) + { + next_ambigious: + rc = keydb_search (kh, &desc, 1); + if (rc == -1) + rc = 0; + else if (!rc) + { + KsbaCert cert2 = NULL; + + /* We have to ignore ambigious names as long as + there only fault is a bad key usage */ + if (!keydb_get_cert (kh, &cert2)) + { + int tmp = (same_subject_issuer (subject, issuer, cert2) + && ((gpg_err_code ( + secret? gpgsm_cert_use_sign_p (cert2) + : gpgsm_cert_use_encrypt_p (cert2) + ) + ) == GPG_ERR_WRONG_KEY_USAGE)); + ksba_cert_release (cert2); + if (tmp) + goto next_ambigious; + } + rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME); + } + } + xfree (subject); + xfree (issuer); + + if (!rc && secret) + { + char *p; + + rc = gpg_error (GPG_ERR_NO_SECKEY); + p = gpgsm_get_keygrip_hexstring (cert); + if (p) + { + if (!gpgsm_agent_havekey (p)) + rc = 0; + xfree (p); + } + } + if (!rc) + rc = gpgsm_validate_chain (ctrl, cert, NULL); + if (!rc) + { + CERTLIST cl = xtrycalloc (1, sizeof *cl); + if (!cl) + rc = OUT_OF_CORE (errno); + else + { + cl->cert = cert; cert = NULL; + cl->next = *listaddr; + *listaddr = cl; + } + } + } + } + + keydb_release (kh); + ksba_cert_release (cert); + return rc == -1? gpg_error (GPG_ERR_NO_PUBKEY): rc; +} + +void +gpgsm_release_certlist (CERTLIST list) +{ + while (list) + { + CERTLIST cl = list->next; + ksba_cert_release (list->cert); + xfree (list); + list = cl; + } +} + + +/* Like gpgsm_add_to_certlist, but look only for one certificate. No + chain validation is done */ +int +gpgsm_find_cert (const char *name, KsbaCert *r_cert) +{ + int rc; + KEYDB_SEARCH_DESC desc; + KEYDB_HANDLE kh = NULL; + + *r_cert = NULL; + rc = keydb_classify_name (name, &desc); + if (!rc) + { + kh = keydb_new (0); + if (!kh) + rc = gpg_error (GPG_ERR_ENOMEM); + else + { + rc = keydb_search (kh, &desc, 1); + if (!rc) + rc = keydb_get_cert (kh, r_cert); + if (!rc) + { + rc = keydb_search (kh, &desc, 1); + if (rc == -1) + rc = 0; + else + { + if (!rc) + rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME); + ksba_cert_release (*r_cert); + *r_cert = NULL; + } + } + } + } + + keydb_release (kh); + return rc == -1? gpg_error (GPG_ERR_NO_PUBKEY): rc; +} + diff --git a/sm/certreqgen.c b/sm/certreqgen.c new file mode 100644 index 000000000..0dd4fdde9 --- /dev/null +++ b/sm/certreqgen.c @@ -0,0 +1,699 @@ +/* certreqgen.c - Generate a key and a certification request + * Copyright (C) 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* +The format of the native parameter file is follows: + o Text only, line length is limited to about 1000 chars. + o You must use UTF-8 encoding to specify non-ascii characters. + o Empty lines are ignored. + o Leading and trailing spaces are ignored. + o A hash sign as the first non white space character is a comment line. + o Control statements are indicated by a leading percent sign, the + arguments are separated by white space from the keyword. + o Parameters are specified by a keyword, followed by a colon. Arguments + are separated by white space. + o The first parameter must be "Key-Type", control statements + may be placed anywhere. + o Key generation takes place when either the end of the parameter file + is reached, the next "Key-Type" parameter is encountered or at the + controlstatement "%commit" + o Control statements: + %echo + Print . + %dry-run + Suppress actual key generation (useful for syntax checking). + %commit + Perform the key generation. Note that an implicit commit is done + at the next "Key-Type" parameter. + %certfile + Do not write the certificate to the keyDB but to . + This must be given before the first + commit to take place, duplicate specification of the same filename + is ignored, the last filename before a commit is used. + The filename is used until a new filename is used (at commit points) + and all keys are written to that file. If a new filename is given, + this file is created (and overwrites an existing one). + Both control statements must be given. + o The order of the parameters does not matter except for "Key-Type" + which must be the first parameter. The parameters are only for the + generated keyblock and parameters from previous key generations are not + used. Some syntactically checks may be performed. + The currently defined parameters are: + Key-Type: + Starts a new parameter block by giving the type of the + primary key. The algorithm must be capable of signing. + This is a required parameter. For now the only supported + algorithm is "rsa". + Key-Length: + Length of the key in bits. Default is 1024. + Key-Usage: + Space or comma delimited list of key usage, allowed values are + "encrypt" and "sign". This is used to generate the KeyUsage extension. + Please make sure that the algorithm is capable of this usage. Default + is to allow encrypt and sign. + Name-DN: subject name + This is the DN name of the subject in rfc2253 format. + Name-Email: + The ist the email address + +Here is an example: +$ cat >foo < +#include +#include +#include +#include +#include +#include +#include + +#include "gpgsm.h" +#include +#include + +#include "keydb.h" +#include "i18n.h" + + +enum para_name { + pKEYTYPE, + pKEYLENGTH, + pKEYUSAGE, + pNAMEDN, + pNAMEEMAIL +}; + +struct para_data_s { + struct para_data_s *next; + int lnr; + enum para_name key; + union { + unsigned int usage; + char value[1]; + } u; +}; + +struct reqgen_ctrl_s { + int lnr; + int dryrun; + KsbaWriter writer; +}; + + +static int proc_parameters (struct para_data_s *para, + struct reqgen_ctrl_s *outctrl); +static int create_request (struct para_data_s *para, + KsbaConstSexp public, + struct reqgen_ctrl_s *outctrl); + + + +static void +release_parameter_list (struct para_data_s *r) +{ + struct para_data_s *r2; + + for (; r ; r = r2) + { + r2 = r->next; + xfree(r); + } +} + +static struct para_data_s * +get_parameter (struct para_data_s *para, enum para_name key) +{ + struct para_data_s *r; + + for (r = para; r && r->key != key; r = r->next) + ; + return r; +} + +static const char * +get_parameter_value (struct para_data_s *para, enum para_name key) +{ + struct para_data_s *r = get_parameter (para, key); + return (r && *r->u.value)? r->u.value : NULL; +} + +static int +get_parameter_algo (struct para_data_s *para, enum para_name key) +{ + struct para_data_s *r = get_parameter (para, key); + if (!r) + return -1; + if (digitp (r->u.value)) + return atoi( r->u.value ); + return gcry_pk_map_name (r->u.value); +} + +/* parse the usage parameter. Returns 0 on success. Note that we + only care about sign and encrypt and don't (yet) allow all the + other X.509 usage to be specified; instead we will use a fixed + mapping to the X.509 usage flags */ +static int +parse_parameter_usage (struct para_data_s *para, enum para_name key) +{ + struct para_data_s *r = get_parameter (para, key); + char *p, *pn; + unsigned int use; + + if (!r) + return 0; /* none (this is an optional parameter)*/ + + use = 0; + pn = r->u.value; + while ( (p = strsep (&pn, " \t,")) ) + { + if (!*p) + ; + else if ( !ascii_strcasecmp (p, "sign") ) + use |= GCRY_PK_USAGE_SIGN; + else if ( !ascii_strcasecmp (p, "encrypt") ) + use |= GCRY_PK_USAGE_ENCR; + else + { + log_error ("line %d: invalid usage list\n", r->lnr); + return -1; /* error */ + } + } + r->u.usage = use; + return 0; +} + + +static unsigned int +get_parameter_uint (struct para_data_s *para, enum para_name key) +{ + struct para_data_s *r = get_parameter (para, key); + + if (!r) + return 0; + + return (unsigned int)strtoul (r->u.value, NULL, 10); +} + + + +/* Read the certificate generation parameters from FP and generate + (all) certificate requests. */ +static int +read_parameters (FILE *fp, KsbaWriter writer) +{ + static struct { + const char *name; + enum para_name key; + } keywords[] = { + { "Key-Type", pKEYTYPE}, + { "Key-Length", pKEYLENGTH }, + { "Key-Usage", pKEYUSAGE }, + { "Name-DN", pNAMEDN }, + { "Name-Email", pNAMEEMAIL }, + { NULL, 0 } + }; + char line[1024], *p; + const char *err = NULL; + struct para_data_s *para, *r; + int i, rc = 0, any = 0; + struct reqgen_ctrl_s outctrl; + + memset (&outctrl, 0, sizeof (outctrl)); + outctrl.writer = writer; + + err = NULL; + para = NULL; + while (fgets (line, DIM(line)-1, fp) ) + { + char *keyword, *value; + + outctrl.lnr++; + if (*line && line[strlen(line)-1] != '\n') + { + err = "line too long"; + break; + } + for (p=line; spacep (p); p++) + ; + if (!*p || *p == '#') + continue; + + keyword = p; + if (*keyword == '%') + { + for (; !spacep (p); p++) + ; + if (*p) + *p++ = 0; + for (; spacep (p); p++) + ; + value = p; + trim_trailing_spaces (value); + + if (!ascii_strcasecmp (keyword, "%echo")) + log_info ("%s\n", value); + else if (!ascii_strcasecmp (keyword, "%dry-run")) + outctrl.dryrun = 1; + else if (!ascii_strcasecmp( keyword, "%commit")) + { + rc = proc_parameters (para, &outctrl); + if (rc) + goto leave; + any = 1; + release_parameter_list (para); + para = NULL; + } + else + log_info ("skipping control `%s' (%s)\n", keyword, value); + + continue; + } + + + if (!(p = strchr (p, ':')) || p == keyword) + { + err = "missing colon"; + break; + } + if (*p) + *p++ = 0; + for (; spacep (p); p++) + ; + if (!*p) + { + err = "missing argument"; + break; + } + value = p; + trim_trailing_spaces (value); + + for (i=0; (keywords[i].name + && ascii_strcasecmp (keywords[i].name, keyword)); i++) + ; + if (!keywords[i].name) + { + err = "unknown keyword"; + break; + } + if (keywords[i].key != pKEYTYPE && !para) + { + err = "parameter block does not start with \"Key-Type\""; + break; + } + + if (keywords[i].key == pKEYTYPE && para) + { + rc = proc_parameters (para, &outctrl); + if (rc) + goto leave; + any = 1; + release_parameter_list (para); + para = NULL; + } + else + { + for (r = para; r && r->key != keywords[i].key; r = r->next) + ; + if (r) + { + err = "duplicate keyword"; + break; + } + } + + r = xtrycalloc (1, sizeof *r + strlen( value )); + if (!r) + { + err = "out of core"; + break; + } + r->lnr = outctrl.lnr; + r->key = keywords[i].key; + strcpy (r->u.value, value); + r->next = para; + para = r; + } + + if (err) + { + log_error ("line %d: %s\n", outctrl.lnr, err); + rc = gpg_error (GPG_ERR_GENERAL); + } + else if (ferror(fp)) + { + log_error ("line %d: read error: %s\n", outctrl.lnr, strerror(errno) ); + rc = gpg_error (GPG_ERR_GENERAL); + } + else if (para) + { + rc = proc_parameters (para, &outctrl); + if (rc) + goto leave; + any = 1; + } + + if (!rc && !any) + rc = gpg_error (GPG_ERR_NO_DATA); + + leave: + release_parameter_list (para); + return rc; +} + +/* check whether there are invalid characters in the email address S */ +static int +has_invalid_email_chars (const char *s) +{ + int at_seen=0; + static char valid_chars[] = "01234567890_-." + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + for (; *s; s++) + { + if (*s & 0x80) + return 1; + if (*s == '@') + at_seen++; + else if (!at_seen && !( !!strchr (valid_chars, *s) || *s == '+')) + return 1; + else if (at_seen && !strchr (valid_chars, *s)) + return 1; + } + return at_seen != 1; +} + + +/* Check that all required parameters are given and perform the action */ +static int +proc_parameters (struct para_data_s *para, struct reqgen_ctrl_s *outctrl) +{ + struct para_data_s *r; + const char *s; + int i; + unsigned int nbits; + char numbuf[20]; + unsigned char keyparms[100]; + int rc; + KsbaSexp public; + + /* check that we have all required parameters */ + assert (get_parameter (para, pKEYTYPE)); + + /* We can only use RSA for now. There is a with pkcs-10 on how to + use ElGamal becuase it is expected that a PK algorithm can always + be used for signing. */ + i = get_parameter_algo (para, pKEYTYPE); + if (i < 1 || i != GCRY_PK_RSA ) + { + r = get_parameter (para, pKEYTYPE); + log_error ("line %d: invalid algorithm\n", r->lnr); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + + /* check the keylength */ + if (!get_parameter (para, pKEYLENGTH)) + nbits = 1024; + else + nbits = get_parameter_uint (para, pKEYLENGTH); + if (nbits < 512 || nbits > 4096) + { + r = get_parameter (para, pKEYTYPE); + log_error ("line %d: invalid key length %u (valid are 512 to 4096)\n", + r->lnr, nbits); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + + /* check the usage */ + if (parse_parameter_usage (para, pKEYUSAGE)) + return gpg_error (GPG_ERR_INV_PARAMETER); + + /* check that there is a subject name and that this DN fits our + requirements */ + if (!(s=get_parameter_value (para, pNAMEDN))) + { + r = get_parameter (para, pKEYTYPE); + log_error ("line %d: no subject name given\n", r->lnr); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + /* fixme check s */ + + /* check that the optional email address is okay */ + if ((s=get_parameter_value (para, pNAMEEMAIL))) + { + if (has_invalid_email_chars (s) + || *s == '@' + || s[strlen(s)-1] == '@' + || s[strlen(s)-1] == '.' + || strstr(s, "..")) + { + r = get_parameter (para, pKEYTYPE); + log_error ("line %d: not a valid email address\n", r->lnr); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + } + + sprintf (numbuf, "%u", nbits); + snprintf (keyparms, DIM (keyparms)-1, + "(6:genkey(3:rsa(5:nbits%d:%s)))", strlen (numbuf), numbuf); + rc = gpgsm_agent_genkey (keyparms, &public); + if (rc) + { + r = get_parameter (para, pKEYTYPE); + log_error ("line %d: key generation failed: %s\n", + r->lnr, gpg_strerror (rc)); + return rc; + } + + rc = create_request (para, public, outctrl); + xfree (public); + + return rc; +} + + +/* Parameters are checked, the key pair has been created. Now + generate the request and write it out */ +static int +create_request (struct para_data_s *para, KsbaConstSexp public, + struct reqgen_ctrl_s *outctrl) +{ + KsbaCertreq cr; + KsbaError err; + gcry_md_hd_t md; + KsbaStopReason stopreason; + int rc = 0; + const char *s; + + cr = ksba_certreq_new (); + if (!cr) + return gpg_error (GPG_ERR_ENOMEM); + + rc = gcry_md_open (&md, GCRY_MD_SHA1, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if (DBG_HASHING) + gcry_md_start_debug (md, "cr.cri"); + + ksba_certreq_set_hash_function (cr, HASH_FNC, md); + ksba_certreq_set_writer (cr, outctrl->writer); + + err = ksba_certreq_add_subject (cr, get_parameter_value (para, pNAMEDN)); + if (err) + { + log_error ("error setting the subject's name: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + s = get_parameter_value (para, pNAMEEMAIL); + if (s) + { + char *buf; + + buf = xtrymalloc (strlen (s) + 3); + if (!buf) + { + rc = OUT_OF_CORE (errno); + goto leave; + } + *buf = '<'; + strcpy (buf+1, s); + strcat (buf+1, ">"); + err = ksba_certreq_add_subject (cr, buf); + xfree (buf); + if (err) + { + log_error ("error setting the subject's alternate name: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + } + + + err = ksba_certreq_set_public_key (cr, public); + if (err) + { + log_error ("error setting the public key: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + do + { + err = ksba_certreq_build (cr, &stopreason); + if (err) + { + log_error ("ksba_certreq_build failed: %s\n", ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + if (stopreason == KSBA_SR_NEED_SIG) + { + gcry_sexp_t s_pkey; + size_t n; + unsigned char grip[20], hexgrip[41]; + char *sigval; + size_t siglen; + + n = gcry_sexp_canon_len (public, 0, NULL, NULL); + if (!n) + { + log_error ("libksba did not return a proper S-Exp\n"); + err = gpg_error (GPG_ERR_BUG); + goto leave; + } + rc = gcry_sexp_sscan (&s_pkey, NULL, public, n); + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if ( !gcry_pk_get_keygrip (s_pkey, grip) ) + { + rc = gpg_error (GPG_ERR_GENERAL); + log_error ("can't figure out the keygrip\n"); + gcry_sexp_release (s_pkey); + goto leave; + } + gcry_sexp_release (s_pkey); + for (n=0; n < 20; n++) + sprintf (hexgrip+n*2, "%02X", grip[n]); + + rc = gpgsm_agent_pksign (hexgrip, + gcry_md_read(md, GCRY_MD_SHA1), + gcry_md_get_algo_dlen (GCRY_MD_SHA1), + GCRY_MD_SHA1, + &sigval, &siglen); + if (rc) + { + log_error ("signing failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + err = ksba_certreq_set_sig_val (cr, sigval); + xfree (sigval); + if (err) + { + log_error ("failed to store the sig_val: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + } + } + while (stopreason != KSBA_SR_READY); + + + leave: + gcry_md_close (md); + ksba_certreq_release (cr); + return rc; +} + + + +/* Create a new key by reading the parameters from in_fd. Multiple + keys may be created */ +int +gpgsm_genkey (CTRL ctrl, int in_fd, FILE *out_fp) +{ + int rc; + FILE *in_fp; + Base64Context b64writer = NULL; + KsbaWriter writer; + + in_fp = fdopen (dup (in_fd), "rb"); + if (!in_fp) + { + gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("fdopen() failed: %s\n", strerror (errno)); + return tmperr; + } + + ctrl->pem_name = "NEW CERTIFICATE REQUEST"; + rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer); + if (rc) + { + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + goto leave; + } + + rc = read_parameters (in_fp, writer); + if (rc) + { + log_error ("error creating certificate request: %s\n", + gpg_strerror (rc)); + goto leave; + } + + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + gpgsm_status (ctrl, STATUS_KEY_CREATED, "P"); + log_info ("certificate request created\n"); + + leave: + gpgsm_destroy_writer (b64writer); + fclose (in_fp); + return rc; +} + diff --git a/sm/decrypt.c b/sm/decrypt.c new file mode 100644 index 000000000..17483aa49 --- /dev/null +++ b/sm/decrypt.c @@ -0,0 +1,506 @@ +/* decrypt.c - Decrypt a message + * Copyright (C) 2001, 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpgsm.h" +#include +#include + +#include "keydb.h" +#include "i18n.h" + +struct decrypt_filter_parm_s { + int algo; + int mode; + int blklen; + gcry_cipher_hd_t hd; + char iv[16]; + size_t ivlen; + int any_data; /* dod we push anything through the filter at all? */ + unsigned char lastblock[16]; /* to strip the padding we have to + keep this one */ + char helpblock[16]; /* needed because there is no block buffering in + libgcrypt (yet) */ + int helpblocklen; +}; + + + +/* decrypt the session key and fill in the parm structure. The + algo and the IV is expected to be already in PARM. */ +static int +prepare_decryption (const char *hexkeygrip, KsbaConstSexp enc_val, + struct decrypt_filter_parm_s *parm) +{ + char *seskey = NULL; + size_t n, seskeylen; + int rc; + + rc = gpgsm_agent_pkdecrypt (hexkeygrip, enc_val, + &seskey, &seskeylen); + if (rc) + { + log_error ("error decrypting session key: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (DBG_CRYPTO) + log_printhex ("pkcs1 encoded session key:", seskey, seskeylen); + + n=0; + if (seskeylen == 24) + { + /* Smells like a 3-des key. This might happen because a SC has + already done the unpacking. fixme! */ + } + else + { + if (n + 7 > seskeylen ) + { + rc = gpg_error (GPG_ERR_INV_SESSION_KEY); + goto leave; + } + + /* FIXME: Actually the leading zero is required but due to the way + we encode the output in libgcrypt as an MPI we are not able to + encode that leading zero. However, when using a Smartcard we are + doing it the rightway and therefore we have to skip the zero. This + should be fixed in gpg-agent of course. */ + if (!seskey[n]) + n++; + + if (seskey[n] != 2 ) /* wrong block type version */ + { + rc = gpg_error (GPG_ERR_INV_SESSION_KEY); + goto leave; + } + + for (n++; n < seskeylen && seskey[n]; n++) /* skip the random bytes */ + ; + n++; /* and the zero byte */ + if (n >= seskeylen ) + { + rc = gpg_error (GPG_ERR_INV_SESSION_KEY); + goto leave; + } + } + + if (DBG_CRYPTO) + log_printhex ("session key:", seskey+n, seskeylen-n); + + rc = gcry_cipher_open (&parm->hd, parm->algo, parm->mode, 0); + if (rc) + { + log_error ("error creating decryptor: %s\n", gpg_strerror (rc)); + goto leave; + } + + rc = gcry_cipher_setkey (parm->hd, seskey+n, seskeylen-n); + if (gpg_err_code (rc) == GPG_ERR_WEAK_KEY) + { + log_info (_("WARNING: message was encrypted with " + "a weak key in the symmetric cipher.\n")); + rc = 0; + } + if (rc) + { + log_error("key setup failed: %s\n", gpg_strerror(rc) ); + goto leave; + } + + gcry_cipher_setiv (parm->hd, parm->iv, parm->ivlen); + + leave: + xfree (seskey); + return rc; +} + + +/* This function is called by the KSBA writer just before the actual + write is done. The function must take INLEN bytes from INBUF, + decrypt it and store it inoutbuf which has a maximum size of + maxoutlen. The valid bytes in outbuf should be return in outlen. + Due to different buffer sizes or different length of input and + output, it may happen that fewer bytes are process or fewer bytes + are written. */ +static KsbaError +decrypt_filter (void *arg, + const void *inbuf, size_t inlen, size_t *inused, + void *outbuf, size_t maxoutlen, size_t *outlen) +{ + struct decrypt_filter_parm_s *parm = arg; + int blklen = parm->blklen; + size_t orig_inlen = inlen; + + /* fixme: Should we issue an error when we have not seen one full block? */ + if (!inlen) + return KSBA_Bug; + + if (maxoutlen < 2*parm->blklen) + return KSBA_Bug; + /* make some space becuase we will later need an extra block at the end */ + maxoutlen -= blklen; + + if (parm->helpblocklen) + { + int i, j; + + for (i=parm->helpblocklen,j=0; i < blklen && j < inlen; i++, j++) + parm->helpblock[i] = ((const char*)inbuf)[j]; + inlen -= j; + if (blklen > maxoutlen) + return KSBA_Bug; + if (i < blklen) + { + parm->helpblocklen = i; + *outlen = 0; + } + else + { + parm->helpblocklen = 0; + if (parm->any_data) + { + memcpy (outbuf, parm->lastblock, blklen); + *outlen =blklen; + } + else + *outlen = 0; + gcry_cipher_decrypt (parm->hd, parm->lastblock, blklen, + parm->helpblock, blklen); + parm->any_data = 1; + } + *inused = orig_inlen - inlen; + return 0; + } + + + if (inlen > maxoutlen) + inlen = maxoutlen; + if (inlen % blklen) + { /* store the remainder away */ + parm->helpblocklen = inlen%blklen; + inlen = inlen/blklen*blklen; + memcpy (parm->helpblock, (const char*)inbuf+inlen, parm->helpblocklen); + } + + *inused = inlen + parm->helpblocklen; + if (inlen) + { + assert (inlen >= blklen); + if (parm->any_data) + { + gcry_cipher_decrypt (parm->hd, (char*)outbuf+blklen, inlen, + inbuf, inlen); + memcpy (outbuf, parm->lastblock, blklen); + memcpy (parm->lastblock,(char*)outbuf+inlen, blklen); + *outlen = inlen; + } + else + { + gcry_cipher_decrypt (parm->hd, outbuf, inlen, inbuf, inlen); + memcpy (parm->lastblock, (char*)outbuf+inlen-blklen, blklen); + *outlen = inlen - blklen; + parm->any_data = 1; + } + } + else + *outlen = 0; + return 0; +} + + + +/* Perform a decrypt operation. */ +int +gpgsm_decrypt (CTRL ctrl, int in_fd, FILE *out_fp) +{ + int rc; + KsbaError err; + Base64Context b64reader = NULL; + Base64Context b64writer = NULL; + KsbaReader reader; + KsbaWriter writer; + KsbaCMS cms = NULL; + KsbaStopReason stopreason; + KEYDB_HANDLE kh; + int recp; + FILE *in_fp = NULL; + struct decrypt_filter_parm_s dfparm; + + memset (&dfparm, 0, sizeof dfparm); + + kh = keydb_new (0); + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + + in_fp = fdopen ( dup (in_fd), "rb"); + if (!in_fp) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("fdopen() failed: %s\n", strerror (errno)); + goto leave; + } + + rc = gpgsm_create_reader (&b64reader, ctrl, in_fp, &reader); + if (rc) + { + log_error ("can't create reader: %s\n", gpg_strerror (rc)); + goto leave; + } + + rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer); + if (rc) + { + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + goto leave; + } + + cms = ksba_cms_new (); + if (!cms) + { + rc = gpg_error (GPG_ERR_ENOMEM); + goto leave; + } + + err = ksba_cms_set_reader_writer (cms, reader, writer); + if (err) + { + log_debug ("ksba_cms_set_reader_writer failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + /* parser loop */ + do + { + err = ksba_cms_parse (cms, &stopreason); + if (err) + { + log_debug ("ksba_cms_parse failed: %s\n", ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + if (stopreason == KSBA_SR_BEGIN_DATA + || stopreason == KSBA_SR_DETACHED_DATA) + { + int algo, mode; + const char *algoid; + int any_key = 0; + + algoid = ksba_cms_get_content_oid (cms, 2/* encryption algo*/); + algo = gcry_cipher_map_name (algoid); + mode = gcry_cipher_mode_from_oid (algoid); + if (!algo || !mode) + { + rc = gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + log_error ("unsupported algorithm `%s'\n", algoid? algoid:"?"); + if (algoid && !strcmp (algoid, "1.2.840.113549.3.2")) + log_info (_("(this is the RC2 algorithm)\n")); + else if (!algoid) + log_info (_("(this does not seem to be an encrypted" + " message)\n")); + { + char numbuf[50]; + sprintf (numbuf, "%d", rc); + gpgsm_status2 (ctrl, STATUS_ERROR, "decrypt.algorithm", + numbuf, algoid?algoid:"?", NULL); + } + + goto leave; + } + dfparm.algo = algo; + dfparm.mode = mode; + dfparm.blklen = gcry_cipher_get_algo_blklen (algo); + if (dfparm.blklen > sizeof (dfparm.helpblock)) + return gpg_error (GPG_ERR_BUG); + + rc = ksba_cms_get_content_enc_iv (cms, + dfparm.iv, + sizeof (dfparm.iv), + &dfparm.ivlen); + if (rc) + { + log_error ("error getting IV: %s\n", ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + for (recp=0; !any_key; recp++) + { + char *issuer; + KsbaSexp serial; + KsbaSexp enc_val; + char *hexkeygrip = NULL; + + err = ksba_cms_get_issuer_serial (cms, recp, &issuer, &serial); + if (err == -1 && recp) + break; /* no more recipients */ + if (err) + log_error ("recp %d - error getting info: %s\n", + recp, ksba_strerror (err)); + else + { + KsbaCert cert = NULL; + + log_debug ("recp %d - issuer: `%s'\n", + recp, issuer? issuer:"[NONE]"); + log_debug ("recp %d - serial: ", recp); + gpgsm_dump_serial (serial); + log_printf ("\n"); + + keydb_search_reset (kh); + rc = keydb_search_issuer_sn (kh, issuer, serial); + if (rc) + { + log_error ("failed to find the certificate: %s\n", + gpg_strerror(rc)); + goto oops; + } + + rc = keydb_get_cert (kh, &cert); + if (rc) + { + log_error ("failed to get cert: %s\n", gpg_strerror (rc)); + goto oops; + } + /* Just in case there is a problem with the own + certificate we print this message - should never + happen of course */ + rc = gpgsm_cert_use_decrypt_p (cert); + if (rc) + { + char numbuf[50]; + sprintf (numbuf, "%d", rc); + gpgsm_status2 (ctrl, STATUS_ERROR, "decrypt.keyusage", + numbuf, NULL); + rc = 0; + } + + hexkeygrip = gpgsm_get_keygrip_hexstring (cert); + + oops: + xfree (issuer); + xfree (serial); + ksba_cert_release (cert); + } + + if (!hexkeygrip) + ; + else if (!(enc_val = ksba_cms_get_enc_val (cms, recp))) + log_error ("recp %d - error getting encrypted session key\n", + recp); + else + { + rc = prepare_decryption (hexkeygrip, enc_val, &dfparm); + xfree (enc_val); + if (rc) + { + log_debug ("decrypting session key failed: %s\n", + gpg_strerror (rc)); + } + else + { /* setup the bulk decrypter */ + any_key = 1; + ksba_writer_set_filter (writer, + decrypt_filter, + &dfparm); + } + } + } + if (!any_key) + { + rc = gpg_error (GPG_ERR_NO_SECKEY); + goto leave; + } + } + else if (stopreason == KSBA_SR_END_DATA) + { + ksba_writer_set_filter (writer, NULL, NULL); + if (dfparm.any_data) + { /* write the last block with padding removed */ + int i, npadding = dfparm.lastblock[dfparm.blklen-1]; + if (!npadding || npadding > dfparm.blklen) + { + log_error ("invalid padding with value %d\n", npadding); + rc = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + rc = ksba_writer_write (writer, + dfparm.lastblock, + dfparm.blklen - npadding); + if (rc) + { + rc = map_ksba_err (rc); + goto leave; + } + for (i=dfparm.blklen - npadding; i < dfparm.blklen; i++) + { + if (dfparm.lastblock[i] != npadding) + { + log_error ("inconsistent padding\n"); + rc = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + } + } + } + + } + while (stopreason != KSBA_SR_READY); + + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + gpgsm_status (ctrl, STATUS_DECRYPTION_OKAY, NULL); + + + leave: + if (rc) + gpgsm_status (ctrl, STATUS_DECRYPTION_FAILED, NULL); + ksba_cms_release (cms); + gpgsm_destroy_reader (b64reader); + gpgsm_destroy_writer (b64writer); + keydb_release (kh); + if (in_fp) + fclose (in_fp); + if (dfparm.hd) + gcry_cipher_close (dfparm.hd); + return rc; +} + + diff --git a/sm/delete.c b/sm/delete.c new file mode 100644 index 000000000..53eff864c --- /dev/null +++ b/sm/delete.c @@ -0,0 +1,165 @@ +/* delete.c + * Copyright (C) 2002 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpgsm.h" +#include +#include + +#include "keydb.h" +#include "i18n.h" + + +/* Delete a certificate or an secret key from a key database. */ +static int +delete_one (CTRL ctrl, const char *username) +{ + int rc = 0; + KEYDB_SEARCH_DESC desc; + KEYDB_HANDLE kh = NULL; + KsbaCert cert = NULL; + int duplicates = 0; + + rc = keydb_classify_name (username, &desc); + if (rc) + { + log_error (_("certificate `%s' not found: %s\n"), + username, gpg_strerror (rc)); + gpgsm_status2 (ctrl, STATUS_DELETE_PROBLEM, "1", NULL); + goto leave; + } + + kh = keydb_new (0); + if (!kh) + { + log_error ("keydb_new failed\n"); + goto leave; + } + + + rc = keydb_search (kh, &desc, 1); + if (!rc) + rc = keydb_get_cert (kh, &cert); + if (!rc) + { + char fpr[20]; + + gpgsm_get_fingerprint (cert, 0, fpr, NULL); + + next_ambigious: + rc = keydb_search (kh, &desc, 1); + if (rc == -1) + rc = 0; + else if (!rc) + { + KsbaCert cert2 = NULL; + char fpr2[20]; + + /* We ignore all duplicated certificates which might have + been inserted due to program bugs. */ + if (!keydb_get_cert (kh, &cert2)) + { + gpgsm_get_fingerprint (cert2, 0, fpr2, NULL); + ksba_cert_release (cert2); + if (!memcmp (fpr, fpr2, 20)) + { + duplicates++; + goto next_ambigious; + } + } + rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME); + } + } + if (rc) + { + if (rc == -1) + rc = gpg_error (GPG_ERR_NO_PUBKEY); + log_error (_("certificate `%s' not found: %s\n"), + username, gpg_strerror (rc)); + gpgsm_status2 (ctrl, STATUS_DELETE_PROBLEM, "3", NULL); + goto leave; + } + + /* we need to search again to get back to the right position. */ + do + { + keydb_search_reset (kh); + rc = keydb_search (kh, &desc, 1); + if (rc) + { + log_error ("problem re-searching certificate: %s\n", + gpg_strerror (rc)); + goto leave; + } + + rc = keydb_delete (kh); + if (rc) + goto leave; + if (opt.verbose) + { + if (duplicates) + log_info (_("duplicated certificate `%s' deleted\n"), username); + else + log_info (_("certificate `%s' deleted\n"), username); + } + } + while (duplicates--); + + leave: + keydb_release (kh); + ksba_cert_release (cert); + return rc; +} + + + +/* Delete the certificates specified by NAMES. */ +int +gpgsm_delete (CTRL ctrl, STRLIST names) +{ + int rc; + + if (!names) + { + log_error ("nothing to delete\n"); + return gpg_error (GPG_ERR_NO_DATA); + } + + for (; names; names=names->next ) + { + rc = delete_one (ctrl, names->d); + if (rc) + { + log_error (_("deleting certificate \"%s\" failed: %s\n"), + names->d, gpg_strerror (rc) ); + return rc; + } + } + + return 0; +} diff --git a/sm/encrypt.c b/sm/encrypt.c new file mode 100644 index 000000000..725a81b70 --- /dev/null +++ b/sm/encrypt.c @@ -0,0 +1,550 @@ +/* encrypt.c - Encrypt a message + * Copyright (C) 2001, 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpgsm.h" +#include +#include + +#include "keydb.h" +#include "i18n.h" + + +struct dek_s { + const char *algoid; + int algo; + gcry_cipher_hd_t chd; + char key[32]; + int keylen; + char iv[32]; + int ivlen; +}; +typedef struct dek_s *DEK; + +struct encrypt_cb_parm_s { + FILE *fp; + DEK dek; + int eof_seen; + int ready; + int readerror; + int bufsize; + unsigned char *buffer; + int buflen; +}; + + + + + +/* initialize the data encryptionkey (session key) */ +static int +init_dek (DEK dek) +{ + int rc=0, mode, i; + + dek->algo = gcry_cipher_map_name (dek->algoid); + mode = gcry_cipher_mode_from_oid (dek->algoid); + if (!dek->algo || !mode) + { + log_error ("unsupported algorithm `%s'\n", dek->algoid); + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + } + + dek->keylen = gcry_cipher_get_algo_keylen (dek->algo); + if (!dek->keylen || dek->keylen > sizeof (dek->key)) + return gpg_error (GPG_ERR_BUG); + + dek->ivlen = gcry_cipher_get_algo_blklen (dek->algo); + if (!dek->ivlen || dek->ivlen > sizeof (dek->iv)) + return gpg_error (GPG_ERR_BUG); + + if (dek->keylen < 100/8) + { /* make sure we don't use weak keys */ + log_error ("key length of `%s' too small\n", dek->algoid); + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + } + + rc = gcry_cipher_open (&dek->chd, dek->algo, mode, GCRY_CIPHER_SECURE); + if (rc) + { + log_error ("failed to create cipher context: %s\n", gpg_strerror (rc)); + return rc; + } + + for (i=0; i < 8; i++) + { + gcry_randomize (dek->key, dek->keylen, GCRY_STRONG_RANDOM ); + rc = gcry_cipher_setkey (dek->chd, dek->key, dek->keylen); + if (gpg_err_code (rc) != GPG_ERR_WEAK_KEY) + break; + log_info(_("weak key created - retrying\n") ); + } + if (rc) + { + log_error ("failed to set the key: %s\n", gpg_strerror (rc)); + gcry_cipher_close (dek->chd); + dek->chd = NULL; + return rc; + } + + gcry_randomize (dek->iv, dek->ivlen, GCRY_STRONG_RANDOM); + rc = gcry_cipher_setiv (dek->chd, dek->iv, dek->ivlen); + if (rc) + { + log_error ("failed to set the IV: %s\n", gpg_strerror (rc)); + gcry_cipher_close (dek->chd); + dek->chd = NULL; + return rc; + } + + return 0; +} + + +/* Encode the session key. NBITS is the number of bits which should be + used for packing the session key. returns: An mpi with the session + key (caller must free) */ +static gcry_mpi_t +encode_session_key (DEK dek, unsigned int nbits) +{ + int nframe = (nbits+7) / 8; + byte *p; + byte *frame; + int i,n; + gcry_mpi_t a; + + if (dek->keylen + 7 > nframe || !nframe) + log_bug ("can't encode a %d bit key in a %d bits frame\n", + dek->keylen*8, nbits ); + + /* We encode the session key in this way: + * + * 0 2 RND(n bytes) 0 KEY(k bytes) + * + * (But how can we store the leading 0 - the external representaion + * of MPIs doesn't allow leading zeroes =:-) + * + * RND are non-zero random bytes. + * KEY is the encryption key (session key) + */ + + frame = gcry_xmalloc_secure (nframe); + n = 0; + frame[n++] = 0; + frame[n++] = 2; + i = nframe - 3 - dek->keylen; + assert (i > 0); + p = gcry_random_bytes_secure (i, GCRY_STRONG_RANDOM); + /* replace zero bytes by new values */ + for (;;) + { + int j, k; + byte *pp; + + /* count the zero bytes */ + for(j=k=0; j < i; j++ ) + { + if( !p[j] ) + k++; + } + if( !k ) + break; /* okay: no zero bytes */ + + k += k/128; /* better get some more */ + pp = gcry_random_bytes_secure (k, GCRY_STRONG_RANDOM); + for (j=0; j < i && k; j++) + { + if( !p[j] ) + p[j] = pp[--k]; + } + xfree (pp); + } + memcpy (frame+n, p, i); + xfree (p); + + n += i; + frame[n++] = 0; + memcpy (frame+n, dek->key, dek->keylen); + n += dek->keylen; + assert (n == nframe); + if (gcry_mpi_scan (&a, GCRYMPI_FMT_USG, frame, n, &nframe) ) + BUG (); + gcry_free(frame); + + return a; +} + + + +/* encrypt the DEK under the key contained in CERT and return it as a + canonical S-Exp in encval */ +static int +encrypt_dek (const DEK dek, KsbaCert cert, char **encval) +{ + gcry_sexp_t s_ciph, s_data, s_pkey; + int rc; + KsbaSexp buf; + size_t len; + + *encval = NULL; + + /* get the key from the cert */ + buf = ksba_cert_get_public_key (cert); + if (!buf) + { + log_error ("no public key for recipient\n"); + return gpg_error (GPG_ERR_NO_PUBKEY); + } + len = gcry_sexp_canon_len (buf, 0, NULL, NULL); + if (!len) + { + log_error ("libksba did not return a proper S-Exp\n"); + return gpg_error (GPG_ERR_BUG); + } + rc = gcry_sexp_sscan (&s_pkey, NULL, buf, len); + xfree (buf); buf = NULL; + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + return rc; + } + + /* put the encoded cleartext into a simple list */ + { + /* fixme: actually the pkcs-1 encoding should go into libgcrypt */ + gcry_mpi_t data = encode_session_key (dek, gcry_pk_get_nbits (s_pkey)); + if (!data) + { + gcry_mpi_release (data); + return gpg_error (GPG_ERR_GENERAL); + } + if (gcry_sexp_build (&s_data, NULL, "%m", data)) + BUG (); + gcry_mpi_release (data); + } + + /* pass it to libgcrypt */ + rc = gcry_pk_encrypt (&s_ciph, s_data, s_pkey); + gcry_sexp_release (s_data); + gcry_sexp_release (s_pkey); + + /* reformat it */ + len = gcry_sexp_sprint (s_ciph, GCRYSEXP_FMT_CANON, NULL, 0); + assert (len); + buf = xtrymalloc (len); + if (!buf) + { + gpg_error_t tmperr = OUT_OF_CORE (errno); + gcry_sexp_release (s_ciph); + return tmperr; + } + len = gcry_sexp_sprint (s_ciph, GCRYSEXP_FMT_CANON, buf, len); + assert (len); + + *encval = buf; + return 0; +} + + + +/* do the actual encryption */ +static int +encrypt_cb (void *cb_value, char *buffer, size_t count, size_t *nread) +{ + struct encrypt_cb_parm_s *parm = cb_value; + int blklen = parm->dek->ivlen; + unsigned char *p; + size_t n; + + *nread = 0; + if (!buffer) + return -1; /* not supported */ + + if (parm->ready) + return -1; + + if (count < blklen) + BUG (); + + if (!parm->eof_seen) + { /* fillup the buffer */ + p = parm->buffer; + for (n=parm->buflen; n < parm->bufsize; n++) + { + int c = getc (parm->fp); + if (c == EOF) + { + if (ferror (parm->fp)) + { + parm->readerror = errno; + return -1; + } + parm->eof_seen = 1; + break; + } + p[n] = c; + } + parm->buflen = n; + } + + n = parm->buflen < count? parm->buflen : count; + n = n/blklen * blklen; + if (n) + { /* encrypt the stuff */ + gcry_cipher_encrypt (parm->dek->chd, buffer, n, parm->buffer, n); + *nread = n; + /* Who cares about cycles, take the easy way and shift the buffer */ + parm->buflen -= n; + memmove (parm->buffer, parm->buffer+n, parm->buflen); + } + else if (parm->eof_seen) + { /* no complete block but eof: add padding */ + /* fixme: we should try to do this also in the above code path */ + int i, npad = blklen - (parm->buflen % blklen); + p = parm->buffer; + for (n=parm->buflen, i=0; n < parm->bufsize && i < npad; n++, i++) + p[n] = npad; + gcry_cipher_encrypt (parm->dek->chd, buffer, n, parm->buffer, n); + *nread = n; + parm->ready = 1; + } + + return 0; +} + + + + +/* Perform an encrypt operation. + + Encrypt the data received on DATA-FD and write it to OUT_FP. The + recipients are take from the certificate given in recplist; if this + is NULL it will be encrypted for a default recipient */ +int +gpgsm_encrypt (CTRL ctrl, CERTLIST recplist, int data_fd, FILE *out_fp) +{ + int rc = 0; + Base64Context b64writer = NULL; + KsbaError err; + KsbaWriter writer; + KsbaReader reader = NULL; + KsbaCMS cms = NULL; + KsbaStopReason stopreason; + KEYDB_HANDLE kh = NULL; + struct encrypt_cb_parm_s encparm; + DEK dek = NULL; + int recpno; + FILE *data_fp = NULL; + CERTLIST cl; + + memset (&encparm, 0, sizeof encparm); + + if (!recplist) + { + log_error(_("no valid recipients given\n")); + gpgsm_status (ctrl, STATUS_NO_RECP, "0"); + rc = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + + kh = keydb_new (0); + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + data_fp = fdopen ( dup (data_fd), "rb"); + if (!data_fp) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("fdopen() failed: %s\n", strerror (errno)); + goto leave; + } + + reader = ksba_reader_new (); + if (!reader) + rc = KSBA_Out_Of_Core; + if (!rc) + rc = ksba_reader_set_cb (reader, encrypt_cb, &encparm); + if (rc) + { + rc = map_ksba_err (rc); + goto leave; + } + encparm.fp = data_fp; + + ctrl->pem_name = "ENCRYPTED MESSAGE"; + rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer); + if (rc) + { + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + goto leave; + } + + cms = ksba_cms_new (); + if (!cms) + { + rc = gpg_error (GPG_ERR_ENOMEM); + goto leave; + } + + err = ksba_cms_set_reader_writer (cms, reader, writer); + if (err) + { + log_debug ("ksba_cms_set_reader_writer failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + /* We are going to create enveloped data with uninterpreted data as + inner content */ + err = ksba_cms_set_content_type (cms, 0, KSBA_CT_ENVELOPED_DATA); + if (!err) + err = ksba_cms_set_content_type (cms, 1, KSBA_CT_DATA); + if (err) + { + log_debug ("ksba_cms_set_content_type failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + /* create a session key */ + dek = xtrycalloc (1, sizeof *dek); /* hmmm: should we put it into secmem?*/ + if (!dek) + rc = OUT_OF_CORE (errno); + else + { + dek->algoid = opt.def_cipher_algoid; + rc = init_dek (dek); + } + if (rc) + { + log_error ("failed to create the session key: %s\n", + gpg_strerror (rc)); + goto leave; + } + + err = ksba_cms_set_content_enc_algo (cms, dek->algoid, dek->iv, dek->ivlen); + if (err) + { + log_error ("ksba_cms_set_content_enc_algo failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + encparm.dek = dek; + /* Use a ~8k (AES) or ~4k (3DES) buffer */ + encparm.bufsize = 500 * dek->ivlen; + encparm.buffer = xtrymalloc (encparm.bufsize); + if (!encparm.buffer) + { + rc = OUT_OF_CORE (errno); + goto leave; + } + + /* gather certificates of recipients, encrypt the session key for + each and store them in the CMS object */ + for (recpno = 0, cl = recplist; cl; recpno++, cl = cl->next) + { + char *encval; + + rc = encrypt_dek (dek, cl->cert, &encval); + if (rc) + { + log_error ("encryption failed for recipient no. %d: %s\n", + recpno, gpg_strerror (rc)); + goto leave; + } + + err = ksba_cms_add_recipient (cms, cl->cert); + if (err) + { + log_error ("ksba_cms_add_recipient failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + xfree (encval); + goto leave; + } + + err = ksba_cms_set_enc_val (cms, recpno, encval); + xfree (encval); + if (err) + { + log_error ("ksba_cms_set_enc_val failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + } + + /* main control loop for encryption */ + recpno = 0; + do + { + err = ksba_cms_build (cms, &stopreason); + if (err) + { + log_debug ("ksba_cms_build failed: %s\n", ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + } + while (stopreason != KSBA_SR_READY); + + if (encparm.readerror) + { + log_error ("error reading input: %s\n", strerror (encparm.readerror)); + rc = gpg_error (gpg_err_code_from_errno (encparm.readerror)); + goto leave; + } + + + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + log_info ("encrypted data created\n"); + + leave: + ksba_cms_release (cms); + gpgsm_destroy_writer (b64writer); + ksba_reader_release (reader); + keydb_release (kh); + xfree (dek); + if (data_fp) + fclose (data_fp); + xfree (encparm.buffer); + return rc; +} diff --git a/sm/export.c b/sm/export.c new file mode 100644 index 000000000..93a55debc --- /dev/null +++ b/sm/export.c @@ -0,0 +1,249 @@ +/* export.c + * Copyright (C) 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpgsm.h" +#include +#include + +#include "keydb.h" + +static void print_short_info (KsbaCert cert, FILE *fp); + + + +/* Export all certificates or just those given in NAMES. */ +void +gpgsm_export (CTRL ctrl, STRLIST names, FILE *fp) +{ + KEYDB_HANDLE hd; + KEYDB_SEARCH_DESC *desc = NULL; + int ndesc; + Base64Context b64writer = NULL; + KsbaWriter writer; + STRLIST sl; + KsbaCert cert = NULL; + int rc=0; + int count = 0; + int i; + + hd = keydb_new (0); + if (!hd) + { + log_error ("keydb_new failed\n"); + goto leave; + } + + if (!names) + ndesc = 1; + else + { + for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++) + ; + } + + desc = xtrycalloc (ndesc, sizeof *desc); + if (!ndesc) + { + log_error ("allocating memory for export failed: %s\n", + gpg_strerror (OUT_OF_CORE (errno))); + goto leave; + } + + if (!names) + desc[0].mode = KEYDB_SEARCH_MODE_FIRST; + else + { + for (ndesc=0, sl=names; sl; sl = sl->next) + { + rc = keydb_classify_name (sl->d, desc+ndesc); + if (rc) + { + log_error ("key `%s' not found: %s\n", + sl->d, gpg_strerror (rc)); + rc = 0; + } + else + ndesc++; + } + } + + /* If all specifications are done by fingerprint, we switch to + ephemeral mode so that _all_ currently available and matching + certificates are exported. + + fixme: we should in this case keep a list of certificates to + avoid accidential export of duplicate certificates. */ + if (names && ndesc) + { + for (i=0; (i < ndesc + && (desc[i].mode == KEYDB_SEARCH_MODE_FPR + || desc[i].mode == KEYDB_SEARCH_MODE_FPR20 + || desc[i].mode == KEYDB_SEARCH_MODE_FPR16)); i++) + ; + if (i == ndesc) + keydb_set_ephemeral (hd, 1); + } + + while (!(rc = keydb_search (hd, desc, ndesc))) + { + const unsigned char *image; + size_t imagelen; + + if (!names) + desc[0].mode = KEYDB_SEARCH_MODE_NEXT; + + rc = keydb_get_cert (hd, &cert); + if (rc) + { + log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + image = ksba_cert_get_image (cert, &imagelen); + if (!image) + { + log_error ("ksba_cert_get_image failed\n"); + goto leave; + } + + if (ctrl->create_pem) + { + if (count) + putc ('\n', fp); + print_short_info (cert, fp); + putc ('\n', fp); + } + count++; + + if (!b64writer) + { + ctrl->pem_name = "CERTIFICATE"; + rc = gpgsm_create_writer (&b64writer, ctrl, fp, &writer); + if (rc) + { + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + goto leave; + } + } + + rc = ksba_writer_write (writer, image, imagelen); + if (rc) + { + log_error ("write error: %s\n", ksba_strerror (rc)); + goto leave; + } + + if (ctrl->create_pem) + { + /* We want one certificate per PEM block */ + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + gpgsm_destroy_writer (b64writer); + b64writer = NULL; + } + + ksba_cert_release (cert); + cert = NULL; + } + if (rc && rc != -1) + log_error ("keydb_search failed: %s\n", gpg_strerror (rc)); + else if (b64writer) + { + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + } + + leave: + gpgsm_destroy_writer (b64writer); + ksba_cert_release (cert); + xfree (desc); + keydb_release (hd); +} + + +/* Print some info about the certifciate CERT to FP */ +static void +print_short_info (KsbaCert cert, FILE *fp) +{ + char *p; + KsbaSexp sexp; + int idx; + + for (idx=0; (p = ksba_cert_get_issuer (cert, idx)); idx++) + { + fputs (!idx? "Issuer ...: " + : "\n aka ...: ", fp); + gpgsm_print_name (fp, p); + xfree (p); + } + putc ('\n', fp); + + fputs ("Serial ...: ", fp); + sexp = ksba_cert_get_serial (cert); + if (sexp) + { + int len; + const unsigned char *s = sexp; + + if (*s == '(') + { + s++; + for (len=0; *s && *s != ':' && digitp (s); s++) + len = len*10 + atoi_1 (s); + if (*s == ':') + for (s++; len; len--, s++) + fprintf (fp, "%02X", *s); + } + xfree (sexp); + } + putc ('\n', fp); + + for (idx=0; (p = ksba_cert_get_subject (cert, idx)); idx++) + { + fputs (!idx? "Subject ..: " + : "\n aka ..: ", fp); + gpgsm_print_name (fp, p); + xfree (p); + } + putc ('\n', fp); +} + + + + + + diff --git a/sm/fingerprint.c b/sm/fingerprint.c new file mode 100644 index 000000000..028c08aab --- /dev/null +++ b/sm/fingerprint.c @@ -0,0 +1,271 @@ +/* fingerprint.c - Get the fingerprint + * Copyright (C) 2001 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "gpgsm.h" +#include +#include + +/* Return the fingerprint of the certificate (we can't put this into + libksba because we need libgcrypt support). The caller must + provide an array of sufficient length or NULL so that the function + allocates the array. If r_len is not NULL, the length of the + digest is returned; well, this can also be done by using + gcry_md_get_algo_dlen(). If algo is 0, a SHA-1 will be used. + + If there is a problem , the function does never return NULL but a + digest of all 0xff. + */ +char * +gpgsm_get_fingerprint (KsbaCert cert, int algo, char *array, int *r_len) +{ + gcry_md_hd_t md; + int rc, len; + + if (!algo) + algo = GCRY_MD_SHA1; + + len = gcry_md_get_algo_dlen (algo); + assert (len); + if (!array) + array = xmalloc (len); + + if (r_len) + *r_len = len; + + rc = gcry_md_open (&md, algo, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + memset (array, 0xff, len); /* better return an invalid fpr than NULL */ + return array; + } + + rc = ksba_cert_hash (cert, 0, HASH_FNC, md); + if (rc) + { + log_error ("ksba_cert_hash failed: %s\n", ksba_strerror (rc)); + gcry_md_close (md); + memset (array, 0xff, len); /* better return an invalid fpr than NULL */ + return array; + } + gcry_md_final (md); + memcpy (array, gcry_md_read(md, algo), len ); + return array; +} + + +/* Return an allocated buffer with the formatted fingerprint */ +char * +gpgsm_get_fingerprint_string (KsbaCert cert, int algo) +{ + unsigned char digest[MAX_DIGEST_LEN]; + char *buf; + int len, i; + + if (!algo) + algo = GCRY_MD_SHA1; + + len = gcry_md_get_algo_dlen (algo); + assert (len <= MAX_DIGEST_LEN ); + gpgsm_get_fingerprint (cert, algo, digest, NULL); + buf = xmalloc (len*3+1); + *buf = 0; + for (i=0; i < len; i++ ) + sprintf (buf+strlen(buf), i? ":%02X":"%02X", digest[i]); + return buf; +} + +/* Return an allocated buffer with the formatted fingerprint as one + large hexnumber */ +char * +gpgsm_get_fingerprint_hexstring (KsbaCert cert, int algo) +{ + unsigned char digest[MAX_DIGEST_LEN]; + char *buf; + int len, i; + + if (!algo) + algo = GCRY_MD_SHA1; + + len = gcry_md_get_algo_dlen (algo); + assert (len <= MAX_DIGEST_LEN ); + gpgsm_get_fingerprint (cert, algo, digest, NULL); + buf = xmalloc (len*3+1); + *buf = 0; + for (i=0; i < len; i++ ) + sprintf (buf+strlen(buf), "%02X", digest[i]); + return buf; +} + +/* Return a certificate ID. These are the last 4 bytes of the SHA-1 + fingerprint. */ +unsigned long +gpgsm_get_short_fingerprint (KsbaCert cert) +{ + unsigned char digest[20]; + + gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); + return ((digest[16]<<24)|(digest[17]<<16)|(digest[18]<< 8)|digest[19]); +} + + +/* Return the so called KEYGRIP which is the SHA-1 hash of the public + key parameters expressed as an canoncial encoded S-Exp. array must + be 20 bytes long. returns the array or a newly allocated one if the + passed one was NULL */ +char * +gpgsm_get_keygrip (KsbaCert cert, char *array) +{ + gcry_sexp_t s_pkey; + int rc; + KsbaSexp p; + size_t n; + + p = ksba_cert_get_public_key (cert); + if (!p) + return NULL; /* oops */ + + if (DBG_X509) + log_debug ("get_keygrip for public key: %s\n", p); + n = gcry_sexp_canon_len (p, 0, NULL, NULL); + if (!n) + { + log_error ("libksba did not return a proper S-Exp\n"); + return NULL; + } + rc = gcry_sexp_sscan ( &s_pkey, NULL, p, n); + xfree (p); + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + return NULL; + } + array = gcry_pk_get_keygrip (s_pkey, array); + gcry_sexp_release (s_pkey); + if (!array) + { + rc = gpg_error (GPG_ERR_GENERAL); + log_error ("can't calculate keygrip\n"); + return NULL; + } + if (DBG_X509) + log_printhex ("keygrip=", array, 20); + + return array; +} + +/* Return an allocated buffer with the keygrip of CERT in from of an + hexstring. NULL is returned in case of error */ +char * +gpgsm_get_keygrip_hexstring (KsbaCert cert) +{ + unsigned char grip[20]; + char *buf, *p; + int i; + + gpgsm_get_keygrip (cert, grip); + buf = p = xmalloc (20*2+1); + for (i=0; i < 20; i++, p += 2 ) + sprintf (p, "%02X", grip[i]); + return buf; +} + + + +/* For certain purposes we need a certificate id which has an upper + limit of the size. We use the hash of the issuer name and the + serial number for this. In most cases the serial number is not + that large and the resulting string can be passed on an assuan + command line. Everything is hexencoded with the serialnumber + delimted from the has by a dot. + + The caller must free the string. +*/ +char * +gpgsm_get_certid (KsbaCert cert) +{ + KsbaSexp serial; + unsigned char *p; + char *endp; + unsigned char hash[20]; + unsigned long n; + char *certid; + int i; + + p = ksba_cert_get_issuer (cert, 0); + if (!p) + return NULL; /* Ooops: No issuer */ + gcry_md_hash_buffer (GCRY_MD_SHA1, hash, p, strlen (p)); + xfree (p); + + serial = ksba_cert_get_serial (cert); + if (!serial) + return NULL; /* oops: no serial number */ + p = serial; + if (*p != '(') + { + log_error ("Ooops: invalid serial number\n"); + xfree (serial); + return NULL; + } + p++; + n = strtoul (p, &endp, 10); + p = endp; + if (*p != ':') + { + log_error ("Ooops: invalid serial number (no colon)\n"); + xfree (serial); + return NULL; + } + p++; + + certid = xtrymalloc ( 40 + 1 + n*2 + 1); + if (!certid) + { + xfree (serial); + return NULL; /* out of core */ + } + + for (i=0, endp = certid; i < 20; i++, endp += 2 ) + sprintf (endp, "%02X", hash[i]); + *endp++ = '.'; + for (i=0; i < n; i++, endp += 2) + sprintf (endp, "%02X", p[i]); + *endp = 0; + + xfree (serial); + return certid; +} + + + + + + diff --git a/sm/gpgsm.c b/sm/gpgsm.c new file mode 100644 index 000000000..c392886ba --- /dev/null +++ b/sm/gpgsm.c @@ -0,0 +1,1458 @@ +/* gpgsm.c - GnuPG for S/MIME + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpgsm.h" +#include +#include /* malloc hooks */ + +#include "../kbx/keybox.h" /* malloc hooks */ +#include "i18n.h" +#include "keydb.h" +#include "sysutils.h" + +enum cmd_and_opt_values { + aNull = 0, + oArmor = 'a', + aDetachedSign = 'b', + aSym = 'c', + aDecrypt = 'd', + aEncr = 'e', + oInteractive = 'i', + oKOption = 'k', + oDryRun = 'n', + oOutput = 'o', + oQuiet = 'q', + oRecipient = 'r', + aSign = 's', + oTextmodeShort= 't', + oUser = 'u', + oVerbose = 'v', + oCompress = 'z', + oNotation = 'N', + oBatch = 500, + aClearsign, + aStore, + aKeygen, + aSignEncr, + aSignKey, + aLSignKey, + aListPackets, + aEditKey, + aDeleteKey, + aImport, + aVerify, + aVerifyFiles, + aListKeys, + aListExternalKeys, + aListSigs, + aListSecretKeys, + aSendKeys, + aRecvKeys, + aExport, + aCheckKeys, /* nyi */ + aServer, + aLearnCard, + aCallDirmngr, + aCallProtectTool, + aPasswd, + + oOptions, + oDebug, + oDebugAll, + oDebugWait, + oDebugNoChainValidation, + oLogFile, + + oEnableSpecialFilenames, + + oAgentProgram, + oDisplay, + oTTYname, + oTTYtype, + oLCctype, + oLCmessages, + + oDirmngrProgram, + oFakedSystemTime, + + + oAssumeArmor, + oAssumeBase64, + oAssumeBinary, + + oBase64, + oNoArmor, + + oDisableCRLChecks, + oEnableCRLChecks, + + oIncludeCerts, + oPolicyFile, + oDisablePolicyChecks, + oEnablePolicyChecks, + oAutoIssuerKeyRetrieve, + + + oTextmode, + oFingerprint, + oWithFingerprint, + oAnswerYes, + oAnswerNo, + oKeyring, + oSecretKeyring, + oDefaultKey, + oDefRecipient, + oDefRecipientSelf, + oNoDefRecipient, + oStatusFD, + oNoComment, + oNoVersion, + oEmitVersion, + oCompletesNeeded, + oMarginalsNeeded, + oMaxCertDepth, + oLoadExtension, + oRFC1991, + oOpenPGP, + oCipherAlgo, + oDigestAlgo, + oCompressAlgo, + oCommandFD, + oNoVerbose, + oTrustDBName, + oNoSecmemWarn, + oNoDefKeyring, + oNoGreeting, + oNoTTY, + oNoOptions, + oNoBatch, + oHomedir, + oWithColons, + oWithKeyData, + oSkipVerify, + oCompressKeys, + oCompressSigs, + oAlwaysTrust, + oRunAsShmCP, + oSetFilename, + oSetPolicyURL, + oUseEmbeddedFilename, + oComment, + oDefaultComment, + oThrowKeyid, + oForceV3Sigs, + oForceMDC, + oS2KMode, + oS2KDigest, + oS2KCipher, + oCharset, + oNotDashEscaped, + oEscapeFrom, + oLockOnce, + oLockMultiple, + oLockNever, + oKeyServer, + oEncryptTo, + oNoEncryptTo, + oLoggerFD, + oUtf8Strings, + oNoUtf8Strings, + oDisableCipherAlgo, + oDisablePubkeyAlgo, + oAllowNonSelfsignedUID, + oAllowFreeformUID, + oNoLiteral, + oSetFilesize, + oHonorHttpProxy, + oFastListMode, + oListOnly, + oIgnoreTimeConflict, + oNoRandomSeedFile, + oNoAutoKeyRetrieve, + oUseAgent, + oMergeOnly, + oTryAllSecrets, + oTrustedKey, + oEmuMDEncodeBug, + aDummy + }; + + +static ARGPARSE_OPTS opts[] = { + + { 300, NULL, 0, N_("@Commands:\n ") }, + + { aSign, "sign", 256, N_("|[file]|make a signature")}, + { aClearsign, "clearsign", 256, N_("|[file]|make a clear text signature") }, + { aDetachedSign, "detach-sign", 256, N_("make a detached signature")}, + { aEncr, "encrypt", 256, N_("encrypt data")}, + { aSym, "symmetric", 256, N_("encryption only with symmetric cipher")}, + { aDecrypt, "decrypt", 256, N_("decrypt data (default)")}, + { aVerify, "verify" , 256, N_("verify a signature")}, + { aVerifyFiles, "verify-files" , 256, "@" }, + { aListKeys, "list-keys", 256, N_("list keys")}, + { aListExternalKeys, "list-external-keys", 256, N_("list external keys")}, + { aListSecretKeys, "list-secret-keys", 256, N_("list secret keys")}, + { aListSigs, "list-sigs", 256, N_("list certificate chain")}, + { aListSigs, "check-sigs",256, "@"}, + { oFingerprint, "fingerprint", 256, N_("list keys and fingerprints")}, + { aKeygen, "gen-key", 256, N_("generate a new key pair")}, + { aDeleteKey, "delete-key",256, N_("remove key from the public keyring")}, + { aSendKeys, "send-keys" , 256, N_("export keys to a key server") }, + { aRecvKeys, "recv-keys" , 256, N_("import keys from a key server") }, + { aImport, "import", 256 , N_("import certificates")}, + { aExport, "export", 256 , N_("export certificates")}, + { aLearnCard, "learn-card", 256 ,N_("register a smartcard")}, + { aServer, "server", 256, N_("run in server mode")}, + { aCallDirmngr, "call-dirmngr", 256, N_("pass a command to the dirmngr")}, + { aCallProtectTool, "call-protect-tool", 256, + N_("invoke gpg-protect-tool")}, + { aPasswd, "passwd", 256, N_("change a passphrase")}, + + { 301, NULL, 0, N_("@\nOptions:\n ") }, + + { oArmor, "armor", 0, N_("create ascii armored output")}, + { oArmor, "armour", 0, "@" }, + { oBase64, "base64", 0, N_("create base-64 encoded output")}, + + { oAssumeArmor, "assume-armor", 0, N_("assume input is in PEM format")}, + { oAssumeBase64, "assume-base64", 0, + N_("assume input is in base-64 format")}, + { oAssumeBinary, "assume-binary", 0, + N_("assume input is in binary format")}, + + { oRecipient, "recipient", 2, N_("|NAME|encrypt for NAME")}, + + + { oDisableCRLChecks, "disable-crl-checks", 0, N_("never consult a CRL")}, + { oEnableCRLChecks, "enable-crl-checks", 0, "@"}, + + { oIncludeCerts, "include-certs", 1, + N_("|N|number of certificates to include") }, + + { oPolicyFile, "policy-file", 2, + N_("|FILE|take policy information from FILE") }, + + { oDisablePolicyChecks, "disable-policy-checks", 0, + N_("do not check certificate policies")}, + { oEnablePolicyChecks, "enable-policy-checks", 0, "@"}, + + { oAutoIssuerKeyRetrieve, "auto-issuer-key-retrieve", 0, + N_("fetch missing issuer certificates")}, + +#if 0 + { oDefRecipient, "default-recipient" ,2, + N_("|NAME|use NAME as default recipient")}, + { oDefRecipientSelf, "default-recipient-self" ,0, + N_("use the default key as default recipient")}, + { oNoDefRecipient, "no-default-recipient", 0, "@" }, + { oEncryptTo, "encrypt-to", 2, "@" }, + { oNoEncryptTo, "no-encrypt-to", 0, "@" }, + +#endif + { oUser, "local-user",2, N_("use this user-id to sign or decrypt")}, + +#if 0 + { oCompress, NULL, 1, N_("|N|set compress level N (0 disables)") }, + { oTextmodeShort, NULL, 0, "@"}, + { oTextmode, "textmode", 0, N_("use canonical text mode")}, +#endif + + { oOutput, "output", 2, N_("use as output file")}, + { oVerbose, "verbose", 0, N_("verbose") }, + { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, + { oNoTTY, "no-tty", 0, N_("don't use the terminal at all") }, + { oLogFile, "log-file" ,2, N_("use a log file for the server")}, +#if 0 + { oForceV3Sigs, "force-v3-sigs", 0, N_("force v3 signatures") }, + { oForceMDC, "force-mdc", 0, N_("always use a MDC for encryption") }, +#endif + { oDryRun, "dry-run", 0, N_("do not make any changes") }, + /*{ oInteractive, "interactive", 0, N_("prompt before overwriting") }, */ + /*{ oUseAgent, "use-agent",0, N_("use the gpg-agent")},*/ + { oBatch, "batch", 0, N_("batch mode: never ask")}, + { oAnswerYes, "yes", 0, N_("assume yes on most questions")}, + { oAnswerNo, "no", 0, N_("assume no on most questions")}, + + { oKeyring, "keyring" ,2, N_("add this keyring to the list of keyrings")}, + { oSecretKeyring, "secret-keyring" ,2, N_("add this secret keyring to the list")}, + { oDefaultKey, "default-key" ,2, N_("|NAME|use NAME as default secret key")}, + { oKeyServer, "keyserver",2, N_("|HOST|use this keyserver to lookup keys")}, + { oCharset, "charset" , 2, N_("|NAME|set terminal charset to NAME") }, + { oOptions, "options" , 2, N_("read options from file")}, + + { oDebug, "debug" ,4|16, "@"}, + { oDebugAll, "debug-all" ,0, "@"}, + { oDebugWait, "debug-wait" ,1, "@"}, + { oDebugNoChainValidation, "debug-no-chain-validation" ,0, "@"}, + { oStatusFD, "status-fd" ,1, N_("|FD|write status info to this FD") }, + { aDummy, "no-comment", 0, "@"}, + { aDummy, "completes-needed", 1, "@"}, + { aDummy, "marginals-needed", 1, "@"}, + { oMaxCertDepth, "max-cert-depth", 1, "@" }, + { aDummy, "trusted-key", 2, "@"}, + { oLoadExtension, "load-extension" ,2, + N_("|FILE|load extension module FILE")}, + { aDummy, "rfc1991", 0, "@"}, + { aDummy, "openpgp", 0, "@"}, + { aDummy, "s2k-mode", 1, "@"}, + { aDummy, "s2k-digest-algo",2, "@"}, + { aDummy, "s2k-cipher-algo",2, "@"}, + { oCipherAlgo, "cipher-algo", 2 , N_("|NAME|use cipher algorithm NAME")}, + { oDigestAlgo, "digest-algo", 2 , + N_("|NAME|use message digest algorithm NAME")}, +#if 0 + { oCompressAlgo, "compress-algo", 1 , N_("|N|use compress algorithm N")}, +#endif + { aDummy, "throw-keyid", 0, "@"}, + { aDummy, "notation-data", 2, "@"}, + + { 302, NULL, 0, N_( + "@\n(See the man page for a complete listing of all commands and options)\n" + )}, + + { 303, NULL, 0, N_("@\nExamples:\n\n" + " -se -r Bob [file] sign and encrypt for user Bob\n" + " --clearsign [file] make a clear text signature\n" + " --detach-sign [file] make a detached signature\n" + " --list-keys [names] show keys\n" + " --fingerprint [names] show fingerprints\n" ) }, + + /* hidden options */ + { oNoVerbose, "no-verbose", 0, "@"}, + + { oEnableSpecialFilenames, "enable-special-filenames", 0, "@" }, + + + { oTrustDBName, "trustdb-name", 2, "@" }, + { oNoSecmemWarn, "no-secmem-warning", 0, "@" }, + { oNoArmor, "no-armor", 0, "@"}, + { oNoArmor, "no-armour", 0, "@"}, + { oNoDefKeyring, "no-default-keyring", 0, "@" }, + { oNoGreeting, "no-greeting", 0, "@" }, + { oNoOptions, "no-options", 0, "@" }, /* shortcut for --options /dev/null */ + { oHomedir, "homedir", 2, "@" }, /* defaults to "~/.gnupg" */ + { oAgentProgram, "agent-program", 2 , "@" }, + { oDisplay, "display", 2, "@" }, + { oTTYname, "ttyname", 2, "@" }, + { oTTYtype, "ttytype", 2, "@" }, + { oLCctype, "lc-ctype", 2, "@" }, + { oLCmessages, "lc-messages", 2, "@" }, + { oDirmngrProgram, "dirmngr-program", 2 , "@" }, + { oFakedSystemTime, "faked-system-time", 4, "@" }, /* (epoch time) */ + + + { oNoBatch, "no-batch", 0, "@" }, + { oWithColons, "with-colons", 0, "@"}, + { oWithKeyData,"with-key-data", 0, "@"}, + { aListKeys, "list-key", 0, "@" }, /* alias */ + { aListSigs, "list-sig", 0, "@" }, /* alias */ + { aListSigs, "check-sig",0, "@" }, /* alias */ + { oSkipVerify, "skip-verify",0, "@" }, + { oCompressKeys, "compress-keys",0, "@"}, + { oCompressSigs, "compress-sigs",0, "@"}, + { oAlwaysTrust, "always-trust", 0, "@"}, + { oNoVersion, "no-version", 0, "@"}, + { oLockOnce, "lock-once", 0, "@" }, + { oLockMultiple, "lock-multiple", 0, "@" }, + { oLockNever, "lock-never", 0, "@" }, + { oLoggerFD, "logger-fd",1, "@" }, + { oWithFingerprint, "with-fingerprint", 0, "@" }, + { oDisableCipherAlgo, "disable-cipher-algo", 2, "@" }, + { oDisablePubkeyAlgo, "disable-pubkey-algo", 2, "@" }, + { oHonorHttpProxy,"honor-http-proxy", 0, "@" }, + { oListOnly, "list-only", 0, "@"}, + { oIgnoreTimeConflict, "ignore-time-conflict", 0, "@" }, + { oNoRandomSeedFile, "no-random-seed-file", 0, "@" }, +{0} }; + + + +int gpgsm_errors_seen = 0; + +/* It is possible that we are currentlu running under setuid permissions */ +static int maybe_setuid = 1; + +/* Option --enable-special-filenames */ +static int allow_special_filenames; + + +static char *build_list (const char *text, + const char *(*mapf)(int), int (*chkf)(int)); +static void set_cmd (enum cmd_and_opt_values *ret_cmd, + enum cmd_and_opt_values new_cmd ); + +static void emergency_cleanup (void); +static int check_special_filename (const char *fname); +static int open_read (const char *filename); +static FILE *open_fwrite (const char *filename); +static void run_protect_tool (int argc, char **argv); + + +static int +our_pk_test_algo (int algo) +{ + return 1; +} + +static int +our_cipher_test_algo (int algo) +{ + return 1; +} + +static int +our_md_test_algo (int algo) +{ + return 1; +} + +static const char * +my_strusage( int level ) +{ + static char *digests, *pubkeys, *ciphers; + const char *p; + + switch (level) + { + case 11: p = "gpgsm (GnuPG)"; + break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n"); + break; + case 1: + case 40: p = _("Usage: gpgsm [options] [files] (-h for help)"); + break; + case 41: + p = _("Syntax: gpgsm [options] [files]\n" + "sign, check, encrypt or decrypt using the S/MIME protocol\n" + "default operation depends on the input data\n"); + break; + + case 31: p = "\nHome: "; break; + case 32: p = opt.homedir; break; + case 33: p = _("\nSupported algorithms:\n"); break; + case 34: + if (!ciphers) + ciphers = build_list ("Cipher: ", gcry_cipher_algo_name, + our_cipher_test_algo ); + p = ciphers; + break; + case 35: + if (!pubkeys) + pubkeys = build_list ("Pubkey: ", gcry_pk_algo_name, + our_pk_test_algo ); + p = pubkeys; + break; + case 36: + if (!digests) + digests = build_list("Hash: ", gcry_md_algo_name, our_md_test_algo ); + p = digests; + break; + + default: p = NULL; break; + } + return p; +} + + +static char * +build_list (const char *text, const char * (*mapf)(int), int (*chkf)(int)) +{ + int i; + size_t n=strlen(text)+2; + char *list, *p; + + if (maybe_setuid) { + gcry_control (GCRYCTL_DROP_PRIVS); /* drop setuid */ + } + + for (i=1; i < 110; i++ ) + if (!chkf(i)) + n += strlen(mapf(i)) + 2; + list = xmalloc (21 + n); + *list = 0; + for (p=NULL, i=1; i < 110; i++) + { + if (!chkf(i)) + { + if( !p ) + p = stpcpy (list, text ); + else + p = stpcpy (p, ", "); + p = stpcpy (p, mapf(i) ); + } + } + if (p) + p = stpcpy(p, "\n" ); + return list; +} + + +static void +i18n_init(void) +{ +#ifdef USE_SIMPLE_GETTEXT + set_gettext_file (PACKAGE); +#else +# ifdef ENABLE_NLS +# ifdef HAVE_LC_MESSAGES + setlocale (LC_TIME, ""); + setlocale (LC_MESSAGES, ""); +# else + setlocale (LC_ALL, "" ); +# endif + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); +# endif +#endif +} + + +static void +wrong_args (const char *text) +{ + fputs (_("usage: gpgsm [options] "), stderr); + fputs (text, stderr); + putc ('\n', stderr); + gpgsm_exit (2); +} + + +static void +set_debug(void) +{ + if (opt.debug & DBG_MPI_VALUE) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); + if (opt.debug & DBG_CRYPTO_VALUE ) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); +} + + +static void +set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd) +{ + enum cmd_and_opt_values cmd = *ret_cmd; + + if (!cmd || cmd == new_cmd) + cmd = new_cmd; + else if ( cmd == aSign && new_cmd == aEncr ) + cmd = aSignEncr; + else if ( cmd == aEncr && new_cmd == aSign ) + cmd = aSignEncr; + else if ( (cmd == aSign && new_cmd == aClearsign) + || (cmd == aClearsign && new_cmd == aSign) ) + cmd = aClearsign; + else + { + log_error(_("conflicting commands\n")); + gpgsm_exit(2); + } + + *ret_cmd = cmd; +} + + +int +main ( int argc, char **argv) +{ + ARGPARSE_ARGS pargs; + int orig_argc; + char **orig_argv; + const char *fname; + /* char *username;*/ + int may_coredump; + STRLIST sl, remusr= NULL, locusr=NULL; + STRLIST nrings=NULL; + int detached_sig = 0; + FILE *configfp = NULL; + char *configname = NULL; + unsigned configlineno; + int parse_debug = 0; + int no_more_options = 0; + int default_config =1; + int default_keyring = 1; + char *logfile = NULL; + int greeting = 0; + int nogreeting = 0; + int debug_wait = 0; + int use_random_seed = 1; + int with_fpr = 0; + char *def_digest_string = NULL; + enum cmd_and_opt_values cmd = 0; + struct server_control_s ctrl; + CERTLIST recplist = NULL; + CERTLIST signerlist = NULL; + + /* trap_unaligned ();*/ + set_strusage (my_strusage); + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + /* We don't need any locking in libgcrypt unless we use any kind of + threading. */ + gcry_control (GCRYCTL_DISABLE_INTERNAL_LOCKING); + + /* Please note that we may running SUID(ROOT), so be very CAREFUL + when adding any stuff between here and the call to secmem_init() + somewhere after the option parsing */ + log_set_prefix ("gpgsm", 1); + /* check that the libraries are suitable. Do it here because the + option parse may need services of the library */ + if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) ) + { + log_fatal( _("libgcrypt is too old (need %s, have %s)\n"), + NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) ); + } + if (!ksba_check_version (NEED_KSBA_VERSION) ) + { + log_fatal( _("libksba is too old (need %s, have %s)\n"), + NEED_KSBA_VERSION, ksba_check_version (NULL) ); + } + + gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); + + may_coredump = disable_core_dumps (); + + gnupg_init_signals (0, emergency_cleanup); + + create_dotlock (NULL); /* register locking cleanup */ + i18n_init(); + + opt.def_cipher_algoid = "1.2.840.113549.3.7"; /*des-EDE3-CBC*/ +#ifdef __MINGW32__ + opt.homedir = read_w32_registry_string ( NULL, + "Software\\GNU\\GnuPG", "HomeDir" ); +#else + opt.homedir = getenv ("GNUPGHOME"); +#endif + if (!opt.homedir || !*opt.homedir ) + opt.homedir = GNUPG_DEFAULT_HOMEDIR; + + /* first check whether we have a config file on the commandline */ + orig_argc = argc; + orig_argv = argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */ + while (arg_parse( &pargs, opts)) + { + if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll) + parse_debug++; + else if (pargs.r_opt == oOptions) + { /* yes there is one, so we do not try the default one but + read the config file when it is encountered at the + commandline */ + default_config = 0; + } + else if (pargs.r_opt == oNoOptions) + default_config = 0; /* --no-options */ + else if (pargs.r_opt == oHomedir) + opt.homedir = pargs.r.ret_str; + else if (pargs.r_opt == aCallProtectTool) + break; /* This break makes sure that --version and --help are + passed to the protect-tool. */ + } + + + /* initialize the secure memory. */ + gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); + maybe_setuid = 0; + + /* + Now we are now working under our real uid + */ + + ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free ); + assuan_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free); + keybox_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free); + + /* Setup a default control structure for command line mode */ + memset (&ctrl, 0, sizeof ctrl); + gpgsm_init_default_ctrl (&ctrl); + ctrl.no_server = 1; + ctrl.status_fd = -1; /* not status output */ + ctrl.autodetect_encoding = 1; + + /* set the default option file */ + if (default_config ) + configname = make_filename (opt.homedir, "gpgsm.conf", NULL); + /* cet the default policy file */ + opt.policy_file = make_filename (opt.homedir, "policies.txt", NULL); + + argc = orig_argc; + argv = orig_argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags = 1; /* do not remove the args */ + + next_pass: + if (configname) { + configlineno = 0; + configfp = fopen (configname, "r"); + if (!configfp) + { + if (default_config) + { + if (parse_debug) + log_info (_("NOTE: no default option file `%s'\n"), configname); + } + else + { + log_error (_("option file `%s': %s\n"), configname, strerror(errno)); + gpgsm_exit(2); + } + xfree(configname); + configname = NULL; + } + if (parse_debug && configname) + log_info (_("reading options from `%s'\n"), configname); + default_config = 0; + } + + while (!no_more_options + && optfile_parse (configfp, configname, &configlineno, &pargs, opts)) + { + switch (pargs.r_opt) + { + case aServer: + opt.batch = 1; + set_cmd (&cmd, aServer); + break; + case aCallDirmngr: + opt.batch = 1; + set_cmd (&cmd, aCallDirmngr); + break; + + case aCallProtectTool: + opt.batch = 1; + set_cmd (&cmd, aCallProtectTool); + no_more_options = 1; /* Stop parsing. */ + break; + + case aCheckKeys: set_cmd (&cmd, aCheckKeys); break; + case aImport: set_cmd (&cmd, aImport); break; + case aSendKeys: set_cmd (&cmd, aSendKeys); break; + case aRecvKeys: set_cmd (&cmd, aRecvKeys); break; + case aExport: set_cmd (&cmd, aExport); break; + case aListKeys: set_cmd (&cmd, aListKeys); break; + case aListExternalKeys: set_cmd (&cmd, aListExternalKeys); break; + case aListSecretKeys: set_cmd (&cmd, aListSecretKeys); break; + case aListSigs: set_cmd (&cmd, aListSigs); break; + + case aLearnCard: set_cmd (&cmd, aLearnCard); break; + + case aPasswd: set_cmd (&cmd, aPasswd); break; + + case aDeleteKey: + set_cmd (&cmd, aDeleteKey); + /*greeting=1;*/ + break; + + case aDetachedSign: + detached_sig = 1; + set_cmd (&cmd, aSign ); + break; + + case aSym: set_cmd (&cmd, aSym); break; + case aDecrypt: set_cmd (&cmd, aDecrypt); break; + case aEncr: set_cmd (&cmd, aEncr); break; + case aSign: set_cmd (&cmd, aSign ); break; + case aKeygen: set_cmd (&cmd, aKeygen); greeting=1; break; + case aClearsign: set_cmd (&cmd, aClearsign); break; + case aVerify: set_cmd (&cmd, aVerify); break; + + + /* output encoding selection */ + case oArmor: + ctrl.create_pem = 1; + break; + case oBase64: + ctrl.create_pem = 0; + ctrl.create_base64 = 1; + break; + case oNoArmor: + ctrl.create_pem = 0; + ctrl.create_base64 = 0; + break; + + /* Input encoding selection */ + case oAssumeArmor: + ctrl.autodetect_encoding = 0; + ctrl.is_pem = 1; + ctrl.is_base64 = 0; + break; + case oAssumeBase64: + ctrl.autodetect_encoding = 0; + ctrl.is_pem = 0; + ctrl.is_base64 = 1; + break; + case oAssumeBinary: + ctrl.autodetect_encoding = 0; + ctrl.is_pem = 0; + ctrl.is_base64 = 0; + break; + + case oDisableCRLChecks: + opt.no_crl_check = 1; + break; + case oEnableCRLChecks: + opt.no_crl_check = 0; + break; + + case oIncludeCerts: ctrl.include_certs = pargs.r.ret_int; break; + + case oPolicyFile: + xfree (opt.policy_file); + if (*pargs.r.ret_str) + opt.policy_file = xstrdup (pargs.r.ret_str); + else + opt.policy_file = NULL; + break; + + case oDisablePolicyChecks: + opt.no_policy_check = 1; + break; + case oEnablePolicyChecks: + opt.no_policy_check = 0; + break; + + case oAutoIssuerKeyRetrieve: + opt.auto_issuer_key_retrieve = 1; + break; + + case oOutput: opt.outfile = pargs.r.ret_str; break; + + + case oQuiet: opt.quiet = 1; break; + case oNoTTY: /* fixme:tty_no_terminal(1);*/ break; + case oDryRun: opt.dry_run = 1; break; + + case oVerbose: + opt.verbose++; + gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); + break; + case oNoVerbose: + opt.verbose = 0; + gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); + break; + + case oLogFile: logfile = pargs.r.ret_str; break; + + case oBatch: + opt.batch = 1; + greeting = 0; + break; + case oNoBatch: opt.batch = 0; break; + + case oAnswerYes: opt.answer_yes = 1; break; + case oAnswerNo: opt.answer_no = 1; break; + + case oKeyring: append_to_strlist (&nrings, pargs.r.ret_str); break; + + case oDebug: opt.debug |= pargs.r.ret_ulong; break; + case oDebugAll: opt.debug = ~0; break; + case oDebugWait: debug_wait = pargs.r.ret_int; break; + case oDebugNoChainValidation: opt.no_chain_validation = 1; break; + + case oStatusFD: ctrl.status_fd = pargs.r.ret_int; break; + case oLoggerFD: log_set_fd (pargs.r.ret_int ); break; + case oWithFingerprint: + with_fpr=1; /*fall thru*/ + case oFingerprint: + opt.fingerprint++; + break; + + case oOptions: + /* config files may not be nested (silently ignore them) */ + if (!configfp) + { + xfree(configname); + configname = xstrdup (pargs.r.ret_str); + goto next_pass; + } + break; + case oNoOptions: break; /* no-options */ + case oHomedir: opt.homedir = pargs.r.ret_str; break; + case oAgentProgram: opt.agent_program = pargs.r.ret_str; break; + case oDisplay: opt.display = xstrdup (pargs.r.ret_str); break; + case oTTYname: opt.ttyname = xstrdup (pargs.r.ret_str); break; + case oTTYtype: opt.ttytype = xstrdup (pargs.r.ret_str); break; + case oLCctype: opt.lc_ctype = xstrdup (pargs.r.ret_str); break; + case oLCmessages: opt.lc_messages = xstrdup (pargs.r.ret_str); break; + case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break; + + case oFakedSystemTime: + gnupg_set_time ( (time_t)pargs.r.ret_ulong, 0); + break; + + case oNoDefKeyring: default_keyring = 0; break; + case oNoGreeting: nogreeting = 1; break; + + case oDefaultKey: + /* fixme:opt.def_secret_key = pargs.r.ret_str;*/ + break; + case oDefRecipient: + if (*pargs.r.ret_str) + opt.def_recipient = xstrdup (pargs.r.ret_str); + break; + case oDefRecipientSelf: + xfree (opt.def_recipient); + opt.def_recipient = NULL; + opt.def_recipient_self = 1; + break; + case oNoDefRecipient: + xfree (opt.def_recipient); + opt.def_recipient = NULL; + opt.def_recipient_self = 0; + break; + + case oWithKeyData: opt.with_key_data=1; /* fall thru */ + case oWithColons: ctrl.with_colons = 1; break; + + case oSkipVerify: opt.skip_verify=1; break; + + case oNoEncryptTo: /*fixme: opt.no_encrypt_to = 1;*/ break; + case oEncryptTo: /* store the recipient in the second list */ + sl = add_to_strlist (&remusr, pargs.r.ret_str); + sl->flags = 1; + break; + + case oRecipient: /* store the recipient */ + add_to_strlist ( &remusr, pargs.r.ret_str); + break; + + case oTextmodeShort: /*fixme:opt.textmode = 2;*/ break; + case oTextmode: /*fixme:opt.textmode=1;*/ break; + + case oUser: /* store the local users, the first one is the default */ + if (!opt.local_user) + opt.local_user = pargs.r.ret_str; + add_to_strlist (&locusr, pargs.r.ret_str); + break; + + case oNoSecmemWarn: + gcry_control (GCRYCTL_DISABLE_SECMEM_WARN); + break; + + case oCipherAlgo: + opt.def_cipher_algoid = pargs.r.ret_str; + break; + + case oDisableCipherAlgo: + { + int algo = gcry_cipher_map_name (pargs.r.ret_str); + gcry_cipher_ctl (NULL, GCRYCTL_DISABLE_ALGO, &algo, sizeof algo); + } + break; + case oDisablePubkeyAlgo: + { + int algo = gcry_pk_map_name (pargs.r.ret_str); + gcry_pk_ctl (GCRYCTL_DISABLE_ALGO,&algo, sizeof algo ); + } + break; + + case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break; + case oNoRandomSeedFile: use_random_seed = 0; break; + + case oEnableSpecialFilenames: allow_special_filenames =1; break; + + + case aDummy: + break; + default: + pargs.err = configfp? 1:2; + break; + } + } + + if (configfp) + { + fclose (configfp); + configfp = NULL; + xfree (configname); + configname = NULL; + goto next_pass; + } + + xfree (configname); + configname = NULL; + + if (log_get_errorcount(0)) + gpgsm_exit(2); + + if (nogreeting) + greeting = 0; + + if (greeting) + { + fprintf(stderr, "%s %s; %s\n", + strusage(11), strusage(13), strusage(14) ); + fprintf(stderr, "%s\n", strusage(15) ); + } +# ifdef IS_DEVELOPMENT_VERSION + if (!opt.batch) + { + log_info ("NOTE: THIS IS A DEVELOPMENT VERSION!\n"); + log_info ("It is only intended for test purposes and should NOT be\n"); + log_info ("used in a production environment or with production keys!\n"); + } +# endif + + if (may_coredump && !opt.quiet) + log_info (_("WARNING: program may create a core file!\n")); + + if (logfile && cmd == aServer) + { + log_set_file (logfile); + log_set_prefix (NULL, 1|2|4); + } + + if (gnupg_faked_time_p ()) + { + log_info (_("WARNING: running with faked system time: ")); + gpgsm_dump_time (gnupg_get_time ()); + log_printf ("\n"); + } + +/*FIXME if (opt.batch) */ +/* tty_batchmode (1); */ + + gcry_control (GCRYCTL_RESUME_SECMEM_WARN); + + set_debug (); + + /* FIXME: should set filenames of libgcrypt explicitly + * gpg_opt_homedir = opt.homedir; */ + + /* must do this after dropping setuid, because the mapping functions + may try to load an module and we may have disabled an algorithm */ + if ( !gcry_cipher_map_name (opt.def_cipher_algoid) + || !gcry_cipher_mode_from_oid (opt.def_cipher_algoid)) + log_error (_("selected cipher algorithm is invalid\n")); + + if (def_digest_string) + { + opt.def_digest_algo = gcry_md_map_name (def_digest_string); + xfree (def_digest_string); + def_digest_string = NULL; + if (our_md_test_algo(opt.def_digest_algo) ) + log_error (_("selected digest algorithm is invalid\n")); + } + + if (log_get_errorcount(0)) + gpgsm_exit(2); + + /* set the random seed file */ + if (use_random_seed) { + char *p = make_filename (opt.homedir, "random_seed", NULL); + gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p); + xfree(p); + } + + + if (!cmd && opt.fingerprint && !with_fpr) + set_cmd (&cmd, aListKeys); + + if (!nrings && default_keyring) /* add default keybox */ + keydb_add_resource ("pubring.kbx", 0, 0); + for (sl = nrings; sl; sl = sl->next) + keydb_add_resource (sl->d, 0, 0); + FREE_STRLIST(nrings); + + + for (sl = locusr; sl; sl = sl->next) + { + int rc = gpgsm_add_to_certlist (&ctrl, sl->d, 1, &signerlist); + if (rc) + { + log_error (_("can't sign using `%s': %s\n"), + sl->d, gpg_strerror (rc)); + gpgsm_status2 (&ctrl, STATUS_INV_RECP, + gpg_err_code (rc) == -1? "1": + gpg_err_code (rc) == GPG_ERR_NO_PUBKEY? "1": + gpg_err_code (rc) == GPG_ERR_AMBIGUOUS_NAME? "2": + gpg_err_code (rc) == GPG_ERR_WRONG_KEY_USAGE? "3": + gpg_err_code (rc) == GPG_ERR_CERT_REVOKED? "4": + gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED? "5": + gpg_err_code (rc) == GPG_ERR_NO_CRL_KNOWN? "6": + gpg_err_code (rc) == GPG_ERR_CRL_TOO_OLD? "7": + gpg_err_code (rc) == GPG_ERR_NO_POLICY_MATCH? "8": + gpg_err_code (rc) == GPG_ERR_NO_SECKEY? "9": + "0", + sl->d, NULL); + } + } + for (sl = remusr; sl; sl = sl->next) + { + int rc = gpgsm_add_to_certlist (&ctrl, sl->d, 0, &recplist); + if (rc) + { + log_error (_("can't encrypt to `%s': %s\n"), + sl->d, gpg_strerror (rc)); + gpgsm_status2 (&ctrl, STATUS_INV_RECP, + gpg_err_code (rc) == -1? "1": + gpg_err_code (rc) == GPG_ERR_NO_PUBKEY? "1": + gpg_err_code (rc) == GPG_ERR_AMBIGUOUS_NAME? "2": + gpg_err_code (rc) == GPG_ERR_WRONG_KEY_USAGE? "3": + gpg_err_code (rc) == GPG_ERR_CERT_REVOKED? "4": + gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED? "5": + gpg_err_code (rc) == GPG_ERR_NO_CRL_KNOWN? "6": + gpg_err_code (rc) == GPG_ERR_CRL_TOO_OLD? "7": + gpg_err_code (rc) == GPG_ERR_NO_POLICY_MATCH? "8": + "0", + sl->d, NULL); + } + } + if (log_get_errorcount(0)) + gpgsm_exit(1); /* must stop for invalid recipients */ + + + + fname = argc? *argv : NULL; + + switch (cmd) + { + case aServer: + if (debug_wait) + { + log_debug ("waiting for debugger - my pid is %u .....\n", + (unsigned int)getpid()); + sleep (debug_wait); + log_debug ("... okay\n"); + } + gpgsm_server (); + break; + + case aCallDirmngr: + if (!argc) + wrong_args (_("--call-dirmngr {args}")); + else + if (gpgsm_dirmngr_run_command (&ctrl, *argv, argc-1, argv+1)) + gpgsm_exit (1); + break; + + case aCallProtectTool: + run_protect_tool (argc, argv); + break; + + case aEncr: /* encrypt the given file */ + if (!argc) + gpgsm_encrypt (&ctrl, recplist, 0, stdout); /* from stdin */ + else if (argc == 1) + gpgsm_encrypt (&ctrl, recplist, open_read (*argv), stdout); /* from file */ + else + wrong_args (_("--encrypt [datafile]")); + break; + + case aSign: /* sign the given file */ + /* FIXME: We don't handle --output yet. We should also allow + to concatenate multiple files for signing because that is + what gpg does.*/ + if (!argc) + gpgsm_sign (&ctrl, signerlist, + 0, detached_sig, stdout); /* create from stdin */ + else if (argc == 1) + gpgsm_sign (&ctrl, signerlist, + open_read (*argv), detached_sig, stdout); /* from file */ + else + wrong_args (_("--sign [datafile]")); + break; + + case aSignEncr: /* sign and encrypt the given file */ + log_error ("this command has not yet been implemented\n"); + break; + + case aClearsign: /* make a clearsig */ + log_error ("this command has not yet been implemented\n"); + break; + + case aVerify: + { + FILE *fp = NULL; + + if (argc == 2 && opt.outfile) + log_info ("option --output ignored for a detached signature\n"); + else if (opt.outfile) + fp = open_fwrite (opt.outfile); + + if (!argc) + gpgsm_verify (&ctrl, 0, -1, fp); /* normal signature from stdin */ + else if (argc == 1) + gpgsm_verify (&ctrl, open_read (*argv), -1, fp); /* std signature */ + else if (argc == 2) /* detached signature (sig, detached) */ + gpgsm_verify (&ctrl, open_read (*argv), open_read (argv[1]), NULL); + else + wrong_args (_("--verify [signature [detached_data]]")); + + if (fp && fp != stdout) + fclose (fp); + } + break; + + case aVerifyFiles: + log_error ("this command has not yet been implemented\n"); + break; + + case aDecrypt: + if (!argc) + gpgsm_decrypt (&ctrl, 0, stdout); /* from stdin */ + else if (argc == 1) + gpgsm_decrypt (&ctrl, open_read (*argv), stdout); /* from file */ + else + wrong_args (_("--decrypt [filename]")); + break; + + case aDeleteKey: + for (sl=NULL; argc; argc--, argv++) + add_to_strlist (&sl, *argv); + gpgsm_delete (&ctrl, sl); + free_strlist(sl); + break; + + case aListSigs: + ctrl.with_chain = 1; + case aListKeys: + for (sl=NULL; argc; argc--, argv++) + add_to_strlist (&sl, *argv); + gpgsm_list_keys (&ctrl, sl, stdout, (0 | (1<<6))); + free_strlist(sl); + break; + + case aListExternalKeys: + for (sl=NULL; argc; argc--, argv++) + add_to_strlist (&sl, *argv); + gpgsm_list_keys (&ctrl, sl, stdout, (0 | (1<<7))); + free_strlist(sl); + break; + + case aListSecretKeys: + for (sl=NULL; argc; argc--, argv++) + add_to_strlist (&sl, *argv); + gpgsm_list_keys (&ctrl, sl, stdout, (2 | (1<<6))); + free_strlist(sl); + break; + + case aKeygen: /* generate a key */ + log_error ("this function is not yet available from the commandline\n"); + break; + + case aImport: + gpgsm_import_files (&ctrl, argc, argv, open_read); + break; + + case aExport: + for (sl=NULL; argc; argc--, argv++) + add_to_strlist (&sl, *argv); + gpgsm_export (&ctrl, sl, stdout); + free_strlist(sl); + break; + + + case aSendKeys: + case aRecvKeys: + log_error ("this command has not yet been implemented\n"); + break; + + + case aLearnCard: + if (argc) + wrong_args ("--learn-card"); + else + { + int rc = gpgsm_agent_learn (); + if (rc) + log_error ("error learning card: %s\n", gpg_strerror (rc)); + } + break; + + case aPasswd: + if (argc != 1) + wrong_args ("--passwd "); + else + { + int rc; + KsbaCert cert = NULL; + char *grip = NULL; + + rc = gpgsm_find_cert (*argv, &cert); + if (rc) + ; + else if (!(grip = gpgsm_get_keygrip_hexstring (cert))) + rc = gpg_error (GPG_ERR_BUG); + else + rc = gpgsm_agent_passwd (grip); + if (rc) + log_error ("error changing passphrase: %s\n", gpg_strerror (rc)); + xfree (grip); + ksba_cert_release (cert); + } + break; + + default: + log_error ("invalid command (there is no implicit command)\n"); + break; + } + + /* cleanup */ + gpgsm_release_certlist (recplist); + gpgsm_release_certlist (signerlist); + FREE_STRLIST(remusr); + FREE_STRLIST(locusr); + gpgsm_exit(0); + return 8; /*NEVER REACHED*/ +} + +/* Note: This function is used by signal handlers!. */ +static void +emergency_cleanup (void) +{ + gcry_control (GCRYCTL_TERM_SECMEM ); +} + + +void +gpgsm_exit (int rc) +{ + gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE); + if (opt.debug & DBG_MEMSTAT_VALUE) + { + gcry_control( GCRYCTL_DUMP_MEMORY_STATS ); + gcry_control( GCRYCTL_DUMP_RANDOM_STATS ); + } + if (opt.debug) + gcry_control (GCRYCTL_DUMP_SECMEM_STATS ); + emergency_cleanup (); + rc = rc? rc : log_get_errorcount(0)? 2 : gpgsm_errors_seen? 1 : 0; + exit (rc); +} + + +void +gpgsm_init_default_ctrl (struct server_control_s *ctrl) +{ + ctrl->include_certs = 1; /* only include the signer's cert */ +} + + + +/* Check whether the filename has the form "-&nnnn", where n is a + non-zero number. Returns this number or -1 if it is not the case. */ +static int +check_special_filename (const char *fname) +{ + if (allow_special_filenames + && fname && *fname == '-' && fname[1] == '&' ) { + int i; + + fname += 2; + for (i=0; isdigit (fname[i]); i++ ) + ; + if ( !fname[i] ) + return atoi (fname); + } + return -1; +} + + + +/* Open the FILENAME for read and return the filedescriptor. Stop + with an error message in case of problems. "-" denotes stdin and + if special filenames are allowed the given fd is opened instead. */ +static int +open_read (const char *filename) +{ + int fd; + + if (filename[0] == '-' && !filename[1]) + return 0; /* stdin */ + fd = check_special_filename (filename); + if (fd != -1) + return fd; + fd = open (filename, O_RDONLY); + if (fd == -1) + { + log_error (_("can't open `%s': %s\n"), filename, strerror (errno)); + gpgsm_exit (2); + } + return fd; +} + +/* Open FILENAME for fwrite and return the stream. Stop with an error + message in case of problems. "-" denotes stdout and if special + filenames are allowed the given fd is opened instead. Caller must + close the returned stream unless it is stdout. */ +static FILE * +open_fwrite (const char *filename) +{ + int fd; + FILE *fp; + + if (filename[0] == '-' && !filename[1]) + return stdout; + + fd = check_special_filename (filename); + if (fd != -1) + { + fp = fdopen (dup (fd), "wb"); + if (!fp) + { + log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); + gpgsm_exit (2); + } + return fp; + } + fp = fopen (filename, "wb"); + if (!fp) + { + log_error (_("can't open `%s': %s\n"), filename, strerror (errno)); + gpgsm_exit (2); + } + return fp; +} + + +static void +run_protect_tool (int argc, char **argv) +{ + char *pgm = GNUPG_PROTECT_TOOL; + char **av; + int i; + + av = xcalloc (argc+2, sizeof *av); + av[0] = strrchr (pgm, '/'); + if (!av[0]) + av[0] = pgm; + for (i=1; argc; i++, argc--, argv++) + av[i] = *argv; + av[i] = NULL; + execv (pgm, av); + log_error ("error executing `%s': %s\n", pgm, strerror (errno)); + gpgsm_exit (2); +} + diff --git a/sm/gpgsm.h b/sm/gpgsm.h new file mode 100644 index 000000000..f996d578c --- /dev/null +++ b/sm/gpgsm.h @@ -0,0 +1,274 @@ +/* gpgsm.h - Global definitions for GpgSM + * Copyright (C) 2001, 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef GPGSM_H +#define GPGSM_H + +#ifdef GPG_ERR_SOURCE_DEFAULT +#error GPG_ERR_SOURCE_DEFAULT already defined +#endif +#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_GPGSM +#include + +#include +#include "../common/util.h" +#include "../common/errors.h" + +#define OUT_OF_CORE(a) (gpg_error (gpg_err_code_from_errno ((a)))) + +#define MAX_DIGEST_LEN 24 + +/* A large struct name "opt" to keep global flags */ +struct { + unsigned int debug; /* debug flags (DBG_foo_VALUE) */ + int verbose; /* verbosity level */ + int quiet; /* be as quiet as possible */ + int batch; /* run in batch mode, i.e w/o any user interaction */ + int answer_yes; /* assume yes on most questions */ + int answer_no; /* assume no on most questions */ + int dry_run; /* don't change any persistent data */ + + const char *homedir; /* configuration directory name */ + const char *agent_program; + char *display; + char *ttyname; + char *ttytype; + char *lc_ctype; + char *lc_messages; + + const char *dirmngr_program; + char *outfile; /* name of output file */ + + int with_key_data;/* include raw key in the column delimted output */ + + int fingerprint; /* list fingerprints in all key listings */ + + int armor; /* force base64 armoring (see also ctrl.with_base64) */ + int no_armor; /* don't try to figure out whether data is base64 armored*/ + + const char *def_cipher_algoid; /* cipher algorithm to use if + nothing else is specified */ + + int def_digest_algo; /* Ditto for hash algorithm */ + int def_compress_algo; /* Ditto for compress algorithm */ + + char *def_recipient; /* userID of the default recipient */ + int def_recipient_self; /* The default recipient is the default key */ + + char *local_user; /* NULL or argument to -u */ + + int always_trust; /* Trust the given keys even if there is no + valid certification chain */ + int skip_verify; /* do not check signatures on data */ + + int lock_once; /* Keep lock once they are set */ + + int ignore_time_conflict; /* Ignore certain time conflicts */ + + int no_crl_check; /* Don't do a CRL check */ + + char *policy_file; /* full pathname of policy file */ + int no_policy_check; /* ignore certificate policies */ + int no_chain_validation; /* Bypass all cert chain validity tests */ + + int auto_issuer_key_retrieve; /* try to retrieve a missing issuer key. */ +} opt; + + +#define DBG_X509_VALUE 1 /* debug x.509 data reading/writing */ +#define DBG_MPI_VALUE 2 /* debug mpi details */ +#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ +#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */ +#define DBG_CACHE_VALUE 64 /* debug the caching */ +#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */ +#define DBG_HASHING_VALUE 512 /* debug hashing operations */ +#define DBG_ASSUAN_VALUE 1024 /* debug assuan communication */ + +#define DBG_X509 (opt.debug & DBG_X509_VALUE) +#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) +#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE) +#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE) +#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE) +#define DBG_ASSUAN (opt.debug & DBG_ASSUAN_VALUE) + +struct server_local_s; + +/* Note that the default values for this are set by + gpgsm_init_default_ctrl() */ +struct server_control_s { + int no_server; /* we are not running under server control */ + int status_fd; /* only for non-server mode */ + struct server_local_s *server_local; + int with_colons; /* use column delimited output format */ + int with_chain; /* include the certifying certs in a listing */ + + int autodetect_encoding; /* try to detect the input encoding */ + int is_pem; /* Is in PEM format */ + int is_base64; /* is in plain base-64 format */ + + int create_base64; /* Create base64 encoded output */ + int create_pem; /* create PEM output */ + const char *pem_name; /* PEM name to use */ + + int include_certs; /* -1 to send all certificates in the chain + along with a signature or the number of + certificates up the chain (0 = none, 1 = only + signer) */ +}; +typedef struct server_control_s *CTRL; + +/* data structure used in base64.c */ +typedef struct base64_context_s *Base64Context; + + +struct certlist_s { + struct certlist_s *next; + KsbaCert cert; +}; +typedef struct certlist_s *CERTLIST; + +/*-- gpgsm.c --*/ +void gpgsm_exit (int rc); +void gpgsm_init_default_ctrl (struct server_control_s *ctrl); + +/*-- server.c --*/ +void gpgsm_server (void); +void gpgsm_status (CTRL ctrl, int no, const char *text); +void gpgsm_status2 (CTRL ctrl, int no, ...); +void gpgsm_status_with_err_code (CTRL ctrl, int no, const char *text, + gpg_err_code_t ec); + +/*-- fingerprint --*/ +char *gpgsm_get_fingerprint (KsbaCert cert, int algo, char *array, int *r_len); +char *gpgsm_get_fingerprint_string (KsbaCert cert, int algo); +char *gpgsm_get_fingerprint_hexstring (KsbaCert cert, int algo); +unsigned long gpgsm_get_short_fingerprint (KsbaCert cert); +char *gpgsm_get_keygrip (KsbaCert cert, char *array); +char *gpgsm_get_keygrip_hexstring (KsbaCert cert); +char *gpgsm_get_certid (KsbaCert cert); + + +/*-- base64.c --*/ +int gpgsm_create_reader (Base64Context *ctx, + CTRL ctrl, FILE *fp, KsbaReader *r_reader); +void gpgsm_destroy_reader (Base64Context ctx); +int gpgsm_create_writer (Base64Context *ctx, + CTRL ctrl, FILE *fp, KsbaWriter *r_writer); +int gpgsm_finish_writer (Base64Context ctx); +void gpgsm_destroy_writer (Base64Context ctx); + + +/*-- certdump.c --*/ +void gpgsm_print_serial (FILE *fp, KsbaConstSexp p); +void gpgsm_print_time (FILE *fp, time_t t); +void gpgsm_print_name (FILE *fp, const char *string); + +void gpgsm_dump_cert (const char *text, KsbaCert cert); +void gpgsm_dump_serial (KsbaConstSexp p); +void gpgsm_dump_time (time_t t); +void gpgsm_dump_string (const char *string); + + + +/*-- certcheck.c --*/ +int gpgsm_check_cert_sig (KsbaCert issuer_cert, KsbaCert cert); +int gpgsm_check_cms_signature (KsbaCert cert, KsbaConstSexp sigval, + gcry_md_hd_t md, int hash_algo); +/* fixme: move create functions to another file */ +int gpgsm_create_cms_signature (KsbaCert cert, gcry_md_hd_t md, int mdalgo, + char **r_sigval); + + +/*-- certchain.c --*/ +int gpgsm_walk_cert_chain (KsbaCert start, KsbaCert *r_next); +int gpgsm_is_root_cert (KsbaCert cert); +int gpgsm_validate_chain (CTRL ctrl, KsbaCert cert, time_t *r_exptime); +int gpgsm_basic_cert_check (KsbaCert cert); + +/*-- certlist.c --*/ +int gpgsm_cert_use_sign_p (KsbaCert cert); +int gpgsm_cert_use_encrypt_p (KsbaCert cert); +int gpgsm_cert_use_verify_p (KsbaCert cert); +int gpgsm_cert_use_decrypt_p (KsbaCert cert); +int gpgsm_cert_use_cert_p (KsbaCert cert); +int gpgsm_add_to_certlist (CTRL ctrl, const char *name, int secret, + CERTLIST *listaddr); +void gpgsm_release_certlist (CERTLIST list); +int gpgsm_find_cert (const char *name, KsbaCert *r_cert); + +/*-- keylist.c --*/ +void gpgsm_list_keys (CTRL ctrl, STRLIST names, FILE *fp, unsigned int mode); + +/*-- import.c --*/ +int gpgsm_import (CTRL ctrl, int in_fd); +int gpgsm_import_files (CTRL ctrl, int nfiles, char **files, + int (*of)(const char *fname)); + +/*-- export.c --*/ +void gpgsm_export (CTRL ctrl, STRLIST names, FILE *fp); + +/*-- delete.c --*/ +int gpgsm_delete (CTRL ctrl, STRLIST names); + +/*-- verify.c --*/ +int gpgsm_verify (CTRL ctrl, int in_fd, int data_fd, FILE *out_fp); + +/*-- sign.c --*/ +int gpgsm_get_default_cert (KsbaCert *r_cert); +int gpgsm_sign (CTRL ctrl, CERTLIST signerlist, + int data_fd, int detached, FILE *out_fp); + +/*-- encrypt.c --*/ +int gpgsm_encrypt (CTRL ctrl, CERTLIST recplist, int in_fd, FILE *out_fp); + +/*-- decrypt.c --*/ +int gpgsm_decrypt (CTRL ctrl, int in_fd, FILE *out_fp); + +/*-- certreqgen.c --*/ +int gpgsm_genkey (CTRL ctrl, int in_fd, FILE *out_fp); + +/*-- call-agent.c --*/ +int gpgsm_agent_pksign (const char *keygrip, + unsigned char *digest, + size_t digestlen, + int digestalgo, + char **r_buf, size_t *r_buflen); +int gpgsm_agent_pkdecrypt (const char *keygrip, + KsbaConstSexp ciphertext, + char **r_buf, size_t *r_buflen); +int gpgsm_agent_genkey (KsbaConstSexp keyparms, KsbaSexp *r_pubkey); +int gpgsm_agent_istrusted (KsbaCert cert); +int gpgsm_agent_havekey (const char *hexkeygrip); +int gpgsm_agent_marktrusted (KsbaCert cert); +int gpgsm_agent_learn (void); +int gpgsm_agent_passwd (const char *hexkeygrip); + +/*-- call-dirmngr.c --*/ +int gpgsm_dirmngr_isvalid (KsbaCert cert); +int gpgsm_dirmngr_lookup (CTRL ctrl, STRLIST names, + void (*cb)(void*, KsbaCert), void *cb_value); +int gpgsm_dirmngr_run_command (CTRL ctrl, const char *command, + int argc, char **argv); + + + + + +#endif /*GPGSM_H*/ diff --git a/sm/import.c b/sm/import.c new file mode 100644 index 000000000..17dc3d66c --- /dev/null +++ b/sm/import.c @@ -0,0 +1,349 @@ +/* import.c - Import certificates + * Copyright (C) 2001, 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpgsm.h" +#include +#include + +#include "keydb.h" +#include "i18n.h" + +struct stats_s { + unsigned long count; + unsigned long imported; + unsigned long unchanged; + unsigned long not_imported; +}; + + + +static void +print_imported_status (CTRL ctrl, KsbaCert cert) +{ + char *fpr; + + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + gpgsm_status2 (ctrl, STATUS_IMPORTED, fpr, "[X.509]", NULL); + xfree (fpr); +} + + +/* Print an IMPORT_PROBLEM status. REASON is one of: + 0 := "No specific reason given". + 1 := "Invalid Certificate". + 2 := "Issuer Certificate missing". + 3 := "Certificate Chain too long". + 4 := "Error storing certificate". +*/ +static void +print_import_problem (CTRL ctrl, KsbaCert cert, int reason) +{ + char *fpr = NULL; + char buf[25]; + int i; + + sprintf (buf, "%d", reason); + if (cert) + { + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + /* detetect an error (all high) value */ + for (i=0; fpr[i] == 'F'; i++) + ; + if (!fpr[i]) + { + xfree (fpr); + fpr = NULL; + } + } + gpgsm_status2 (ctrl, STATUS_IMPORT_PROBLEM, buf, fpr, NULL); + xfree (fpr); +} + + +void +print_imported_summary (CTRL ctrl, struct stats_s *stats) +{ + char buf[14*25]; + + if (!opt.quiet) + { + log_info (_("total number processed: %lu\n"), stats->count); + if (stats->imported) + { + log_info (_(" imported: %lu"), stats->imported ); + log_printf ("\n"); + } + if (stats->unchanged) + log_info (_(" unchanged: %lu\n"), stats->unchanged); + if (stats->not_imported) + log_info (_(" not imported: %lu\n"), stats->not_imported); + } + + sprintf (buf, "%lu 0 %lu 0 %lu 0 0 0 0 0 0 0 0 %lu", + stats->count, + stats->imported, + stats->unchanged, + stats->not_imported + ); + gpgsm_status (ctrl, STATUS_IMPORT_RES, buf); +} + + + +static void +check_and_store (CTRL ctrl, struct stats_s *stats, KsbaCert cert, int depth) +{ + int rc; + + stats->count++; + if ( depth >= 50 ) + { + log_error (_("certificate chain too long\n")); + stats->not_imported++; + print_import_problem (ctrl, cert, 3); + return; + } + + rc = gpgsm_basic_cert_check (cert); + if (!rc) + { + int existed; + + if (!keydb_store_cert (cert, 0, &existed)) + { + KsbaCert next = NULL; + + if (!existed) + { + print_imported_status (ctrl, cert); + stats->imported++; + } + else + stats->unchanged++; + + if (opt.verbose > 1 && existed) + { + if (depth) + log_info ("issuer certificate already in DB\n"); + else + log_info ("certificate already in DB\n"); + } + else if (opt.verbose && !existed) + { + if (depth) + log_info ("issuer certificate imported\n"); + else + log_info ("certificate imported\n"); + } + /* Now lets walk up the chain and import all certificates up + the chain.*/ + else if (!gpgsm_walk_cert_chain (cert, &next)) + { + check_and_store (ctrl, stats, next, depth+1); + ksba_cert_release (next); + } + } + else + { + log_error (_("error storing certificate\n")); + stats->not_imported++; + print_import_problem (ctrl, cert, 4); + } + } + else + { + log_error (_("basic certificate checks failed - not imported\n")); + stats->not_imported++; + print_import_problem (ctrl, cert, + gpg_err_code (rc) == GPG_ERR_MISSING_CERT? 2 : + gpg_err_code (rc) == GPG_ERR_BAD_CERT? 1 : 0); + } +} + + + + +static int +import_one (CTRL ctrl, struct stats_s *stats, int in_fd) +{ + int rc; + Base64Context b64reader = NULL; + KsbaReader reader; + KsbaCert cert = NULL; + KsbaCMS cms = NULL; + FILE *fp = NULL; + KsbaContentType ct; + + fp = fdopen ( dup (in_fd), "rb"); + if (!fp) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("fdopen() failed: %s\n", strerror (errno)); + goto leave; + } + + rc = gpgsm_create_reader (&b64reader, ctrl, fp, &reader); + if (rc) + { + log_error ("can't create reader: %s\n", gpg_strerror (rc)); + goto leave; + } + + ct = ksba_cms_identify (reader); + if (ct == KSBA_CT_SIGNED_DATA) + { /* This is probably a signed-only message - import the certs */ + KsbaStopReason stopreason; + int i; + + cms = ksba_cms_new (); + if (!cms) + { + rc = gpg_error (GPG_ERR_ENOMEM); + goto leave; + } + + rc = ksba_cms_set_reader_writer (cms, reader, NULL); + if (rc) + { + log_error ("ksba_cms_set_reader_writer failed: %s\n", + ksba_strerror (rc)); + rc = map_ksba_err (rc); + goto leave; + } + + + do + { + rc = ksba_cms_parse (cms, &stopreason); + if (rc) + { + log_error ("ksba_cms_parse failed: %s\n", ksba_strerror (rc)); + rc = map_ksba_err (rc); + goto leave; + } + + if (stopreason == KSBA_SR_BEGIN_DATA) + log_info ("not a certs-only message\n"); + } + while (stopreason != KSBA_SR_READY); + + for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++) + { + check_and_store (ctrl, stats, cert, 0); + ksba_cert_release (cert); + cert = NULL; + } + if (!i) + log_error ("no certificate found\n"); + } + else if (ct == KSBA_CT_NONE) + { /* Failed to identify this message - assume a certificate */ + + cert = ksba_cert_new (); + if (!cert) + { + rc = gpg_error (GPG_ERR_ENOMEM); + goto leave; + } + + rc = ksba_cert_read_der (cert, reader); + if (rc) + { + rc = map_ksba_err (rc); + goto leave; + } + + check_and_store (ctrl, stats, cert, 0); + } + else + { + log_error ("can't extract certificates from input\n"); + rc = gpg_error (GPG_ERR_NO_DATA); + } + + leave: + ksba_cms_release (cms); + ksba_cert_release (cert); + gpgsm_destroy_reader (b64reader); + if (fp) + fclose (fp); + return rc; +} + + +int +gpgsm_import (CTRL ctrl, int in_fd) +{ + int rc; + struct stats_s stats; + + memset (&stats, 0, sizeof stats); + rc = import_one (ctrl, &stats, in_fd); + print_imported_summary (ctrl, &stats); + /* If we never printed an error message do it now so that a command + line invocation will return with an error (log_error keeps a + global errorcount) */ + if (rc && !log_get_errorcount (0)) + log_error (_("error importing certificate: %s\n"), gpg_strerror (rc)); + return rc; +} + + +int +gpgsm_import_files (CTRL ctrl, int nfiles, char **files, + int (*of)(const char *fname)) +{ + int rc = 0; + struct stats_s stats; + + memset (&stats, 0, sizeof stats); + + if (!nfiles) + rc = import_one (ctrl, &stats, 0); + else + { + for (; nfiles && !rc ; nfiles--, files++) + { + int fd = of (*files); + rc = import_one (ctrl, &stats, fd); + close (fd); + if (rc == -1) + rc = 0; + } + } + print_imported_summary (ctrl, &stats); + /* If we never printed an error message do it now so that a command + line invocation will return with an error (log_error keeps a + global errorcount) */ + if (rc && !log_get_errorcount (0)) + log_error (_("error importing certificate: %s\n"), gpg_strerror (rc)); + return rc; +} + + diff --git a/sm/keydb.c b/sm/keydb.c new file mode 100644 index 000000000..fe6556549 --- /dev/null +++ b/sm/keydb.c @@ -0,0 +1,1282 @@ +/* keydb.c - key database dispatcher + * Copyright (C) 2001, 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpgsm.h" +#include "../kbx/keybox.h" +#include "keydb.h" +#include "i18n.h" + +#define DIRSEP_C '/' + +static int active_handles; + +typedef enum { + KEYDB_RESOURCE_TYPE_NONE = 0, + KEYDB_RESOURCE_TYPE_KEYBOX +} KeydbResourceType; +#define MAX_KEYDB_RESOURCES 20 + +struct resource_item { + KeydbResourceType type; + union { + KEYBOX_HANDLE kr; + } u; + void *token; + int secret; + DOTLOCK lockhandle; +}; + +static struct resource_item all_resources[MAX_KEYDB_RESOURCES]; +static int used_resources; + +struct keydb_handle { + int locked; + int found; + int current; + int is_ephemeral; + int used; /* items in active */ + struct resource_item active[MAX_KEYDB_RESOURCES]; +}; + + +static int lock_all (KEYDB_HANDLE hd); +static void unlock_all (KEYDB_HANDLE hd); + + +/* + * Register a resource (which currently may only be a keybox file). + * The first keybox which is added by this function is + * created if it does not exist. + * Note: this function may be called before secure memory is + * available. + */ +int +keydb_add_resource (const char *url, int force, int secret) +{ + static int any_secret, any_public; + const char *resname = url; + char *filename = NULL; + int rc = 0; + FILE *fp; + KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE; + const char *created_fname = NULL; + + /* Do we have an URL? + gnupg-kbx:filename := this is a plain keybox + filename := See what is is, but create as plain keybox. + */ + if (strlen (resname) > 10) + { + if (!strncmp (resname, "gnupg-kbx:", 10) ) + { + rt = KEYDB_RESOURCE_TYPE_KEYBOX; + resname += 10; + } +#if !defined(HAVE_DRIVE_LETTERS) && !defined(__riscos__) + else if (strchr (resname, ':')) + { + log_error ("invalid key resource URL `%s'\n", url ); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } +#endif /* !HAVE_DRIVE_LETTERS && !__riscos__ */ + } + + if (*resname != DIRSEP_C ) + { /* do tilde expansion etc */ + if (strchr(resname, DIRSEP_C) ) + filename = make_filename (resname, NULL); + else + filename = make_filename (opt.homedir, resname, NULL); + } + else + filename = xstrdup (resname); + + if (!force) + force = secret? !any_secret : !any_public; + + /* see whether we can determine the filetype */ + if (rt == KEYDB_RESOURCE_TYPE_NONE) + { + FILE *fp2 = fopen( filename, "rb" ); + + if (fp2) { + u32 magic; + + /* FIXME: check for the keybox magic */ + if (fread( &magic, 4, 1, fp2) == 1 ) + { + if (magic == 0x13579ace || magic == 0xce9a5713) + ; /* GDBM magic - no more support */ + else + rt = KEYDB_RESOURCE_TYPE_KEYBOX; + } + else /* maybe empty: assume ring */ + rt = KEYDB_RESOURCE_TYPE_KEYBOX; + fclose (fp2); + } + else /* no file yet: create ring */ + rt = KEYDB_RESOURCE_TYPE_KEYBOX; + } + + switch (rt) + { + case KEYDB_RESOURCE_TYPE_NONE: + log_error ("unknown type of key resource `%s'\n", url ); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + + case KEYDB_RESOURCE_TYPE_KEYBOX: + fp = fopen (filename, "rb"); + if (!fp && !force) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + goto leave; + } + + if (!fp) + { /* no file */ +#if 0 /* no autocreate of the homedirectory yet */ + { + char *last_slash_in_filename; + + last_slash_in_filename = strrchr (filename, DIRSEP_C); + *last_slash_in_filename = 0; + if (access (filename, F_OK)) + { /* on the first time we try to create the default + homedir and in this case the process will be + terminated, so that on the next invocation can + read the options file in on startup */ + try_make_homedir (filename); + rc = gpg_error (GPG_ERR_FILE_OPEN_ERROR); + *last_slash_in_filename = DIRSEP_C; + goto leave; + } + *last_slash_in_filename = DIRSEP_C; + } +#endif + fp = fopen (filename, "w"); + if (!fp) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + log_error (_("error creating keybox `%s': %s\n"), + filename, strerror(errno)); + goto leave; + } + + if (!opt.quiet) + log_info (_("keybox `%s' created\n"), filename); + created_fname = filename; + } + fclose (fp); + fp = NULL; + /* now register the file */ + { + + void *token = keybox_register_file (filename, secret); + if (!token) + ; /* already registered - ignore it */ + else if (used_resources >= MAX_KEYDB_RESOURCES) + rc = gpg_error (GPG_ERR_RESOURCE_LIMIT); + else + { + all_resources[used_resources].type = rt; + all_resources[used_resources].u.kr = NULL; /* Not used here */ + all_resources[used_resources].token = token; + all_resources[used_resources].secret = secret; + + all_resources[used_resources].lockhandle + = create_dotlock (filename); + if (!all_resources[used_resources].lockhandle) + log_fatal ( _("can't create lock for `%s'\n"), filename); + + used_resources++; + } + } + break; + default: + log_error ("resource type of `%s' not supported\n", url); + rc = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + /* fixme: check directory permissions and print a warning */ + + leave: + if (rc) + log_error ("keyblock resource `%s': %s\n", filename, gpg_strerror(rc)); + else if (secret) + any_secret = 1; + else + any_public = 1; + xfree (filename); + return rc; +} + + +KEYDB_HANDLE +keydb_new (int secret) +{ + KEYDB_HANDLE hd; + int i, j; + + hd = xcalloc (1, sizeof *hd); + hd->found = -1; + + assert (used_resources <= MAX_KEYDB_RESOURCES); + for (i=j=0; i < used_resources; i++) + { + if (!all_resources[i].secret != !secret) + continue; + switch (all_resources[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: /* ignore */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + hd->active[j].type = all_resources[i].type; + hd->active[j].token = all_resources[i].token; + hd->active[j].secret = all_resources[i].secret; + hd->active[j].lockhandle = all_resources[i].lockhandle; + hd->active[j].u.kr = keybox_new (all_resources[i].token, secret); + if (!hd->active[j].u.kr) + { + xfree (hd); + return NULL; /* fixme: release all previously allocated handles*/ + } + j++; + break; + } + } + hd->used = j; + + active_handles++; + return hd; +} + +void +keydb_release (KEYDB_HANDLE hd) +{ + int i; + + if (!hd) + return; + assert (active_handles > 0); + active_handles--; + + unlock_all (hd); + for (i=0; i < hd->used; i++) + { + switch (hd->active[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + keybox_release (hd->active[i].u.kr); + break; + } + } + + xfree (hd); +} + + +/* Return the name of the current resource. This is function first + looks for the last found found, then for the current search + position, and last returns the first available resource. The + returned string is only valid as long as the handle exists. This + function does only return NULL if no handle is specified, in all + other error cases an empty string is returned. */ +const char * +keydb_get_resource_name (KEYDB_HANDLE hd) +{ + int idx; + const char *s = NULL; + + if (!hd) + return NULL; + + if ( hd->found >= 0 && hd->found < hd->used) + idx = hd->found; + else if ( hd->current >= 0 && hd->current < hd->used) + idx = hd->current; + else + idx = 0; + + switch (hd->active[idx].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + s = NULL; + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + s = keybox_get_resource_name (hd->active[idx].u.kr); + break; + } + + return s? s: ""; +} + +/* Switch the handle into ephemeral mode and return the orginal value. */ +int +keydb_set_ephemeral (KEYDB_HANDLE hd, int yes) +{ + int i; + + if (!hd) + return 0; + + yes = !!yes; + if (hd->is_ephemeral != yes) + { + for (i=0; i < hd->used; i++) + { + switch (hd->active[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + keybox_set_ephemeral (hd->active[i].u.kr, yes); + break; + } + } + } + + i = hd->is_ephemeral; + hd->is_ephemeral = yes; + return i; +} + + + +static int +lock_all (KEYDB_HANDLE hd) +{ + int i, rc = 0; + + /* Fixme: This locking scheme may lead to deadlock if the resources + are not added in the same sequence by all processes. We are + cuurently only allowing one resource so it is not a problem. */ + for (i=0; i < hd->used; i++) + { + switch (hd->active[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + if (hd->active[i].lockhandle) + rc = make_dotlock (hd->active[i].lockhandle, -1); + break; + } + if (rc) + break; + } + + if (rc) + { + /* revert the already set locks */ + for (i--; i >= 0; i--) + { + switch (hd->active[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + if (hd->active[i].lockhandle) + release_dotlock (hd->active[i].lockhandle); + break; + } + } + } + else + hd->locked = 1; + + return rc; +} + +static void +unlock_all (KEYDB_HANDLE hd) +{ + int i; + + if (!hd->locked) + return; + + for (i=hd->used-1; i >= 0; i--) + { + switch (hd->active[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + if (hd->active[i].lockhandle) + release_dotlock (hd->active[i].lockhandle); + break; + } + } + hd->locked = 0; +} + + +#if 0 +/* + * Return the last found keybox. Caller must free it. + * The returned keyblock has the kbode flag bit 0 set for the node with + * the public key used to locate the keyblock or flag bit 1 set for + * the user ID node. + */ +int +keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb) +{ + int rc = 0; + + if (!hd) + return G10ERR_INV_ARG; + + if ( hd->found < 0 || hd->found >= hd->used) + return -1; /* nothing found */ + + switch (hd->active[hd->found].type) { + case KEYDB_RESOURCE_TYPE_NONE: + rc = G10ERR_GENERAL; /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_get_keyblock (hd->active[hd->found].u.kr, ret_kb); + break; + } + + return rc; +} + +/* + * update the current keyblock with KB + */ +int +keydb_update_keyblock (KEYDB_HANDLE hd, KBNODE kb) +{ + int rc = 0; + + if (!hd) + return G10ERR_INV_ARG; + + if ( hd->found < 0 || hd->found >= hd->used) + return -1; /* nothing found */ + + if( opt.dry_run ) + return 0; + + rc = lock_all (hd); + if (rc) + return rc; + + switch (hd->active[hd->found].type) { + case KEYDB_RESOURCE_TYPE_NONE: + rc = G10ERR_GENERAL; /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_update_keyblock (hd->active[hd->found].u.kr, kb); + break; + } + + unlock_all (hd); + return rc; +} + + +/* + * Insert a new KB into one of the resources. + */ +int +keydb_insert_keyblock (KEYDB_HANDLE hd, KBNODE kb) +{ + int rc = -1; + int idx; + + if (!hd) + return G10ERR_INV_ARG; + + if( opt.dry_run ) + return 0; + + if ( hd->found >= 0 && hd->found < hd->used) + idx = hd->found; + else if ( hd->current >= 0 && hd->current < hd->used) + idx = hd->current; + else + return G10ERR_GENERAL; + + rc = lock_all (hd); + if (rc) + return rc; + + switch (hd->active[idx].type) { + case KEYDB_RESOURCE_TYPE_NONE: + rc = G10ERR_GENERAL; /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_insert_keyblock (hd->active[idx].u.kr, kb); + break; + } + + unlock_all (hd); + return rc; +} + +#endif /*disabled code*/ + + + +/* + Return the last found keybox. Caller must free it. The returned + keyblock has the kbode flag bit 0 set for the node with the public + key used to locate the keyblock or flag bit 1 set for the user ID + node. */ +int +keydb_get_cert (KEYDB_HANDLE hd, KsbaCert *r_cert) +{ + int rc = 0; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + if ( hd->found < 0 || hd->found >= hd->used) + return -1; /* nothing found */ + + switch (hd->active[hd->found].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + rc = gpg_error (GPG_ERR_GENERAL); /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_get_cert (hd->active[hd->found].u.kr, r_cert); + break; + } + + return rc; +} + +/* + * Insert a new Certificate into one of the resources. + */ +int +keydb_insert_cert (KEYDB_HANDLE hd, KsbaCert cert) +{ + int rc = -1; + int idx; + char digest[20]; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + if (opt.dry_run) + return 0; + + if ( hd->found >= 0 && hd->found < hd->used) + idx = hd->found; + else if ( hd->current >= 0 && hd->current < hd->used) + idx = hd->current; + else + return gpg_error (GPG_ERR_GENERAL); + + rc = lock_all (hd); + if (rc) + return rc; + + gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/ + + switch (hd->active[idx].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + rc = gpg_error (GPG_ERR_GENERAL); + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_insert_cert (hd->active[idx].u.kr, cert, digest); + break; + } + + unlock_all (hd); + return rc; +} + + + +/* update the current keyblock with KB */ +int +keydb_update_cert (KEYDB_HANDLE hd, KsbaCert cert) +{ + int rc = 0; + char digest[20]; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + if ( hd->found < 0 || hd->found >= hd->used) + return -1; /* nothing found */ + + if (opt.dry_run) + return 0; + + rc = lock_all (hd); + if (rc) + return rc; + + gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/ + + switch (hd->active[hd->found].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + rc = gpg_error (GPG_ERR_GENERAL); /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_update_cert (hd->active[hd->found].u.kr, cert, digest); + break; + } + + unlock_all (hd); + return rc; +} + + +/* + * The current keyblock or cert will be deleted. + */ +int +keydb_delete (KEYDB_HANDLE hd) +{ + int rc = -1; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + if ( hd->found < 0 || hd->found >= hd->used) + return -1; /* nothing found */ + + if( opt.dry_run ) + return 0; + + rc = lock_all (hd); + if (rc) + return rc; + + switch (hd->active[hd->found].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + rc = gpg_error (GPG_ERR_GENERAL); + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_delete (hd->active[hd->found].u.kr); + break; + } + + unlock_all (hd); + return rc; +} + + + +/* + * Locate the default writable key resource, so that the next + * operation (which is only relevant for inserts) will be done on this + * resource. + */ +int +keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved) +{ + int rc; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + rc = keydb_search_reset (hd); /* this does reset hd->current */ + if (rc) + return rc; + + for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++) + { + switch (hd->active[hd->current].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + BUG(); + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + if (keybox_is_writable (hd->active[hd->current].token)) + return 0; /* found (hd->current is set to it) */ + break; + } + } + + return -1; +} + +/* + * Rebuild the caches of all key resources. + */ +void +keydb_rebuild_caches (void) +{ + int i; + + for (i=0; i < used_resources; i++) + { + if (all_resources[i].secret) + continue; + switch (all_resources[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: /* ignore */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: +/* rc = keybox_rebuild_cache (all_resources[i].token); */ +/* if (rc) */ +/* log_error (_("failed to rebuild keybox cache: %s\n"), */ +/* g10_errstr (rc)); */ + break; + } + } +} + + + +/* + * Start the next search on this handle right at the beginning + */ +int +keydb_search_reset (KEYDB_HANDLE hd) +{ + int i, rc = 0; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + hd->current = 0; + hd->found = -1; + /* and reset all resources */ + for (i=0; !rc && i < hd->used; i++) + { + switch (hd->active[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_search_reset (hd->active[i].u.kr); + break; + } + } + return rc; /* fixme: we need to map error codes or share them with + all modules*/ +} + +/* + * Search through all keydb resources, starting at the current position, + * for a keyblock which contains one of the keys described in the DESC array. + */ +int +keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc) +{ + int rc = -1; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + while (rc == -1 && hd->current >= 0 && hd->current < hd->used) + { + switch (hd->active[hd->current].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + BUG(); /* we should never see it here */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_search (hd->active[hd->current].u.kr, desc, ndesc); + break; + } + if (rc == -1) /* EOF -> switch to next resource */ + hd->current++; + else if (!rc) + hd->found = hd->current; + } + + return rc; +} + + +int +keydb_search_first (KEYDB_HANDLE hd) +{ + KEYDB_SEARCH_DESC desc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_FIRST; + return keydb_search (hd, &desc, 1); +} + +int +keydb_search_next (KEYDB_HANDLE hd) +{ + KEYDB_SEARCH_DESC desc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_NEXT; + return keydb_search (hd, &desc, 1); +} + +int +keydb_search_kid (KEYDB_HANDLE hd, u32 *kid) +{ + KEYDB_SEARCH_DESC desc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_LONG_KID; +/* desc.u.kid[0] = kid[0]; */ +/* desc.u.kid[1] = kid[1]; */ + return keydb_search (hd, &desc, 1); +} + +int +keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr) +{ + KEYDB_SEARCH_DESC desc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_FPR; + memcpy (desc.u.fpr, fpr, 20); + return keydb_search (hd, &desc, 1); +} + +int +keydb_search_issuer (KEYDB_HANDLE hd, const char *issuer) +{ + KEYDB_SEARCH_DESC desc; + int rc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_ISSUER; + desc.u.name = issuer; + rc = keydb_search (hd, &desc, 1); + return rc; +} + +int +keydb_search_issuer_sn (KEYDB_HANDLE hd, + const char *issuer, KsbaConstSexp serial) +{ + KEYDB_SEARCH_DESC desc; + int rc; + const unsigned char *s; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_ISSUER_SN; + s = serial; + if (*s !='(') + return gpg_error (GPG_ERR_INV_VALUE); + s++; + for (desc.snlen = 0; digitp (s); s++) + desc.snlen = 10*desc.snlen + atoi_1 (s); + if (*s !=':') + return gpg_error (GPG_ERR_INV_VALUE); + desc.sn = s+1; + desc.u.name = issuer; + rc = keydb_search (hd, &desc, 1); + return rc; +} + +int +keydb_search_subject (KEYDB_HANDLE hd, const char *name) +{ + KEYDB_SEARCH_DESC desc; + int rc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_SUBJECT; + desc.u.name = name; + rc = keydb_search (hd, &desc, 1); + return rc; +} + + +static int +hextobyte (const unsigned char *s) +{ + int c; + + if( *s >= '0' && *s <= '9' ) + c = 16 * (*s - '0'); + else if ( *s >= 'A' && *s <= 'F' ) + c = 16 * (10 + *s - 'A'); + else if ( *s >= 'a' && *s <= 'f' ) + c = 16 * (10 + *s - 'a'); + else + return -1; + s++; + if ( *s >= '0' && *s <= '9' ) + c += *s - '0'; + else if ( *s >= 'A' && *s <= 'F' ) + c += 10 + *s - 'A'; + else if ( *s >= 'a' && *s <= 'f' ) + c += 10 + *s - 'a'; + else + return -1; + return c; +} + + +static int +classify_user_id (const char *name, + KEYDB_SEARCH_DESC *desc, + int *force_exact ) +{ + const char *s; + int hexprefix = 0; + int hexlength; + int mode = 0; + + /* clear the structure so that the mode field is set to zero unless + * we set it to the correct value right at the end of this function */ + memset (desc, 0, sizeof *desc); + *force_exact = 0; + /* skip leading spaces. Fixme: what about trailing white space? */ + for(s = name; *s && spacep (s); s++ ) + ; + + switch (*s) + { + case 0: /* empty string is an error */ + return 0; + + case '.': /* an email address, compare from end */ + mode = KEYDB_SEARCH_MODE_MAILEND; + s++; + desc->u.name = s; + break; + + case '<': /* an email address */ + mode = KEYDB_SEARCH_MODE_MAIL; + s++; + desc->u.name = s; + break; + + case '@': /* part of an email address */ + mode = KEYDB_SEARCH_MODE_MAILSUB; + s++; + desc->u.name = s; + break; + + case '=': /* exact compare */ + mode = KEYDB_SEARCH_MODE_EXACT; + s++; + desc->u.name = s; + break; + + case '*': /* case insensitive substring search */ + mode = KEYDB_SEARCH_MODE_SUBSTR; + s++; + desc->u.name = s; + break; + + case '+': /* compare individual words */ + mode = KEYDB_SEARCH_MODE_WORDS; + s++; + desc->u.name = s; + break; + + case '/': /* subject's DN */ + s++; + if (!*s || spacep (s)) + return 0; /* no DN or prefixed with a space */ + desc->u.name = s; + mode = KEYDB_SEARCH_MODE_SUBJECT; + break; + + case '#': + { + const char *si; + + s++; + if ( *s == '/') + { /* "#/" indicates an issuer's DN */ + s++; + if (!*s || spacep (s)) + return 0; /* no DN or prefixed with a space */ + desc->u.name = s; + mode = KEYDB_SEARCH_MODE_ISSUER; + } + else + { /* serialnumber + optional issuer ID */ + for (si=s; *si && *si != '/'; si++) + { + if (!strchr("01234567890abcdefABCDEF", *si)) + return 0; /* invalid digit in serial number*/ + } + desc->sn = s; + desc->snlen = -1; + if (!*si) + mode = KEYDB_SEARCH_MODE_SN; + else + { + s = si+1; + if (!*s || spacep (s)) + return 0; /* no DN or prefixed with a space */ + desc->u.name = s; + mode = KEYDB_SEARCH_MODE_ISSUER_SN; + } + } + } + break; + + case ':': /*Unified fingerprint */ + { + const char *se, *si; + int i; + + se = strchr (++s,':'); + if (!se) + return 0; + for (i=0,si=s; si < se; si++, i++ ) + { + if (!strchr("01234567890abcdefABCDEF", *si)) + return 0; /* invalid digit */ + } + if (i != 32 && i != 40) + return 0; /* invalid length of fpr*/ + for (i=0,si=s; si < se; i++, si +=2) + desc->u.fpr[i] = hextobyte(si); + for (; i < 20; i++) + desc->u.fpr[i]= 0; + s = se + 1; + mode = KEYDB_SEARCH_MODE_FPR; + } + break; + + default: + if (s[0] == '0' && s[1] == 'x') + { + hexprefix = 1; + s += 2; + } + + hexlength = strspn(s, "0123456789abcdefABCDEF"); + if (hexlength >= 8 && s[hexlength] =='!') + { + *force_exact = 1; + hexlength++; /* just for the following check */ + } + + /* check if a hexadecimal number is terminated by EOS or blank */ + if (hexlength && s[hexlength] && !spacep (s+hexlength)) + { + if (hexprefix) /* a "0x" prefix without correct */ + return 0; /* termination is an error */ + /* The first chars looked like a hex number, but really is + not */ + hexlength = 0; + } + + if (*force_exact) + hexlength--; /* remove the bang */ + + if (hexlength == 8 + || (!hexprefix && hexlength == 9 && *s == '0')) + { /* short keyid */ + unsigned long kid; + if (hexlength == 9) + s++; + kid = strtoul( s, NULL, 16 ); + desc->u.kid[4] = kid >> 24; + desc->u.kid[5] = kid >> 16; + desc->u.kid[6] = kid >> 8; + desc->u.kid[7] = kid; + mode = KEYDB_SEARCH_MODE_SHORT_KID; + } + else if (hexlength == 16 + || (!hexprefix && hexlength == 17 && *s == '0')) + { /* complete keyid */ + unsigned long kid0, kid1; + char buf[9]; + if (hexlength == 17) + s++; + mem2str(buf, s, 9 ); + kid0 = strtoul (buf, NULL, 16); + kid1 = strtoul (s+8, NULL, 16); + desc->u.kid[0] = kid0 >> 24; + desc->u.kid[1] = kid0 >> 16; + desc->u.kid[2] = kid0 >> 8; + desc->u.kid[3] = kid0; + desc->u.kid[4] = kid1 >> 24; + desc->u.kid[5] = kid1 >> 16; + desc->u.kid[6] = kid1 >> 8; + desc->u.kid[7] = kid1; + mode = KEYDB_SEARCH_MODE_LONG_KID; + } + else if (hexlength == 32 + || (!hexprefix && hexlength == 33 && *s == '0')) + { /* md5 fingerprint */ + int i; + if (hexlength == 33) + s++; + memset(desc->u.fpr+16, 0, 4); + for (i=0; i < 16; i++, s+=2) + { + int c = hextobyte(s); + if (c == -1) + return 0; + desc->u.fpr[i] = c; + } + mode = KEYDB_SEARCH_MODE_FPR16; + } + else if (hexlength == 40 + || (!hexprefix && hexlength == 41 && *s == '0')) + { /* sha1/rmd160 fingerprint */ + int i; + if (hexlength == 41) + s++; + for (i=0; i < 20; i++, s+=2) + { + int c = hextobyte(s); + if (c == -1) + return 0; + desc->u.fpr[i] = c; + } + mode = KEYDB_SEARCH_MODE_FPR20; + } + else if (!hexprefix) + { + /* The fingerprint in an X.509 listing is often delimited by + colons, so we try to single this case out. */ + mode = 0; + hexlength = strspn (s, ":0123456789abcdefABCDEF"); + if (hexlength == 59 && (!s[hexlength] || spacep (s+hexlength))) + { + int i; + + for (i=0; i < 20; i++, s += 3) + { + int c = hextobyte(s); + if (c == -1 || (i < 19 && s[2] != ':')) + break; + desc->u.fpr[i] = c; + } + if (i == 20) + mode = KEYDB_SEARCH_MODE_FPR20; + } + if (!mode) /* default is substring search */ + { + *force_exact = 0; + desc->u.name = s; + mode = KEYDB_SEARCH_MODE_SUBSTR; + } + } + else + { /* hex number with a prefix but a wrong length */ + return 0; + } + } + + desc->mode = mode; + return mode; +} + + +int +keydb_classify_name (const char *name, KEYDB_SEARCH_DESC *desc) +{ + int dummy; + KEYDB_SEARCH_DESC dummy_desc; + + if (!desc) + desc = &dummy_desc; + + if (!classify_user_id (name, desc, &dummy)) + return gpg_error (GPG_ERR_INV_NAME); + return 0; +} + + +/* Store the certificate in the key DB but make sure that it does not + already exists. We do this simply by comparing the fingerprint. + If EXISTED is not NULL it will be set to true if the certificate + was already in the DB. */ +int +keydb_store_cert (KsbaCert cert, int ephemeral, int *existed) +{ + KEYDB_HANDLE kh; + int rc; + unsigned char fpr[20]; + + if (existed) + *existed = 0; + + if (!gpgsm_get_fingerprint (cert, 0, fpr, NULL)) + { + log_error (_("failed to get the fingerprint\n")); + return gpg_error (GPG_ERR_GENERAL); + } + + kh = keydb_new (0); + if (!kh) + { + log_error (_("failed to allocate keyDB handle\n")); + return gpg_error (GPG_ERR_ENOMEM);; + } + + if (ephemeral) + keydb_set_ephemeral (kh, 1); + + rc = keydb_search_fpr (kh, fpr); + if (rc != -1) + { + keydb_release (kh); + if (!rc) + { + if (existed) + *existed = 1; + return 0; /* okay */ + } + log_error (_("problem looking for existing certificate: %s\n"), + gpg_strerror (rc)); + return rc; + } + + rc = keydb_locate_writable (kh, 0); + if (rc) + { + log_error (_("error finding writable keyDB: %s\n"), gpg_strerror (rc)); + keydb_release (kh); + return rc; + } + + rc = keydb_insert_cert (kh, cert); + if (rc) + { + log_error (_("error storing certificate: %s\n"), gpg_strerror (rc)); + keydb_release (kh); + return rc; + } + keydb_release (kh); + return 0; +} + + + diff --git a/sm/keylist.c b/sm/keylist.c new file mode 100644 index 000000000..634bda292 --- /dev/null +++ b/sm/keylist.c @@ -0,0 +1,617 @@ +/* keylist.c + * Copyright (C) 1998, 1999, 2000, 2001, 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpgsm.h" + +#include +#include + +#include "keydb.h" +#include "i18n.h" + +struct list_external_parm_s { + FILE *fp; + int print_header; + int with_colons; + int with_chain; +}; + + + +static void +print_key_data (KsbaCert cert, FILE *fp) +{ +#if 0 + int n = pk ? pubkey_get_npkey( pk->pubkey_algo ) : 0; + int i; + + for(i=0; i < n; i++ ) + { + fprintf (fp, "pkd:%d:%u:", i, mpi_get_nbits( pk->pkey[i] ) ); + mpi_print(stdout, pk->pkey[i], 1 ); + putchar(':'); + putchar('\n'); + } +#endif +} + +static void +print_capabilities (KsbaCert cert, FILE *fp) +{ + KsbaError err; + unsigned int use; + + err = ksba_cert_get_key_usage (cert, &use); + if (err == KSBA_No_Data) + { + putc ('e', fp); + putc ('s', fp); + putc ('c', fp); + putc ('E', fp); + putc ('S', fp); + putc ('C', fp); + return; + } + if (err) + { + log_error (_("error getting key usage information: %s\n"), + ksba_strerror (err)); + return; + } + + if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT))) + putc ('e', fp); + if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION))) + putc ('s', fp); + if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN)) + putc ('c', fp); + if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT))) + putc ('E', fp); + if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION))) + putc ('S', fp); + if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN)) + putc ('C', fp); +} + + +static void +print_time (time_t t, FILE *fp) +{ + if (!t) + ; + else if ( t == (time_t)(-1) ) + putc ('?', fp); + else + fprintf (fp, "%lu", (unsigned long)t); +} + + +/* return an allocated string with the email address extracted from a + DN */ +static char * +email_kludge (const char *name) +{ + const unsigned char *p; + unsigned char *buf; + int n; + + if (strncmp (name, "1.2.840.113549.1.9.1=#", 22)) + return NULL; + /* This looks pretty much like an email address in the subject's DN + we use this to add an additional user ID entry. This way, + openSSL generated keys get a nicer and usable listing */ + name += 22; + for (n=0, p=name; hexdigitp (p) && hexdigitp (p+1); p +=2, n++) + ; + if (*p != '#' || !n) + return NULL; + buf = xtrymalloc (n+3); + if (!buf) + return NULL; /* oops, out of core */ + *buf = '<'; + for (n=1, p=name; *p != '#'; p +=2, n++) + buf[n] = xtoi_2 (p); + buf[n++] = '>'; + buf[n] = 0; + return buf; +} + + + + +/* List one certificate in colon mode */ +static void +list_cert_colon (KsbaCert cert, FILE *fp, int have_secret) +{ + int idx, trustletter = 0; + char *p; + KsbaSexp sexp; + char *fpr; + + fputs (have_secret? "crs:":"crt:", fp); + trustletter = 0; +#if 0 + if (is_not_valid (cert)) + putc ('i', fp); + else if ( is_revoked (cert) ) + putc ('r', fp); + else if ( has_expired (cert)) + putcr ('e', fp); + else +#endif + { + trustletter = '?'; /*get_validity_info ( pk, NULL );*/ + putc (trustletter, fp); + } + + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + fprintf (fp, ":%u:%d:%s:", + /*keylen_of_cert (cert)*/1024, + /* pubkey_algo_of_cert (cert)*/1, + fpr+24); + + /* we assume --fixed-list-mode for gpgsm */ + print_time ( ksba_cert_get_validity (cert, 0), fp); + putc (':', fp); + print_time ( ksba_cert_get_validity (cert, 1), fp); + putc (':', fp); + /* field 8, serial number: */ + if ((sexp = ksba_cert_get_serial (cert))) + { + int len; + const unsigned char *s = sexp; + + if (*s == '(') + { + s++; + for (len=0; *s && *s != ':' && digitp (s); s++) + len = len*10 + atoi_1 (s); + if (*s == ':') + for (s++; len; len--, s++) + fprintf (fp,"%02X", *s); + } + xfree (sexp); + } + putc (':', fp); + /* field 9, ownertrust - not used here */ + putc (':', fp); + /* field 10, old user ID - we use it here for the issuer DN */ + if ((p = ksba_cert_get_issuer (cert,0))) + { + print_sanitized_string (fp, p, ':'); + xfree (p); + } + putc (':', fp); + /* field 11, signature class - not used */ + putc (':', fp); + /* field 12, capabilities: */ + print_capabilities (cert, fp); + putc (':', fp); + putc ('\n', fp); + + /* FPR record */ + fprintf (fp, "fpr:::::::::%s:::", fpr); + xfree (fpr); fpr = NULL; + /* print chaining ID (field 13)*/ + { + KsbaCert next; + + if (!gpgsm_walk_cert_chain (cert, &next)) + { + p = gpgsm_get_fingerprint_hexstring (next, GCRY_MD_SHA1); + fputs (p, fp); + xfree (p); + ksba_cert_release (next); + } + } + putc (':', fp); + putc ('\n', fp); + + + if (opt.with_key_data) + { + if ( (p = gpgsm_get_keygrip_hexstring (cert))) + { + fprintf (fp, "grp:::::::::%s:\n", p); + xfree (p); + } + print_key_data (cert, fp); + } + + for (idx=0; (p = ksba_cert_get_subject (cert,idx)); idx++) + { + fprintf (fp, "uid:%c::::::::", trustletter); + print_sanitized_string (fp, p, ':'); + putc (':', fp); + putc (':', fp); + putc ('\n', fp); + if (!idx) + { + /* It would be better to get the faked email address from + the keydb. But as long as we don't have a way to pass + the meta data back, we just check it the same way as the + code used to create the keybox meta data does */ + char *pp = email_kludge (p); + if (pp) + { + fprintf (fp, "uid:%c::::::::", trustletter); + print_sanitized_string (fp, pp, ':'); + putc (':', fp); + putc (':', fp); + putc ('\n', fp); + xfree (pp); + } + } + xfree (p); + } +} + + +/* List one certificate in standard mode */ +static void +list_cert_std (KsbaCert cert, FILE *fp, int have_secret) +{ + KsbaError kerr; + KsbaSexp sexp; + char *dn; + time_t t; + int idx; + int is_ca, chainlen; + unsigned int kusage; + char *string, *p; + + sexp = ksba_cert_get_serial (cert); + fputs ("Serial number: ", fp); + gpgsm_print_serial (fp, sexp); + ksba_free (sexp); + putc ('\n', fp); + + dn = ksba_cert_get_issuer (cert, 0); + fputs (" Issuer: ", fp); + gpgsm_print_name (fp, dn); + ksba_free (dn); + putc ('\n', fp); + for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++) + { + fputs (" aka: ", fp); + gpgsm_print_name (fp, dn); + ksba_free (dn); + putc ('\n', fp); + } + + dn = ksba_cert_get_subject (cert, 0); + fputs (" Subject: ", fp); + gpgsm_print_name (fp, dn); + ksba_free (dn); + putc ('\n', fp); + for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++) + { + fputs (" aka: ", fp); + gpgsm_print_name (fp, dn); + ksba_free (dn); + putc ('\n', fp); + } + + t = ksba_cert_get_validity (cert, 0); + fputs (" validity: ", fp); + gpgsm_print_time (fp, t); + fputs (" through ", fp); + t = ksba_cert_get_validity (cert, 1); + gpgsm_print_time (fp, t); + putc ('\n', fp); + + kerr = ksba_cert_get_key_usage (cert, &kusage); + if (kerr != KSBA_No_Data) + { + fputs (" key usage:", fp); + if (kerr) + fprintf (fp, " [error: %s]", ksba_strerror (kerr)); + else + { + if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE)) + fputs (" digitalSignature", fp); + if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION)) + fputs (" nonRepudiation", fp); + if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT)) + fputs (" keyEncipherment", fp); + if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT)) + fputs (" dataEncipherment", fp); + if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT)) + fputs (" keyAgreement", fp); + if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN)) + fputs (" certSign", fp); + if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN)) + fputs (" crlSign", fp); + if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY)) + fputs (" encipherOnly", fp); + if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY)) + fputs (" decipherOnly", fp); + } + putc ('\n', fp); + } + + kerr = ksba_cert_get_cert_policies (cert, &string); + if (kerr != KSBA_No_Data) + { + fputs (" policies: ", fp); + if (kerr) + fprintf (fp, "[error: %s]", ksba_strerror (kerr)); + else + { + for (p=string; *p; p++) + { + if (*p == '\n') + *p = ','; + } + print_sanitized_string (fp, string, 0); + xfree (string); + } + putc ('\n', fp); + } + + kerr = ksba_cert_is_ca (cert, &is_ca, &chainlen); + if (kerr || is_ca) + { + fputs (" chain length: ", fp); + if (kerr) + fprintf (fp, "[error: %s]", ksba_strerror (kerr)); + else if (chainlen == -1) + fputs ("unlimited", fp); + else + fprintf (fp, "%d", chainlen); + putc ('\n', fp); + } + + + dn = gpgsm_get_fingerprint_string (cert, 0); + fprintf (fp, " fingerprint: %s\n", dn?dn:"error"); + xfree (dn); +} + +/* Same as standard mode mode list all certifying certts too */ +static void +list_cert_chain (KsbaCert cert, FILE *fp) +{ + KsbaCert next = NULL; + + list_cert_std (cert, fp, 0); + ksba_cert_ref (cert); + while (!gpgsm_walk_cert_chain (cert, &next)) + { + ksba_cert_release (cert); + fputs ("Certified by\n", fp); + list_cert_std (next, fp, 0); + cert = next; + } + ksba_cert_release (cert); + putc ('\n', fp); +} + + + +/* List all internal keys or just the key given as NAMES. + */ +static void +list_internal_keys (CTRL ctrl, STRLIST names, FILE *fp, unsigned int mode) +{ + KEYDB_HANDLE hd; + KEYDB_SEARCH_DESC *desc = NULL; + STRLIST sl; + int ndesc; + KsbaCert cert = NULL; + int rc=0; + const char *lastresname, *resname; + int have_secret; + + hd = keydb_new (0); + if (!hd) + { + log_error ("keydb_new failed\n"); + goto leave; + } + + if (!names) + ndesc = 1; + else + { + for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++) + ; + } + + desc = xtrycalloc (ndesc, sizeof *desc); + if (!ndesc) + { + log_error ("out of core\n"); + goto leave; + } + + if (!names) + desc[0].mode = KEYDB_SEARCH_MODE_FIRST; + else + { + for (ndesc=0, sl=names; sl; sl = sl->next) + { + rc = keydb_classify_name (sl->d, desc+ndesc); + if (rc) + { + log_error ("key `%s' not found: %s\n", + sl->d, gpg_strerror (rc)); + rc = 0; + } + else + ndesc++; + } + + } + + /* it would be nice to see which of the given users did actually + match one in the keyring. To implement this we need to have a + found flag for each entry in desc and to set this we must check + all those entries after a match to mark all matched one - + currently we stop at the first match. To do this we need an + extra flag to enable this feature so */ + + lastresname = NULL; + while (!(rc = keydb_search (hd, desc, ndesc))) + { + if (!names) + desc[0].mode = KEYDB_SEARCH_MODE_NEXT; + + rc = keydb_get_cert (hd, &cert); + if (rc) + { + log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + resname = keydb_get_resource_name (hd); + + if (lastresname != resname ) + { + int i; + + if (ctrl->no_server) + { + fprintf (fp, "%s\n", resname ); + for (i=strlen(resname); i; i-- ) + putchar('-'); + putc ('\n', fp); + lastresname = resname; + } + } + + have_secret = 0; + if (mode) + { + char *p = gpgsm_get_keygrip_hexstring (cert); + if (p) + { + if (!gpgsm_agent_havekey (p)) + have_secret = 1; + xfree (p); + } + } + + if (!mode + || ((mode & 1) && !have_secret) + || ((mode & 2) && have_secret) ) + { + if (ctrl->with_colons) + list_cert_colon (cert, fp, have_secret); + else if (ctrl->with_chain) + list_cert_chain (cert, fp); + else + { + list_cert_std (cert, fp, have_secret); + putc ('\n', fp); + } + } + ksba_cert_release (cert); + cert = NULL; + } + if (rc && rc != -1) + log_error ("keydb_search failed: %s\n", gpg_strerror (rc)); + + leave: + ksba_cert_release (cert); + xfree (desc); + keydb_release (hd); +} + + + +static void +list_external_cb (void *cb_value, KsbaCert cert) +{ + struct list_external_parm_s *parm = cb_value; + + if (keydb_store_cert (cert, 1, NULL)) + log_error ("error storing certificate as ephemeral\n"); + + if (parm->print_header) + { + const char *resname = "[external keys]"; + int i; + + fprintf (parm->fp, "%s\n", resname ); + for (i=strlen(resname); i; i-- ) + putchar('-'); + putc ('\n', parm->fp); + parm->print_header = 0; + } + + if (parm->with_colons) + list_cert_colon (cert, parm->fp, 0); + else if (parm->with_chain) + list_cert_chain (cert, parm->fp); + else + { + list_cert_std (cert, parm->fp, 0); + putc ('\n', parm->fp); + } +} + + +/* List external keys similar to internal one. Note: mode does not + make sense here because it would be unwise to list external secret + keys */ +static void +list_external_keys (CTRL ctrl, STRLIST names, FILE *fp) +{ + int rc; + struct list_external_parm_s parm; + + parm.fp = fp; + parm.print_header = ctrl->no_server; + parm.with_colons = ctrl->with_colons; + parm.with_chain = ctrl->with_chain; + + rc = gpgsm_dirmngr_lookup (ctrl, names, list_external_cb, &parm); + if (rc) + log_error ("listing external keys failed: %s\n", gpg_strerror (rc)); +} + +/* List all keys or just the key given as NAMES. + MODE controls the operation mode: + Bit 0-2: + 0 = list all public keys but don't flag secret ones + 1 = list only public keys + 2 = list only secret keys + 3 = list secret and public keys + Bit 6: list internal keys + Bit 7: list external keys + */ +void +gpgsm_list_keys (CTRL ctrl, STRLIST names, FILE *fp, unsigned int mode) +{ + if ((mode & (1<<6))) + list_internal_keys (ctrl, names, fp, (mode & 3)); + if ((mode & (1<<7))) + list_external_keys (ctrl, names, fp); +} diff --git a/sm/server.c b/sm/server.c new file mode 100644 index 000000000..dda150964 --- /dev/null +++ b/sm/server.c @@ -0,0 +1,1070 @@ +/* server.c - Server mode and main entry point + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gpgsm.h" + +#define set_error(e,t) assuan_set_error (ctx, ASSUAN_ ## e, (t)) + + +/* The filepointer for status message used in non-server mode */ +static FILE *statusfp; + +/* Data used to assuciate an Assuan context with local server data */ +struct server_local_s { + ASSUAN_CONTEXT assuan_ctx; + int message_fd; + int list_internal; + int list_external; + CERTLIST recplist; + CERTLIST signerlist; +}; + + + +/* note, that it is sufficient to allocate the target string D as + long as the source string S, i.e.: strlen(s)+1; */ +static void +strcpy_escaped_plus (char *d, const unsigned char *s) +{ + while (*s) + { + if (*s == '%' && s[1] && s[2]) + { + s++; + *d++ = xtoi_2 ( s); + s += 2; + } + else if (*s == '+') + *d++ = ' ', s++; + else + *d++ = *s++; + } + *d = 0; +} + + + + +/* Check whether the option NAME appears in LINE */ +static int +has_option (const char *line, const char *name) +{ + const char *s; + int n = strlen (name); + + s = strstr (line, name); + return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n))); +} + + +static void +close_message_fd (CTRL ctrl) +{ + if (ctrl->server_local->message_fd != -1) + { + close (ctrl->server_local->message_fd); + ctrl->server_local->message_fd = -1; + } +} + + +static int +option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value) +{ + CTRL ctrl = assuan_get_pointer (ctx); + + if (!strcmp (key, "include-certs")) + { + int i = *value? atoi (value) : -1; + if (ctrl->include_certs < -2) + return ASSUAN_Parameter_Error; + ctrl->include_certs = i; + } + else if (!strcmp (key, "display")) + { + if (opt.display) + free (opt.display); + opt.display = strdup (value); + if (!opt.display) + return ASSUAN_Out_Of_Core; + } + else if (!strcmp (key, "ttyname")) + { + if (opt.ttyname) + free (opt.ttyname); + opt.ttyname = strdup (value); + if (!opt.ttyname) + return ASSUAN_Out_Of_Core; + } + else if (!strcmp (key, "ttytype")) + { + if (opt.ttytype) + free (opt.ttytype); + opt.ttytype = strdup (value); + if (!opt.ttytype) + return ASSUAN_Out_Of_Core; + } + else if (!strcmp (key, "lc-ctype")) + { + if (opt.lc_ctype) + free (opt.lc_ctype); + opt.lc_ctype = strdup (value); + if (!opt.lc_ctype) + return ASSUAN_Out_Of_Core; + } + else if (!strcmp (key, "lc-messages")) + { + if (opt.lc_messages) + free (opt.lc_messages); + opt.lc_messages = strdup (value); + if (!opt.lc_messages) + return ASSUAN_Out_Of_Core; + } + else if (!strcmp (key, "list-mode")) + { + int i = *value? atoi (value) : 0; + if (!i || i == 1) /* default and mode 1 */ + { + ctrl->server_local->list_internal = 1; + ctrl->server_local->list_external = 0; + } + else if (i == 2) + { + ctrl->server_local->list_internal = 0; + ctrl->server_local->list_external = 1; + } + else if (i == 3) + { + ctrl->server_local->list_internal = 1; + ctrl->server_local->list_external = 1; + } + else + return ASSUAN_Parameter_Error; + } + else + return ASSUAN_Invalid_Option; + + return 0; +} + + + + +static void +reset_notify (ASSUAN_CONTEXT ctx) +{ + CTRL ctrl = assuan_get_pointer (ctx); + + gpgsm_release_certlist (ctrl->server_local->recplist); + gpgsm_release_certlist (ctrl->server_local->signerlist); + ctrl->server_local->recplist = NULL; + ctrl->server_local->signerlist = NULL; + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); +} + + +static void +input_notify (ASSUAN_CONTEXT ctx, const char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + + ctrl->autodetect_encoding = 0; + ctrl->is_pem = 0; + ctrl->is_base64 = 0; + if (strstr (line, "--armor")) + ctrl->is_pem = 1; + else if (strstr (line, "--base64")) + ctrl->is_base64 = 1; + else if (strstr (line, "--binary")) + ; + else + ctrl->autodetect_encoding = 1; +} + +static void +output_notify (ASSUAN_CONTEXT ctx, const char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + + ctrl->create_pem = 0; + ctrl->create_base64 = 0; + if (strstr (line, "--armor")) + ctrl->create_pem = 1; + else if (strstr (line, "--base64")) + ctrl->create_base64 = 1; /* just the raw output */ +} + + + +/* RECIPIENT + + Set the recipient for the encryption. should be the + internal representation of the key; the server may accept any other + way of specification [we will support this]. If this is a valid and + trusted recipient the server does respond with OK, otherwise the + return is an ERR with the reason why the recipient can't be used, + the encryption will then not be done for this recipient. IF the + policy is not to encrypt at all if not all recipients are valid, the + client has to take care of this. All RECIPIENT commands are + cumulative until a RESET or an successful ENCRYPT command. */ +static int +cmd_recipient (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + + rc = gpgsm_add_to_certlist (ctrl, line, 0, &ctrl->server_local->recplist); + if (rc) + { + gpg_err_code_t r = gpg_err_code (rc); + gpgsm_status2 (ctrl, STATUS_INV_RECP, + r == -1? "1": + r == GPG_ERR_NO_PUBKEY? "1": + r == GPG_ERR_AMBIGUOUS_NAME? "2": + r == GPG_ERR_WRONG_KEY_USAGE? "3": + r == GPG_ERR_CERT_REVOKED? "4": + r == GPG_ERR_CERT_EXPIRED? "5": + r == GPG_ERR_NO_CRL_KNOWN? "6": + r == GPG_ERR_CRL_TOO_OLD? "7": + r == GPG_ERR_NO_POLICY_MATCH? "8": + "0", + line, NULL); + } + + return map_to_assuan_status (rc); +} + +/* SIGNER + + Set the signer's keys for the signature creation. should + be the internal representation of the key; the server may accept any + other way of specification [we will support this]. If this is a + valid and usable signing key the server does respond with OK, + otherwise it returns an ERR with the reason why the key can't be + used, the signing will then not be done for this key. If the policy + is not to sign at all if not all signer keys are valid, the client + has to take care of this. All SIGNER commands are cumulative until + a RESET but they are *not* reset by an SIGN command becuase it can + be expected that set of signers are used for more than one sign + operation. + + Note that this command returns an INV_RECP status which is a bit + strange, but they are very similar. */ +static int +cmd_signer (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + + rc = gpgsm_add_to_certlist (ctrl, line, 1, &ctrl->server_local->signerlist); + if (rc) + { + gpg_err_code_t r = gpg_err_code (rc); + gpgsm_status2 (ctrl, STATUS_INV_RECP, + r == -1? "1": + r == GPG_ERR_NO_PUBKEY? "1": + r == GPG_ERR_AMBIGUOUS_NAME? "2": + r == GPG_ERR_WRONG_KEY_USAGE? "3": + r == GPG_ERR_CERT_REVOKED? "4": + r == GPG_ERR_CERT_EXPIRED? "5": + r == GPG_ERR_NO_CRL_KNOWN? "6": + r == GPG_ERR_CRL_TOO_OLD? "7": + r == GPG_ERR_NO_POLICY_MATCH? "8": + r == GPG_ERR_NO_SECKEY? "9": + "0", + line, NULL); + } + return map_to_assuan_status (rc); +} + + +/* ENCRYPT + + Do the actual encryption process. Takes the plaintext from the INPUT + command, writes to the ciphertext to the file descriptor set with + the OUTPUT command, take the recipients form all the recipients set + so far. If this command fails the clients should try to delete all + output currently done or otherwise mark it as invalid. GPGSM does + ensure that there won't be any security problem with leftover data + on the output in this case. + + This command should in general not fail, as all necessary checks + have been done while setting the recipients. The input and output + pipes are closed. */ +static int +cmd_encrypt (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int inp_fd, out_fd; + FILE *out_fp; + int rc; + + inp_fd = assuan_get_input_fd (ctx); + if (inp_fd == -1) + return set_error (No_Input, NULL); + out_fd = assuan_get_output_fd (ctx); + if (out_fd == -1) + return set_error (No_Output, NULL); + + out_fp = fdopen ( dup(out_fd), "w"); + if (!out_fp) + return set_error (General_Error, "fdopen() failed"); + rc = gpgsm_encrypt (assuan_get_pointer (ctx), + ctrl->server_local->recplist, + inp_fd, out_fp); + fclose (out_fp); + + gpgsm_release_certlist (ctrl->server_local->recplist); + ctrl->server_local->recplist = NULL; + /* close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + return map_to_assuan_status (rc); +} + +/* DECRYPT + + This performs the decrypt operation after doing some check on the + internal state. (e.g. that only needed data has been set). Because + it utilizes the GPG-Agent for the session key decryption, there is + no need to ask the client for a protecting passphrase - GpgAgent + does take care of this by requesting this from the user. */ +static int +cmd_decrypt (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int inp_fd, out_fd; + FILE *out_fp; + int rc; + + inp_fd = assuan_get_input_fd (ctx); + if (inp_fd == -1) + return set_error (No_Input, NULL); + out_fd = assuan_get_output_fd (ctx); + if (out_fd == -1) + return set_error (No_Output, NULL); + + out_fp = fdopen ( dup(out_fd), "w"); + if (!out_fp) + return set_error (General_Error, "fdopen() failed"); + rc = gpgsm_decrypt (ctrl, inp_fd, out_fp); + fclose (out_fp); + + /* close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + return map_to_assuan_status (rc); +} + + +/* VERIFY + + This does a verify operation on the message send to the input-FD. + The result is written out using status lines. If an output FD was + given, the signed text will be written to that. + + If the signature is a detached one, the server will inquire about + the signed material and the client must provide it. + */ +static int +cmd_verify (ASSUAN_CONTEXT ctx, char *line) +{ + int rc; + CTRL ctrl = assuan_get_pointer (ctx); + int fd = assuan_get_input_fd (ctx); + int out_fd = assuan_get_output_fd (ctx); + FILE *out_fp = NULL; + + if (fd == -1) + return set_error (No_Input, NULL); + + if (out_fd != -1) + { + out_fp = fdopen ( dup(out_fd), "w"); + if (!out_fp) + return set_error (General_Error, "fdopen() failed"); + } + + rc = gpgsm_verify (assuan_get_pointer (ctx), fd, + ctrl->server_local->message_fd, out_fp); + if (out_fp) + fclose (out_fp); + + /* close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + return map_to_assuan_status (rc); +} + + +/* SIGN [--detached] + + Sign the data set with the INPUT command and write it to the sink + set by OUTPUT. With "--detached" specified, a detached signature is + created (surprise). */ +static int +cmd_sign (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int inp_fd, out_fd; + FILE *out_fp; + int detached; + int rc; + + inp_fd = assuan_get_input_fd (ctx); + if (inp_fd == -1) + return set_error (No_Input, NULL); + out_fd = assuan_get_output_fd (ctx); + if (out_fd == -1) + return set_error (No_Output, NULL); + + detached = has_option (line, "--detached"); + + out_fp = fdopen ( dup(out_fd), "w"); + if (!out_fp) + return set_error (General_Error, "fdopen() failed"); + + rc = gpgsm_sign (assuan_get_pointer (ctx), ctrl->server_local->signerlist, + inp_fd, detached, out_fp); + fclose (out_fp); + + /* close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + return map_to_assuan_status (rc); +} + + +/* IMPORT + + Import the certificates read form the input-fd, return status + message for each imported one. The import checks the validity of + the certificate but not of the entire chain. It is possible to + import expired certificates. */ +static int +cmd_import (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + int fd = assuan_get_input_fd (ctx); + + if (fd == -1) + return set_error (No_Input, NULL); + + rc = gpgsm_import (assuan_get_pointer (ctx), fd); + + /* close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + return map_to_assuan_status (rc); +} + + +static int +cmd_export (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int fd = assuan_get_output_fd (ctx); + FILE *out_fp; + char *p; + STRLIST list, sl; + + if (fd == -1) + return set_error (No_Output, NULL); + + /* break the line down into an STRLIST */ + list = NULL; + for (p=line; *p; line = p) + { + while (*p && *p != ' ') + p++; + if (*p) + *p++ = 0; + if (*line) + { + sl = xtrymalloc (sizeof *sl + strlen (line)); + if (!sl) + { + free_strlist (list); + return ASSUAN_Out_Of_Core; + } + sl->flags = 0; + strcpy_escaped_plus (sl->d, line); + sl->next = list; + list = sl; + } + } + + out_fp = fdopen ( dup(fd), "w"); + if (!out_fp) + { + free_strlist (list); + return set_error (General_Error, "fdopen() failed"); + } + + gpgsm_export (ctrl, list, out_fp); + fclose (out_fp); + free_strlist (list); + /* close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + return 0; +} + + +static int +cmd_delkeys (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + char *p; + STRLIST list, sl; + int rc; + + /* break the line down into an STRLIST */ + list = NULL; + for (p=line; *p; line = p) + { + while (*p && *p != ' ') + p++; + if (*p) + *p++ = 0; + if (*line) + { + sl = xtrymalloc (sizeof *sl + strlen (line)); + if (!sl) + { + free_strlist (list); + return ASSUAN_Out_Of_Core; + } + sl->flags = 0; + strcpy_escaped_plus (sl->d, line); + sl->next = list; + list = sl; + } + } + + rc = gpgsm_delete (ctrl, list); + free_strlist (list); + + /* close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + return map_to_assuan_status (rc); +} + + + +/* MESSAGE FD= + + Set the file descriptor to read a message which is used with + detached signatures */ +static int +cmd_message (ASSUAN_CONTEXT ctx, char *line) +{ + char *endp; + int fd; + CTRL ctrl = assuan_get_pointer (ctx); + + if (strncmp (line, "FD=", 3)) + return set_error (Syntax_Error, "FD= expected"); + line += 3; + if (!digitp (line)) + return set_error (Syntax_Error, "number required"); + fd = strtoul (line, &endp, 10); + if (*endp) + return set_error (Syntax_Error, "garbage found"); + if (fd == -1) + return set_error (No_Input, NULL); + + ctrl->server_local->message_fd = fd; + return 0; +} + + +static int +do_listkeys (ASSUAN_CONTEXT ctx, char *line, int mode) +{ + CTRL ctrl = assuan_get_pointer (ctx); + FILE *fp = assuan_get_data_fp (ctx); + char *p; + STRLIST list, sl; + unsigned int listmode; + + if (!fp) + return set_error (General_Error, "no data stream"); + + /* break the line down into an STRLIST */ + list = NULL; + for (p=line; *p; line = p) + { + while (*p && *p != ' ') + p++; + if (*p) + *p++ = 0; + if (*line) + { + sl = xtrymalloc (sizeof *sl + strlen (line)); + if (!sl) + { + free_strlist (list); + return ASSUAN_Out_Of_Core; + } + sl->flags = 0; + strcpy_escaped_plus (sl->d, line); + sl->next = list; + list = sl; + } + } + + ctrl->with_colons = 1; + listmode = mode; + if (ctrl->server_local->list_internal) + listmode |= (1<<6); + if (ctrl->server_local->list_external) + listmode |= (1<<7); + gpgsm_list_keys (assuan_get_pointer (ctx), list, fp, listmode); + free_strlist (list); + return 0; +} + +static int +cmd_listkeys (ASSUAN_CONTEXT ctx, char *line) +{ + return do_listkeys (ctx, line, 3); +} + +static int +cmd_listsecretkeys (ASSUAN_CONTEXT ctx, char *line) +{ + return do_listkeys (ctx, line, 2); +} + + +/* GENKEY + + Read the parameters in native format from the input fd and write a + certificate request to the output. + */ +static int +cmd_genkey (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int inp_fd, out_fd; + FILE *out_fp; + int rc; + + inp_fd = assuan_get_input_fd (ctx); + if (inp_fd == -1) + return set_error (No_Input, NULL); + out_fd = assuan_get_output_fd (ctx); + if (out_fd == -1) + return set_error (No_Output, NULL); + + out_fp = fdopen ( dup(out_fd), "w"); + if (!out_fp) + return set_error (General_Error, "fdopen() failed"); + rc = gpgsm_genkey (ctrl, inp_fd, out_fp); + fclose (out_fp); + + /* close and reset the fds */ + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + return map_to_assuan_status (rc); +} + + + + + +/* Tell the assuan library about our commands */ +static int +register_commands (ASSUAN_CONTEXT ctx) +{ + static struct { + const char *name; + int (*handler)(ASSUAN_CONTEXT, char *line); + } table[] = { + { "RECIPIENT", cmd_recipient }, + { "SIGNER", cmd_signer }, + { "ENCRYPT", cmd_encrypt }, + { "DECRYPT", cmd_decrypt }, + { "VERIFY", cmd_verify }, + { "SIGN", cmd_sign }, + { "IMPORT", cmd_import }, + { "EXPORT", cmd_export }, + { "INPUT", NULL }, + { "OUTPUT", NULL }, + { "MESSAGE", cmd_message }, + { "LISTKEYS", cmd_listkeys }, + { "LISTSECRETKEYS",cmd_listsecretkeys }, + { "GENKEY", cmd_genkey }, + { "DELKEYS", cmd_delkeys }, + { NULL } + }; + int i, rc; + + for (i=0; table[i].name; i++) + { + rc = assuan_register_command (ctx, table[i].name, table[i].handler); + if (rc) + return rc; + } + return 0; +} + +/* Startup the server */ +void +gpgsm_server (void) +{ + int rc; + int filedes[2]; + ASSUAN_CONTEXT ctx; + struct server_control_s ctrl; + + memset (&ctrl, 0, sizeof ctrl); + gpgsm_init_default_ctrl (&ctrl); + + /* For now we use a simple pipe based server so that we can work + from scripts. We will later add options to run as a daemon and + wait for requests on a Unix domain socket */ + filedes[0] = 0; + filedes[1] = 1; + rc = assuan_init_pipe_server (&ctx, filedes); + if (rc) + { + log_error ("failed to initialize the server: %s\n", + assuan_strerror(rc)); + gpgsm_exit (2); + } + rc = register_commands (ctx); + if (rc) + { + log_error ("failed to the register commands with Assuan: %s\n", + assuan_strerror(rc)); + gpgsm_exit (2); + } + assuan_set_hello_line (ctx, "GNU Privacy Guard's S/M server ready"); + + assuan_register_reset_notify (ctx, reset_notify); + assuan_register_input_notify (ctx, input_notify); + assuan_register_output_notify (ctx, output_notify); + assuan_register_option_handler (ctx, option_handler); + + assuan_set_pointer (ctx, &ctrl); + ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local); + ctrl.server_local->assuan_ctx = ctx; + ctrl.server_local->message_fd = -1; + ctrl.server_local->list_internal = 1; + ctrl.server_local->list_external = 0; + + if (DBG_ASSUAN) + assuan_set_log_stream (ctx, log_get_stream ()); + + for (;;) + { + rc = assuan_accept (ctx); + if (rc == -1) + { + break; + } + else if (rc) + { + log_info ("Assuan accept problem: %s\n", assuan_strerror (rc)); + break; + } + + rc = assuan_process (ctx); + if (rc) + { + log_info ("Assuan processing failed: %s\n", assuan_strerror (rc)); + continue; + } + } + + gpgsm_release_certlist (ctrl.server_local->recplist); + ctrl.server_local->recplist = NULL; + gpgsm_release_certlist (ctrl.server_local->signerlist); + ctrl.server_local->signerlist = NULL; + + assuan_deinit_server (ctx); +} + + +static const char * +get_status_string ( int no ) +{ + const char *s; + + switch (no) + { + case STATUS_ENTER : s = "ENTER"; break; + case STATUS_LEAVE : s = "LEAVE"; break; + case STATUS_ABORT : s = "ABORT"; break; + case STATUS_GOODSIG: s = "GOODSIG"; break; + case STATUS_SIGEXPIRED: s = "SIGEXPIRED"; break; + case STATUS_KEYREVOKED: s = "KEYREVOKED"; break; + case STATUS_BADSIG : s = "BADSIG"; break; + case STATUS_ERRSIG : s = "ERRSIG"; break; + case STATUS_BADARMOR : s = "BADARMOR"; break; + case STATUS_RSA_OR_IDEA : s= "RSA_OR_IDEA"; break; + case STATUS_TRUST_UNDEFINED: s = "TRUST_UNDEFINED"; break; + case STATUS_TRUST_NEVER : s = "TRUST_NEVER"; break; + case STATUS_TRUST_MARGINAL : s = "TRUST_MARGINAL"; break; + case STATUS_TRUST_FULLY : s = "TRUST_FULLY"; break; + case STATUS_TRUST_ULTIMATE : s = "TRUST_ULTIMATE"; break; + case STATUS_GET_BOOL : s = "GET_BOOL"; break; + case STATUS_GET_LINE : s = "GET_LINE"; break; + case STATUS_GET_HIDDEN : s = "GET_HIDDEN"; break; + case STATUS_GOT_IT : s = "GOT_IT"; break; + case STATUS_SHM_INFO : s = "SHM_INFO"; break; + case STATUS_SHM_GET : s = "SHM_GET"; break; + case STATUS_SHM_GET_BOOL : s = "SHM_GET_BOOL"; break; + case STATUS_SHM_GET_HIDDEN : s = "SHM_GET_HIDDEN"; break; + case STATUS_NEED_PASSPHRASE: s = "NEED_PASSPHRASE"; break; + case STATUS_VALIDSIG : s = "VALIDSIG"; break; + case STATUS_SIG_ID : s = "SIG_ID"; break; + case STATUS_ENC_TO : s = "ENC_TO"; break; + case STATUS_NODATA : s = "NODATA"; break; + case STATUS_BAD_PASSPHRASE : s = "BAD_PASSPHRASE"; break; + case STATUS_NO_PUBKEY : s = "NO_PUBKEY"; break; + case STATUS_NO_SECKEY : s = "NO_SECKEY"; break; + case STATUS_NEED_PASSPHRASE_SYM: s = "NEED_PASSPHRASE_SYM"; break; + case STATUS_DECRYPTION_FAILED: s = "DECRYPTION_FAILED"; break; + case STATUS_DECRYPTION_OKAY: s = "DECRYPTION_OKAY"; break; + case STATUS_MISSING_PASSPHRASE: s = "MISSING_PASSPHRASE"; break; + case STATUS_GOOD_PASSPHRASE : s = "GOOD_PASSPHRASE"; break; + case STATUS_GOODMDC : s = "GOODMDC"; break; + case STATUS_BADMDC : s = "BADMDC"; break; + case STATUS_ERRMDC : s = "ERRMDC"; break; + case STATUS_IMPORTED : s = "IMPORTED"; break; + case STATUS_IMPORT_RES : s = "IMPORT_RES"; break; + case STATUS_FILE_START : s = "FILE_START"; break; + case STATUS_FILE_DONE : s = "FILE_DONE"; break; + case STATUS_FILE_ERROR : s = "FILE_ERROR"; break; + case STATUS_BEGIN_DECRYPTION:s = "BEGIN_DECRYPTION"; break; + case STATUS_END_DECRYPTION : s = "END_DECRYPTION"; break; + case STATUS_BEGIN_ENCRYPTION:s = "BEGIN_ENCRYPTION"; break; + case STATUS_END_ENCRYPTION : s = "END_ENCRYPTION"; break; + case STATUS_DELETE_PROBLEM : s = "DELETE_PROBLEM"; break; + case STATUS_PROGRESS : s = "PROGRESS"; break; + case STATUS_SIG_CREATED : s = "SIG_CREATED"; break; + case STATUS_SESSION_KEY : s = "SESSION_KEY"; break; + case STATUS_NOTATION_NAME : s = "NOTATION_NAME" ; break; + case STATUS_NOTATION_DATA : s = "NOTATION_DATA" ; break; + case STATUS_POLICY_URL : s = "POLICY_URL" ; break; + case STATUS_BEGIN_STREAM : s = "BEGIN_STREAM"; break; + case STATUS_END_STREAM : s = "END_STREAM"; break; + case STATUS_KEY_CREATED : s = "KEY_CREATED"; break; + case STATUS_UNEXPECTED : s = "UNEXPECTED"; break; + case STATUS_INV_RECP : s = "INV_RECP"; break; + case STATUS_NO_RECP : s = "NO_RECP"; break; + case STATUS_ALREADY_SIGNED : s = "ALREADY_SIGNED"; break; + case STATUS_EXPSIG : s = "EXPSIG"; break; + case STATUS_EXPKEYSIG : s = "EXPKEYSIG"; break; + case STATUS_TRUNCATED : s = "TRUNCATED"; break; + case STATUS_ERROR : s = "ERROR"; break; + case STATUS_IMPORT_PROBLEM : s = "IMPORT_PROBLEM"; break; + default: s = "?"; break; + } + return s; +} + + +void +gpgsm_status2 (CTRL ctrl, int no, ...) +{ + va_list arg_ptr; + const char *text; + + va_start (arg_ptr, no); + + if (ctrl->no_server) + { + if (ctrl->status_fd == -1) + return; /* no status wanted */ + if (!statusfp) + { + if (ctrl->status_fd == 1) + statusfp = stdout; + else if (ctrl->status_fd == 2) + statusfp = stderr; + else + statusfp = fdopen (ctrl->status_fd, "w"); + + if (!statusfp) + { + log_fatal ("can't open fd %d for status output: %s\n", + ctrl->status_fd, strerror(errno)); + } + } + + fputs ("[GNUPG:] ", statusfp); + fputs (get_status_string (no), statusfp); + + while ( (text = va_arg (arg_ptr, const char*) )) + { + putc ( ' ', statusfp ); + for (; *text; text++) + { + if (*text == '\n') + fputs ( "\\n", statusfp ); + else if (*text == '\r') + fputs ( "\\r", statusfp ); + else + putc ( *(const byte *)text, statusfp ); + } + } + putc ('\n', statusfp); + fflush (statusfp); + } + else + { + ASSUAN_CONTEXT ctx = ctrl->server_local->assuan_ctx; + char buf[950], *p; + size_t n; + + p = buf; + n = 0; + while ( (text = va_arg (arg_ptr, const char *)) ) + { + if (n) + { + *p++ = ' '; + n++; + } + for ( ; *text && n < DIM (buf)-2; n++) + *p++ = *text++; + } + *p = 0; + assuan_write_status (ctx, get_status_string (no), buf); + } + + va_end (arg_ptr); +} + +void +gpgsm_status (CTRL ctrl, int no, const char *text) +{ + gpgsm_status2 (ctrl, no, text, NULL); +} + +void +gpgsm_status_with_err_code (CTRL ctrl, int no, const char *text, + gpg_err_code_t ec) +{ + char buf[30]; + + sprintf (buf, "%u", (unsigned int)ec); + if (text) + gpgsm_status2 (ctrl, no, text, buf, NULL); + else + gpgsm_status2 (ctrl, no, buf, NULL); +} + +#if 0 +/* + * Write a status line with a buffer using %XX escapes. If WRAP is > + * 0 wrap the line after this length. If STRING is not NULL it will + * be prepended to the buffer, no escaping is done for string. + * A wrap of -1 forces spaces not to be encoded as %20. + */ +void +write_status_text_and_buffer ( int no, const char *string, + const char *buffer, size_t len, int wrap ) +{ + const char *s, *text; + int esc, first; + int lower_limit = ' '; + size_t n, count, dowrap; + + if( !statusfp ) + return; /* not enabled */ + + if (wrap == -1) { + lower_limit--; + wrap = 0; + } + + text = get_status_string (no); + count = dowrap = first = 1; + do { + if (dowrap) { + fprintf (statusfp, "[GNUPG:] %s ", text ); + count = dowrap = 0; + if (first && string) { + fputs (string, statusfp); + count += strlen (string); + } + first = 0; + } + for (esc=0, s=buffer, n=len; n && !esc; s++, n-- ) { + if ( *s == '%' || *(const byte*)s <= lower_limit + || *(const byte*)s == 127 ) + esc = 1; + if ( wrap && ++count > wrap ) { + dowrap=1; + break; + } + } + if (esc) { + s--; n++; + } + if (s != buffer) + fwrite (buffer, s-buffer, 1, statusfp ); + if ( esc ) { + fprintf (statusfp, "%%%02X", *(const byte*)s ); + s++; n--; + } + buffer = s; + len = n; + if ( dowrap && len ) + putc ( '\n', statusfp ); + } while ( len ); + + putc ('\n',statusfp); + fflush (statusfp); +} +#endif diff --git a/sm/sign.c b/sm/sign.c new file mode 100644 index 000000000..0afb52b62 --- /dev/null +++ b/sm/sign.c @@ -0,0 +1,621 @@ +/* sign.c - Sign a message + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpgsm.h" +#include +#include + +#include "keydb.h" +#include "i18n.h" + + +static void +hash_data (int fd, gcry_md_hd_t md) +{ + FILE *fp; + char buffer[4096]; + int nread; + + fp = fdopen ( dup (fd), "rb"); + if (!fp) + { + log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); + return; + } + + do + { + nread = fread (buffer, 1, DIM(buffer), fp); + gcry_md_write (md, buffer, nread); + } + while (nread); + if (ferror (fp)) + log_error ("read error on fd %d: %s\n", fd, strerror (errno)); + fclose (fp); +} + +static int +hash_and_copy_data (int fd, gcry_md_hd_t md, KsbaWriter writer) +{ + KsbaError err; + FILE *fp; + char buffer[4096]; + int nread; + int rc = 0; + int any = 0; + + fp = fdopen ( dup (fd), "rb"); + if (!fp) + { + gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); + return tmperr; + } + + do + { + nread = fread (buffer, 1, DIM(buffer), fp); + if (nread) + { + any = 1; + gcry_md_write (md, buffer, nread); + err = ksba_writer_write_octet_string (writer, buffer, nread, 0); + if (err) + { + log_error ("write failed: %s\n", ksba_strerror (err)); + rc = map_ksba_err (err); + } + } + } + while (nread && !rc); + if (ferror (fp)) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("read error on fd %d: %s\n", fd, strerror (errno)); + } + fclose (fp); + if (!any) + { + /* We can't allow to sign an empty message because it does not + make much sense and more seriously, ksba-cms_build has + already written the tag for data and now expects an octet + string but an octet string of zeize 0 is illegal. */ + log_error ("cannot sign an empty message\n"); + rc = gpg_error (GPG_ERR_NO_DATA); + } + if (!rc) + { + err = ksba_writer_write_octet_string (writer, NULL, 0, 1); + if (err) + { + log_error ("write failed: %s\n", ksba_strerror (err)); + rc = map_ksba_err (err); + } + } + + return rc; +} + + +/* Get the default certificate which is defined as the first one our + keyDB retruns and has a secret key available */ +int +gpgsm_get_default_cert (KsbaCert *r_cert) +{ + KEYDB_HANDLE hd; + KsbaCert cert = NULL; + int rc; + char *p; + + hd = keydb_new (0); + if (!hd) + return gpg_error (GPG_ERR_GENERAL); + rc = keydb_search_first (hd); + if (rc) + { + keydb_release (hd); + return rc; + } + + do + { + rc = keydb_get_cert (hd, &cert); + if (rc) + { + log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc)); + keydb_release (hd); + return rc; + } + + p = gpgsm_get_keygrip_hexstring (cert); + if (p) + { + if (!gpgsm_agent_havekey (p)) + { + xfree (p); + keydb_release (hd); + *r_cert = cert; + return 0; /* got it */ + } + xfree (p); + } + + ksba_cert_release (cert); + cert = NULL; + } + while (!(rc = keydb_search_next (hd))); + if (rc && rc != -1) + log_error ("keydb_search_next failed: %s\n", gpg_strerror (rc)); + + ksba_cert_release (cert); + keydb_release (hd); + return rc; +} + + +static KsbaCert +get_default_signer (void) +{ + KEYDB_SEARCH_DESC desc; + KsbaCert cert = NULL; + KEYDB_HANDLE kh = NULL; + int rc; + + if (!opt.local_user) + { + rc = gpgsm_get_default_cert (&cert); + if (rc) + { + if (rc != -1) + log_debug ("failed to find default certificate: %s\n", + gpg_strerror (rc)); + return NULL; + } + return cert; + } + + rc = keydb_classify_name (opt.local_user, &desc); + if (rc) + { + log_error ("failed to find default signer: %s\n", gpg_strerror (rc)); + return NULL; + } + + kh = keydb_new (0); + if (!kh) + return NULL; + + rc = keydb_search (kh, &desc, 1); + if (rc) + { + log_debug ("failed to find default certificate: rc=%d\n", rc); + } + else + { + rc = keydb_get_cert (kh, &cert); + if (rc) + { + log_debug ("failed to get cert: rc=%d\n", rc); + } + } + + keydb_release (kh); + return cert; +} + +/* Depending on the options in CTRL add the certificate CERT as well as + other certificate up in the chain to the Root-CA to the CMS + object. */ +static int +add_certificate_list (CTRL ctrl, KsbaCMS cms, KsbaCert cert) +{ + KsbaError err; + int rc = 0; + KsbaCert next = NULL; + int n; + int not_root = 0; + + ksba_cert_ref (cert); + + n = ctrl->include_certs; + if (n == -2) + { + not_root = 1; + n = -1; + } + if (n < 0 || n > 50) + n = 50; /* We better apply an upper bound */ + + if (n) + { + if (not_root && gpgsm_is_root_cert (cert)) + err = 0; + else + err = ksba_cms_add_cert (cms, cert); + if (err) + goto ksba_failure; + } + while ( n-- && !(rc = gpgsm_walk_cert_chain (cert, &next)) ) + { + if (not_root && gpgsm_is_root_cert (next)) + err = 0; + else + err = ksba_cms_add_cert (cms, next); + ksba_cert_release (cert); + cert = next; next = NULL; + if (err) + goto ksba_failure; + } + ksba_cert_release (cert); + + return rc == -1? 0: rc; + + ksba_failure: + ksba_cert_release (cert); + log_error ("ksba_cms_add_cert failed: %s\n", ksba_strerror (err)); + return map_ksba_err (err); +} + + + + +/* Perform a sign operation. + + Sign the data received on DATA-FD in embedded mode or in detached + mode when DETACHED is true. Write the signature to OUT_FP. The + keys used to sign are taken from SIGNERLIST or the default one will + be used if the value of this argument is NULL. */ +int +gpgsm_sign (CTRL ctrl, CERTLIST signerlist, + int data_fd, int detached, FILE *out_fp) +{ + int i, rc; + KsbaError err; + Base64Context b64writer = NULL; + KsbaWriter writer; + KsbaCMS cms = NULL; + KsbaStopReason stopreason; + KEYDB_HANDLE kh = NULL; + gcry_md_hd_t data_md = NULL; + int signer; + const char *algoid; + int algo; + time_t signed_at; + CERTLIST cl; + int release_signerlist = 0; + + kh = keydb_new (0); + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + ctrl->pem_name = "SIGNED MESSAGE"; + rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer); + if (rc) + { + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + goto leave; + } + + cms = ksba_cms_new (); + if (!cms) + { + rc = gpg_error (GPG_ERR_ENOMEM); + goto leave; + } + + err = ksba_cms_set_reader_writer (cms, NULL, writer); + if (err) + { + log_debug ("ksba_cms_set_reader_writer failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + /* We are going to create signed data with data as encap. content */ + err = ksba_cms_set_content_type (cms, 0, KSBA_CT_SIGNED_DATA); + if (!err) + err = ksba_cms_set_content_type (cms, 1, KSBA_CT_DATA); + if (err) + { + log_debug ("ksba_cms_set_content_type failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + /* If no list of signers is given, use a default one. */ + if (!signerlist) + { + KsbaCert cert = get_default_signer (); + if (!cert) + { + log_error ("no default signer found\n"); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + signerlist = xtrycalloc (1, sizeof *signerlist); + if (!signerlist) + { + rc = OUT_OF_CORE (errno); + ksba_cert_release (cert); + goto leave; + } + signerlist->cert = cert; + release_signerlist = 1; + } + + + /* Gather certificates of signers and store them in the CMS object. */ + for (cl=signerlist; cl; cl = cl->next) + { + rc = gpgsm_cert_use_sign_p (cl->cert); + if (rc) + goto leave; + + err = ksba_cms_add_signer (cms, cl->cert); + if (err) + { + log_error ("ksba_cms_add_signer failed: %s\n", ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + rc = add_certificate_list (ctrl, cms, cl->cert); + if (rc) + { + log_error ("failed to store list of certificates: %s\n", + gpg_strerror(rc)); + goto leave; + } + /* Set the hash algorithm we are going to use */ + err = ksba_cms_add_digest_algo (cms, "1.3.14.3.2.26" /*SHA-1*/); + if (err) + { + log_debug ("ksba_cms_add_digest_algo failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + } + + /* Prepare hashing (actually we are figuring out what we have set above)*/ + rc = gcry_md_open (&data_md, 0, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if (DBG_HASHING) + gcry_md_start_debug (data_md, "sign.data"); + + for (i=0; (algoid=ksba_cms_get_digest_algo_list (cms, i)); i++) + { + algo = gcry_md_map_name (algoid); + if (!algo) + { + log_error ("unknown hash algorithm `%s'\n", algoid? algoid:"?"); + rc = gpg_error (GPG_ERR_BUG); + goto leave; + } + gcry_md_enable (data_md, algo); + } + + if (detached) + { /* we hash the data right now so that we can store the message + digest. ksba_cms_build() takes this as an flag that detached + data is expected. */ + unsigned char *digest; + size_t digest_len; + /* Fixme do this for all signers and get the algo to use from + the signer's certificate - does not make mich sense, bu we + should do this consistent as we have already done it above */ + algo = GCRY_MD_SHA1; + hash_data (data_fd, data_md); + digest = gcry_md_read (data_md, algo); + digest_len = gcry_md_get_algo_dlen (algo); + if ( !digest || !digest_len) + { + log_error ("problem getting the hash of the data\n"); + rc = gpg_error (GPG_ERR_BUG); + goto leave; + } + for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) + { + err = ksba_cms_set_message_digest (cms, signer, digest, digest_len); + if (err) + { + log_error ("ksba_cms_set_message_digest failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + } + } + + signed_at = gnupg_get_time (); + for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) + { + err = ksba_cms_set_signing_time (cms, signer, signed_at); + if (err) + { + log_error ("ksba_cms_set_signing_time failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + } + + do + { + err = ksba_cms_build (cms, &stopreason); + if (err) + { + log_debug ("ksba_cms_build failed: %s\n", ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + if (stopreason == KSBA_SR_BEGIN_DATA) + { /* hash the data and store the message digest */ + unsigned char *digest; + size_t digest_len; + + assert (!detached); + /* Fixme: get the algo to use from the signer's certificate + - does not make much sense, but we should do this + consistent as we have already done it above. Code is + mostly duplicated above. */ + + algo = GCRY_MD_SHA1; + rc = hash_and_copy_data (data_fd, data_md, writer); + if (rc) + goto leave; + digest = gcry_md_read (data_md, algo); + digest_len = gcry_md_get_algo_dlen (algo); + if ( !digest || !digest_len) + { + log_error ("problem getting the hash of the data\n"); + rc = gpg_error (GPG_ERR_BUG); + goto leave; + } + for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) + { + err = ksba_cms_set_message_digest (cms, signer, + digest, digest_len); + if (err) + { + log_error ("ksba_cms_set_message_digest failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + } + } + else if (stopreason == KSBA_SR_NEED_SIG) + { /* calculate the signature for all signers */ + gcry_md_hd_t md; + + algo = GCRY_MD_SHA1; + rc = gcry_md_open (&md, algo, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if (DBG_HASHING) + gcry_md_start_debug (md, "sign.attr"); + ksba_cms_set_hash_function (cms, HASH_FNC, md); + for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) + { + char *sigval = NULL; + char *buf, *fpr; + + if (signer) + gcry_md_reset (md); + rc = ksba_cms_hash_signed_attrs (cms, signer); + if (rc) + { + log_debug ("hashing signed attrs failed: %s\n", + ksba_strerror (rc)); + gcry_md_close (md); + goto leave; + } + + rc = gpgsm_create_cms_signature (cl->cert, md, algo, &sigval); + if (rc) + { + gcry_md_close (md); + goto leave; + } + + err = ksba_cms_set_sig_val (cms, signer, sigval); + xfree (sigval); + if (err) + { + log_error ("failed to store the signature: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + gcry_md_close (md); + goto leave; + } + + /* write a status message */ + fpr = gpgsm_get_fingerprint_hexstring (cl->cert, GCRY_MD_SHA1); + if (!fpr) + { + rc = gpg_error (GPG_ERR_ENOMEM); + gcry_md_close (md); + goto leave; + } + rc = asprintf (&buf, "%c %d %d 00 %lu %s", + detached? 'D':'S', + GCRY_PK_RSA, /* FIXME: get pk algo from cert */ + algo, + (ulong)signed_at, + fpr); + xfree (fpr); + if (rc < 0) + { + rc = gpg_error (GPG_ERR_ENOMEM); + gcry_md_close (md); + goto leave; + } + rc = 0; + gpgsm_status (ctrl, STATUS_SIG_CREATED, buf); + free (buf); /* yes, we must use the regular free() here */ + } + gcry_md_close (md); + + } + } + while (stopreason != KSBA_SR_READY); + + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + log_info ("signature created\n"); + + + leave: + if (release_signerlist) + gpgsm_release_certlist (signerlist); + ksba_cms_release (cms); + gpgsm_destroy_writer (b64writer); + keydb_release (kh); + gcry_md_close (data_md); + return rc; +} diff --git a/sm/verify.c b/sm/verify.c new file mode 100644 index 000000000..6dd4f4e5b --- /dev/null +++ b/sm/verify.c @@ -0,0 +1,550 @@ +/* verify.c - Verify a messages signature + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpgsm.h" +#include +#include + +#include "keydb.h" +#include "i18n.h" + +static char * +strtimestamp_r (time_t atime) +{ + char *buffer = xmalloc (15); + + if (atime < 0) + strcpy (buffer, "????" "-??" "-??"); + else if (!atime) + strcpy (buffer, "none"); + else + { + struct tm *tp; + + tp = gmtime( &atime ); + sprintf (buffer, "%04d-%02d-%02d", + 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday); + } + return buffer; +} + + + +/* Hash the data for a detached signature */ +static void +hash_data (int fd, gcry_md_hd_t md) +{ + FILE *fp; + char buffer[4096]; + int nread; + + fp = fdopen ( dup (fd), "rb"); + if (!fp) + { + log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); + return; + } + + do + { + nread = fread (buffer, 1, DIM(buffer), fp); + gcry_md_write (md, buffer, nread); + } + while (nread); + if (ferror (fp)) + log_error ("read error on fd %d: %s\n", fd, strerror (errno)); + fclose (fp); +} + + + + +/* Perform a verify operation. To verify detached signatures, data_fd + must be different than -1. With OUT_FP given and a non-detached + signature, the signed material is written to that stream. */ +int +gpgsm_verify (CTRL ctrl, int in_fd, int data_fd, FILE *out_fp) +{ + int i, rc; + Base64Context b64reader = NULL; + Base64Context b64writer = NULL; + KsbaError err; + KsbaReader reader; + KsbaWriter writer = NULL; + KsbaCMS cms = NULL; + KsbaStopReason stopreason; + KsbaCert cert; + KEYDB_HANDLE kh; + gcry_md_hd_t data_md = NULL; + int signer; + const char *algoid; + int algo; + int is_detached; + FILE *fp = NULL; + char *p; + + kh = keydb_new (0); + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + + fp = fdopen ( dup (in_fd), "rb"); + if (!fp) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("fdopen() failed: %s\n", strerror (errno)); + goto leave; + } + + rc = gpgsm_create_reader (&b64reader, ctrl, fp, &reader); + if (rc) + { + log_error ("can't create reader: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (out_fp) + { + rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer); + if (rc) + { + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + goto leave; + } + } + + cms = ksba_cms_new (); + if (!cms) + { + rc = gpg_error (GPG_ERR_ENOMEM); + goto leave; + } + + err = ksba_cms_set_reader_writer (cms, reader, writer); + if (err) + { + log_error ("ksba_cms_set_reader_writer failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + rc = gcry_md_open (&data_md, 0, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if (DBG_HASHING) + gcry_md_start_debug (data_md, "vrfy.data"); + + is_detached = 0; + do + { + err = ksba_cms_parse (cms, &stopreason); + if (err) + { + log_error ("ksba_cms_parse failed: %s\n", ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + if (stopreason == KSBA_SR_NEED_HASH) + { + is_detached = 1; + if (opt.verbose) + log_info ("detached signature\n"); + } + + if (stopreason == KSBA_SR_NEED_HASH + || stopreason == KSBA_SR_BEGIN_DATA) + { /* We are now able to enable the hash algorithms */ + for (i=0; (algoid=ksba_cms_get_digest_algo_list (cms, i)); i++) + { + algo = gcry_md_map_name (algoid); + if (!algo) + log_error ("unknown hash algorithm `%s'\n", + algoid? algoid:"?"); + else + gcry_md_enable (data_md, algo); + } + if (is_detached) + { + if (data_fd == -1) + log_info ("detached signature w/o data " + "- assuming certs-only\n"); + else + hash_data (data_fd, data_md); + } + else + { + ksba_cms_set_hash_function (cms, HASH_FNC, data_md); + } + } + else if (stopreason == KSBA_SR_END_DATA) + { /* The data bas been hashed */ + + } + } + while (stopreason != KSBA_SR_READY); + + if (b64writer) + { + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + } + + if (data_fd != -1 && !is_detached) + { + log_error ("data given for a non-detached signature\n"); + rc = gpg_error (GPG_ERR_CONFLICT); + goto leave; + } + + for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++) + { + /* Fixme: it might be better to check the validity of the + certificate first before entering it into the DB. This way + we would avoid cluttering the DB with invalid + certificates. */ + keydb_store_cert (cert, 0, NULL); + ksba_cert_release (cert); + } + + cert = NULL; + err = 0; + for (signer=0; ; signer++) + { + char *issuer = NULL; + KsbaSexp sigval = NULL; + time_t sigtime, keyexptime; + KsbaSexp serial; + char *msgdigest = NULL; + size_t msgdigestlen; + char *ctattr; + + err = ksba_cms_get_issuer_serial (cms, signer, &issuer, &serial); + if (!signer && err == KSBA_No_Data && data_fd == -1 && is_detached) + { + log_info ("certs-only message accepted\n"); + err = 0; + break; + } + if (err) + { + if (signer && err == -1) + err = 0; + break; + } + if (DBG_X509) + { + log_debug ("signer %d - issuer: `%s'\n", + signer, issuer? issuer:"[NONE]"); + log_debug ("signer %d - serial: ", signer); + gpgsm_dump_serial (serial); + log_printf ("\n"); + } + + err = ksba_cms_get_signing_time (cms, signer, &sigtime); + if (err == KSBA_No_Data) + sigtime = 0; + else if (err) + { + log_error ("error getting signing time: %s\n", ksba_strerror (err)); + sigtime = (time_t)-1; + } + + err = ksba_cms_get_message_digest (cms, signer, + &msgdigest, &msgdigestlen); + if (!err) + { + algoid = ksba_cms_get_digest_algo (cms, signer); + algo = gcry_md_map_name (algoid); + if (DBG_X509) + log_debug ("signer %d - digest algo: %d\n", signer, algo); + if ( !gcry_md_info (data_md, GCRYCTL_IS_ALGO_ENABLED, &algo, NULL) ) + { + log_error ("digest algo %d has not been enabled\n", algo); + goto next_signer; + } + } + else if (err == KSBA_No_Data) + { + assert (!msgdigest); + err = 0; + algoid = NULL; + algo = 0; + } + else /* real error */ + break; + + err = ksba_cms_get_sigattr_oids (cms, signer, + "1.2.840.113549.1.9.3",&ctattr); + if (!err) + { + const char *s; + + if (DBG_X509) + log_debug ("signer %d - content-type attribute: %s", signer, ctattr); + s = ksba_cms_get_content_oid (cms, 1); + if (!s || strcmp (ctattr, s)) + { + log_error ("content-type attribute does not match " + "actual content-type\n"); + ksba_free (ctattr); + ctattr = NULL; + goto next_signer; + } + ksba_free (ctattr); + ctattr = NULL; + } + else if (err != -1) + { + log_error ("error getting content-type attribute: %s\n", + ksba_strerror (err)); + goto next_signer; + } + err = 0; + + + sigval = ksba_cms_get_sig_val (cms, signer); + if (!sigval) + { + log_error ("no signature value available\n"); + goto next_signer; + } + if (DBG_X509) + log_debug ("signer %d - signature available", signer); + + /* Find the certificate of the signer */ + keydb_search_reset (kh); + rc = keydb_search_issuer_sn (kh, issuer, serial); + if (rc) + { + if (rc == -1) + { + log_error ("certificate not found\n"); + rc = gpg_error (GPG_ERR_NO_PUBKEY); + } + else + log_error ("failed to find the certificate: %s\n", + gpg_strerror(rc)); + { + char numbuf[50]; + sprintf (numbuf, "%d", rc); + + gpgsm_status2 (ctrl, STATUS_ERROR, "verify.findkey", + numbuf, NULL); + } + /* fixme: we might want to append the issuer and serial + using our standard notation */ + goto next_signer; + } + + rc = keydb_get_cert (kh, &cert); + if (rc) + { + log_error ("failed to get cert: %s\n", gpg_strerror (rc)); + goto next_signer; + } + + log_info (_("Signature made ")); + if (sigtime) + gpgsm_dump_time (sigtime); + else + log_printf (_("[date not given]")); + log_printf (_(" using certificate ID %08lX\n"), + gpgsm_get_short_fingerprint (cert)); + + + if (msgdigest) + { /* Signed attributes are available. */ + gcry_md_hd_t md; + unsigned char *s; + + /* check that the message digest in the signed attributes + matches the one we calculated on the data */ + s = gcry_md_read (data_md, algo); + if ( !s || !msgdigestlen + || gcry_md_get_algo_dlen (algo) != msgdigestlen + || !s || memcmp (s, msgdigest, msgdigestlen) ) + { + char *fpr; + + log_error ("invalid signature: message digest attribute " + "does not match calculated one\n"); + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + gpgsm_status (ctrl, STATUS_BADSIG, fpr); + xfree (fpr); + goto next_signer; + } + + rc = gcry_md_open (&md, algo, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + goto next_signer; + } + if (DBG_HASHING) + gcry_md_start_debug (md, "vrfy.attr"); + + ksba_cms_set_hash_function (cms, HASH_FNC, md); + rc = ksba_cms_hash_signed_attrs (cms, signer); + if (rc) + { + log_error ("hashing signed attrs failed: %s\n", + ksba_strerror (rc)); + gcry_md_close (md); + goto next_signer; + } + rc = gpgsm_check_cms_signature (cert, sigval, md, algo); + gcry_md_close (md); + } + else + { + rc = gpgsm_check_cms_signature (cert, sigval, data_md, algo); + } + + if (rc) + { + char *fpr; + + log_error ("invalid signature: %s\n", gpg_strerror (rc)); + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + gpgsm_status (ctrl, STATUS_BADSIG, fpr); + xfree (fpr); + goto next_signer; + } + rc = gpgsm_cert_use_verify_p (cert); /*(this displays an info message)*/ + if (rc) + { + gpgsm_status_with_err_code (ctrl, STATUS_ERROR, "verify.keyusage", + gpg_err_code (rc)); + rc = 0; + } + + if (DBG_X509) + log_debug ("signature okay - checking certs\n"); + rc = gpgsm_validate_chain (ctrl, cert, &keyexptime); + if (gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED) + { + gpgsm_status (ctrl, STATUS_EXPKEYSIG, NULL); + rc = 0; + } + else + gpgsm_status (ctrl, STATUS_GOODSIG, NULL); + + { + char *buf, *fpr, *tstr; + + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + tstr = strtimestamp_r (sigtime); + buf = xmalloc ( strlen(fpr) + strlen (tstr) + 120); + sprintf (buf, "%s %s %lu %lu", fpr, tstr, + (unsigned long)sigtime, (unsigned long)keyexptime ); + xfree (tstr); + xfree (fpr); + gpgsm_status (ctrl, STATUS_VALIDSIG, buf); + xfree (buf); + } + + if (rc) /* of validate_chain */ + { + log_error ("invalid certification chain: %s\n", gpg_strerror (rc)); + if (gpg_err_code (rc) == GPG_ERR_BAD_CERT_CHAIN + || gpg_err_code (rc) == GPG_ERR_BAD_CERT + || gpg_err_code (rc) == GPG_ERR_BAD_CA_CERT + || gpg_err_code (rc) == GPG_ERR_CERT_REVOKED) + gpgsm_status_with_err_code (ctrl, STATUS_TRUST_NEVER, NULL, + gpg_err_code (rc)); + else + gpgsm_status_with_err_code (ctrl, STATUS_TRUST_UNDEFINED, NULL, + gpg_err_code (rc)); + goto next_signer; + } + + for (i=0; (p = ksba_cert_get_subject (cert, i)); i++) + { + log_info (!i? _("Good signature from") + : _(" aka")); + log_printf (" \""); + gpgsm_print_name (log_get_stream (), p); + log_printf ("\"\n"); + ksba_free (p); + } + + gpgsm_status (ctrl, STATUS_TRUST_FULLY, NULL); + + + next_signer: + rc = 0; + xfree (issuer); + xfree (serial); + xfree (sigval); + xfree (msgdigest); + ksba_cert_release (cert); + cert = NULL; + } + rc = 0; + if (err) + { + log_error ("ksba error: %s\n", ksba_strerror (err)); + rc = map_ksba_err (rc); + } + + + + leave: + ksba_cms_release (cms); + gpgsm_destroy_reader (b64reader); + gpgsm_destroy_writer (b64writer); + keydb_release (kh); + gcry_md_close (data_md); + if (fp) + fclose (fp); + + if (rc) + { + char numbuf[50]; + sprintf (numbuf, "%d", rc ); + gpgsm_status2 (ctrl, STATUS_ERROR, "verify.leave", + numbuf, NULL); + } + + return rc; +} + -- cgit From 71d265a684da4ef4f1c2b6b73fb9ba3a27a6fa03 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 31 Oct 2003 12:11:48 +0000 Subject: * command.c (cmd_get_confirmation): New command. --- agent/ChangeLog | 4 ++++ agent/command.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) (limited to 'agent/command.c') diff --git a/agent/ChangeLog b/agent/ChangeLog index 39e93f818..005912dff 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,7 @@ +2003-10-27 Werner Koch + + * command.c (cmd_get_confirmation): New command. + 2003-08-20 Timo Schulz * pksign.c (do_encode_md): Allocate enough space. Cast md diff --git a/agent/command.c b/agent/command.c index ed4ea6b02..f7a042ba9 100644 --- a/agent/command.c +++ b/agent/command.c @@ -522,6 +522,55 @@ cmd_clear_passphrase (ASSUAN_CONTEXT ctx, char *line) return 0; } + +/* GET_CONFIRMATION + + This command may be used to ask for a simple confirmation. + DESCRIPTION is displayed along with a Okay and Cancel button. This + command uses a syntax which helps clients to use the agent with + minimum effort. The agent either returns with an error or with a + OK. Note, that the length of DESCRIPTION is implicitly limited by + the maximum length of a command. DESCRIPTION should not conmtain + ant spaces, those must be encoded either percent escaped or simply + as '+'. +*/ + +static int +cmd_get_confirmation (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + char *desc = NULL; + char *p; + + /* parse the stuff */ + for (p=line; *p == ' '; p++) + ; + desc = p; + p = strchr (desc, ' '); + if (p) + *p = 0; /* We ignore any garbage -may be later used for other args. */ + + if (!desc || !*desc) + return set_error (Parameter_Error, "no description given"); + + if (!strcmp (desc, "X")) + desc = NULL; + + /* Note, that we only need to replace the + characters and should + leave the other escaping in place because the escaped string is + send verbatim to the pinentry which does the unescaping (but not + the + replacing) */ + if (desc) + plus_to_blank (desc); + + rc = agent_get_confirmation (ctrl, desc, NULL, NULL); + if (rc) + log_error ("command get_confirmation failed: %s\n", gpg_strerror (rc)); + return map_to_assuan_status (rc); +} + + /* LEARN [--send] @@ -671,6 +720,7 @@ register_commands (ASSUAN_CONTEXT ctx) { "GENKEY", cmd_genkey }, { "GET_PASSPHRASE", cmd_get_passphrase }, { "CLEAR_PASSPHRASE", cmd_clear_passphrase }, + { "GET_CONFIRMATION", cmd_get_confirmation }, { "LISTTRUSTED", cmd_listtrusted }, { "MARKTRUSTED", cmd_marktrusted }, { "LEARN", cmd_learn }, -- cgit From dba40e5e45e80896dc8864c2ae97f026069e2906 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 12 Nov 2003 15:17:44 +0000 Subject: Mainly changes to adjust for the changed KSBA API. --- agent/command.c | 4 ++-- agent/genkey.c | 2 +- agent/pkdecrypt.c | 1 - agent/pksign.c | 5 +++-- common/ChangeLog | 4 ++++ common/maperror.c | 43 ---------------------------------------- common/util.h | 2 -- g10/misc.c | 2 ++ jnlib/ChangeLog | 4 ++++ jnlib/strlist.h | 23 +++++++++++++--------- kbx/ChangeLog | 6 ++++++ kbx/keybox-blob.c | 1 + kbx/keybox-defs.h | 7 ++++--- kbx/keybox-search.c | 12 ++++++------ scd/ChangeLog | 4 ++++ scd/card-dinsig.c | 13 ++++++------- scd/card-p15.c | 13 ++++++------- scd/card.c | 3 ++- scd/command.c | 12 +++++------- sm/ChangeLog | 4 ++++ sm/base64.c | 21 ++++++++++---------- sm/call-agent.c | 10 +++++----- sm/call-dirmngr.c | 8 ++++---- sm/certchain.c | 12 ++++++------ sm/certcheck.c | 4 ++-- sm/certlist.c | 6 +++--- sm/certreqgen.c | 26 ++++++++++++------------- sm/decrypt.c | 30 ++++++++++++++-------------- sm/encrypt.c | 40 ++++++++++++++++++-------------------- sm/export.c | 2 +- sm/fingerprint.c | 4 ++-- sm/import.c | 29 +++++++++------------------ sm/keylist.c | 14 +++++++------- sm/misc.c | 3 ++- sm/sign.c | 56 ++++++++++++++++++++++++++--------------------------- sm/verify.c | 31 +++++++++++++++-------------- 36 files changed, 216 insertions(+), 245 deletions(-) (limited to 'agent/command.c') diff --git a/agent/command.c b/agent/command.c index f7a042ba9..a3e1c514a 100644 --- a/agent/command.c +++ b/agent/command.c @@ -329,7 +329,7 @@ cmd_pkdecrypt (ASSUAN_CONTEXT ctx, char *line) { int rc; CTRL ctrl = assuan_get_pointer (ctx); - char *value; + unsigned char *value; size_t valuelen; /* First inquire the data to decrypt */ @@ -365,7 +365,7 @@ cmd_genkey (ASSUAN_CONTEXT ctx, char *line) { CTRL ctrl = assuan_get_pointer (ctx); int rc; - char *value; + unsigned char *value; size_t valuelen; /* First inquire the parameters */ diff --git a/agent/genkey.c b/agent/genkey.c index 0a0577f17..1417abb02 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -137,7 +137,7 @@ agent_genkey (CTRL ctrl, const char *keyparam, size_t keyparamlen, { log_error ("key generation failed: %s\n", gpg_strerror (rc)); xfree (pi); - return map_gcry_err (rc); + return rc; } /* break out the parts */ diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c index 543a82737..d17c688a0 100644 --- a/agent/pkdecrypt.c +++ b/agent/pkdecrypt.c @@ -105,7 +105,6 @@ agent_pkdecrypt (CTRL ctrl, const char *ciphertext, size_t ciphertextlen, if (rc) { log_error ("decryption failed: %s\n", gpg_strerror (rc)); - rc = map_gcry_err (rc); goto leave; } diff --git a/agent/pksign.c b/agent/pksign.c index 342582177..d5fe17bb7 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -39,7 +39,9 @@ do_encode_md (const byte * md, size_t mdlen, int algo, gcry_sexp_t * r_hash) char * p, tmp[16]; int i, rc; -#warning I do do like that stuff - libgcrypt provides easier interfaces. -wk +#ifdef __GNUC__ +#warning I do not like that stuff - libgcrypt provides easier interfaces. -wk +#endif /* FIXME: Either use the build function or create canonical encoded S-expressions. */ @@ -128,7 +130,6 @@ agent_pksign (CTRL ctrl, FILE *outfp, int ignore_cache) if (rc) { log_error ("signing failed: %s\n", gpg_strerror (rc)); - rc = map_gcry_err (rc); goto leave; } diff --git a/common/ChangeLog b/common/ChangeLog index 7dc3c03eb..2eaa954b9 100644 --- a/common/ChangeLog +++ b/common/ChangeLog @@ -1,3 +1,7 @@ +2003-11-12 Werner Koch + + * maperror.c (map_ksba_err, map_gcry_err, map_kbx_err): Removed. + 2003-10-31 Werner Koch * util.h (gnupg_isotime_t): New. diff --git a/common/maperror.c b/common/maperror.c index f52a5995a..361166388 100644 --- a/common/maperror.c +++ b/common/maperror.c @@ -32,49 +32,6 @@ #include "util.h" #include "errors.h" -/* Note: we might want to wrap this in a macro to get our hands on - the line and file where the error occured */ -int -map_ksba_err (int err) -{ - switch (err) - { - case 0: - break; - - case -1: err = GPG_ERR_EOF; break; - case KSBA_Out_Of_Core: err = GPG_ERR_ENOMEM; break; - case KSBA_Invalid_Value: err = GPG_ERR_INV_VALUE; break; - case KSBA_Not_Implemented: err = GPG_ERR_NOT_IMPLEMENTED; break; - case KSBA_Conflict: err = GPG_ERR_CONFLICT; break; - case KSBA_Read_Error: err = GPG_ERR_EIO; break; - case KSBA_Write_Error: err = GPG_ERR_EIO; break; - case KSBA_No_Data: err = GPG_ERR_NO_DATA; break; - case KSBA_Bug: err = GPG_ERR_BUG; break; - case KSBA_Unsupported_Algorithm: err = GPG_ERR_UNSUPPORTED_ALGORITHM; break; - case KSBA_Invalid_Index: err = GPG_ERR_INV_INDEX; break; - case KSBA_Invalid_Sexp: err = GPG_ERR_INV_SEXP; break; - case KSBA_Unknown_Sexp: err = GPG_ERR_UNKNOWN_SEXP; break; - - default: - err = GPG_ERR_GENERAL; - break; - } - return gpg_err_make (GPG_ERR_SOURCE_GPGSM, err); -} - - -int -map_gcry_err (int err) -{ - return err; -} - -int -map_kbx_err (int err) -{ - return err; -} /* Map Assuan error code ERR to an GPG_ERR_ code. We need to distinguish between genuine (and legacy) Assuan error codes and diff --git a/common/util.h b/common/util.h index fb2c6e839..987c8dfe8 100644 --- a/common/util.h +++ b/common/util.h @@ -58,8 +58,6 @@ typedef char gnupg_isotime_t[16]; /*-- maperror.c --*/ -int map_ksba_err (int err); -int map_gcry_err (int err); int map_kbx_err (int err); gpg_error_t map_assuan_err (int err); int map_to_assuan_status (int rc); diff --git a/g10/misc.c b/g10/misc.c index e122f0c5c..7012a8a25 100644 --- a/g10/misc.c +++ b/g10/misc.c @@ -246,7 +246,9 @@ openpgp_pk_test_algo( int algo, unsigned int usage_flags ) if (algo == GCRY_PK_ELG_E) algo = GCRY_PK_ELG; +#ifdef __GNUC__ #warning need to handle the usage here? +#endif if (algo < 0 || algo > 110) return GPG_ERR_PUBKEY_ALGO; return gcry_pk_algo_info (algo, GCRYCTL_TEST_ALGO, NULL, &value); diff --git a/jnlib/ChangeLog b/jnlib/ChangeLog index 594eb340b..7888ff18d 100644 --- a/jnlib/ChangeLog +++ b/jnlib/ChangeLog @@ -1,3 +1,7 @@ +2003-11-06 Werner Koch + + * strlist.h (strlist_t): New. STRLIST is now deprecated. + 2003-06-18 Werner Koch * strlist.c (strlist_pop): New. diff --git a/jnlib/strlist.h b/jnlib/strlist.h index 443408f16..ea459d006 100644 --- a/jnlib/strlist.h +++ b/jnlib/strlist.h @@ -26,17 +26,22 @@ struct string_list { unsigned int flags; char d[1]; }; -typedef struct string_list *STRLIST; +typedef struct string_list *STRLIST; /* Deprecated. */ +typedef struct string_list *strlist_t; +void free_strlist (strlist_t sl); +strlist_t add_to_strlist (strlist_t *list, const char *string); -void free_strlist( STRLIST sl ); -STRLIST add_to_strlist( STRLIST *list, const char *string ); -/*STRLIST add_to_strlist2( STRLIST *list, const char *string, int is_utf8 );*/ -STRLIST append_to_strlist( STRLIST *list, const char *string ); -/*STRLIST append_to_strlist2( STRLIST *list, const char *string, int is_utf8 );*/ -STRLIST strlist_prev( STRLIST head, STRLIST node ); -STRLIST strlist_last( STRLIST node ); -char * strlist_pop (STRLIST *list); +/*strlist_t add_to_strlist2( strlist_t *list, + const char *string, int is_utf8);*/ + +strlist_t append_to_strlist (strlist_t *list, const char *string); + +/*strlist_t append_to_strlist2( strlist_t *list, const char *string, + int is_utf8);*/ +strlist_t strlist_prev (strlist_t head, strlist_t node); +strlist_t strlist_last (strlist_t node); +char * strlist_pop (strlist_t *list); #define FREE_STRLIST(a) do { free_strlist((a)); (a) = NULL ; } while(0) diff --git a/kbx/ChangeLog b/kbx/ChangeLog index af6e6b016..07a198200 100644 --- a/kbx/ChangeLog +++ b/kbx/ChangeLog @@ -1,3 +1,9 @@ +2003-11-12 Werner Koch + + Adjusted for API changes in Libksba. + + * keybox-blob.c: Include time.h + 2003-06-03 Werner Koch Changed all error codes in all files to the new libgpg-error scheme. diff --git a/kbx/keybox-blob.c b/kbx/keybox-blob.c index 5ad1d2610..ca36d6264 100644 --- a/kbx/keybox-blob.c +++ b/kbx/keybox-blob.c @@ -110,6 +110,7 @@ X.509 specific are noted like [X.509: xxx] #include #include #include +#include #include "keybox-defs.h" #include diff --git a/kbx/keybox-defs.h b/kbx/keybox-defs.h index e4578d76b..fcae64c25 100644 --- a/kbx/keybox-defs.h +++ b/kbx/keybox-defs.h @@ -21,14 +21,15 @@ #ifndef KEYBOX_DEFS_H #define KEYBOX_DEFS_H 1 -#include /* off_t */ -#include "keybox.h" - #ifdef GPG_ERR_SOURCE_DEFAULT #error GPG_ERR_SOURCE_DEFAULT already defined #endif #define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_KEYBOX + #include +#include /* off_t */ +#include "keybox.h" + #ifndef HAVE_BYTE_TYPEDEF diff --git a/kbx/keybox-search.c b/kbx/keybox-search.c index 231a32d42..ff95815a2 100644 --- a/kbx/keybox-search.c +++ b/kbx/keybox-search.c @@ -778,9 +778,9 @@ keybox_get_cert (KEYBOX_HANDLE hd, KsbaCert *r_cert) if (cert_off+cert_len > length) return gpg_error (GPG_ERR_TOO_SHORT); - reader = ksba_reader_new (); - if (!reader) - return gpg_error (GPG_ERR_ENOMEM); + rc = ksba_reader_new (&reader); + if (rc) + return rc; rc = ksba_reader_set_mem (reader, buffer+cert_off, cert_len); if (rc) { @@ -789,11 +789,11 @@ keybox_get_cert (KEYBOX_HANDLE hd, KsbaCert *r_cert) return gpg_error (GPG_ERR_GENERAL); } - cert = ksba_cert_new (); - if (!cert) + rc = ksba_cert_new (&cert); + if (rc) { ksba_reader_release (reader); - return gpg_error (GPG_ERR_ENOMEM); + return rc; } rc = ksba_cert_read_der (cert, reader); diff --git a/scd/ChangeLog b/scd/ChangeLog index d3282b38a..cccd3b669 100644 --- a/scd/ChangeLog +++ b/scd/ChangeLog @@ -1,3 +1,7 @@ +2003-11-12 Werner Koch + + Adjusted for API changes in Libksba. + 2003-10-30 Werner Koch * apdu.c (close_ct_reader, close_pcsc_reader): Implemented. diff --git a/scd/card-dinsig.c b/scd/card-dinsig.c index 391a51da8..18a67abb6 100644 --- a/scd/card-dinsig.c +++ b/scd/card-dinsig.c @@ -79,9 +79,9 @@ #ifdef HAVE_OPENSC #include +#include "scdaemon.h" #include -#include "scdaemon.h" #include "card-common.h" static int dinsig_read_cert (CARD card, const char *certidstr, @@ -113,12 +113,11 @@ dinsig_enum_keypairs (CARD card, int idx, if (rc) return rc; - cert = ksba_cert_new (); - if (!cert) + rc = ksba_cert_new (&cert); + if (rc) { - gpg_error_t tmperr = out_of_core (); xfree (buf); - return tmperr; + return rc; } krc = ksba_cert_init_from_mem (cert, buf, buflen); @@ -126,9 +125,9 @@ dinsig_enum_keypairs (CARD card, int idx, if (krc) { log_error ("failed to parse the certificate at idx %d: %s\n", - idx, ksba_strerror (krc)); + idx, gpg_strerror (krc)); ksba_cert_release (cert); - return gpg_error (GPG_ERR_CARD); + return krc; } if (card_help_get_keygrip (cert, keygrip)) { diff --git a/scd/card-p15.c b/scd/card-p15.c index dfb05c03f..33c58e2c8 100644 --- a/scd/card-p15.c +++ b/scd/card-p15.c @@ -27,9 +27,9 @@ #ifdef HAVE_OPENSC #include -#include #include "scdaemon.h" +#include #include "card-common.h" @@ -148,21 +148,20 @@ p15_enum_keypairs (CARD card, int idx, return gpg_error (GPG_ERR_CARD); } - cert = ksba_cert_new (); - if (!cert) + rc = ksba_cert_new (&cert); + if (rc) { - gpg_error_t tmperr = out_of_core (); sc_pkcs15_free_certificate (certder); - return tmperr; + return rc; } krc = ksba_cert_init_from_mem (cert, certder->data, certder->data_len); sc_pkcs15_free_certificate (certder); if (krc) { log_error ("failed to parse the certificate for private key %d: %s\n", - idx, ksba_strerror (krc)); + idx, gpg_strerror (krc)); ksba_cert_release (cert); - return gpg_error (GPG_ERR_CARD); + return krc; } if (card_help_get_keygrip (cert, keygrip)) { diff --git a/scd/card.c b/scd/card.c index 0a71fb2df..042613294 100644 --- a/scd/card.c +++ b/scd/card.c @@ -28,9 +28,10 @@ #ifdef HAVE_OPENSC #include #endif -#include #include "scdaemon.h" +#include + #include "card-common.h" /* Map the SC error codes to the GNUPG ones */ diff --git a/scd/command.c b/scd/command.c index 363b46480..122a2f3c1 100644 --- a/scd/command.c +++ b/scd/command.c @@ -25,11 +25,11 @@ #include #include #include -#include #include #include "scdaemon.h" +#include #include "app-common.h" /* maximum length aloowed as a PIN; used for INQUIRE NEEDPIN */ @@ -453,18 +453,16 @@ cmd_readkey (ASSUAN_CONTEXT ctx, char *line) goto leave; } - kc = ksba_cert_new (); - if (!kc) + rc = ksba_cert_new (&kc); + if (rc) { - rc = out_of_core (); xfree (cert); goto leave; } rc = ksba_cert_init_from_mem (kc, cert, ncert); if (rc) { - log_error ("failed to parse the certificate: %s\n", ksba_strerror (rc)); - rc = map_ksba_err (rc); + log_error ("failed to parse the certificate: %s\n", gpg_strerror (rc)); goto leave; } @@ -531,7 +529,7 @@ pin_cb (void *opaque, const char *info, char **retstr) ASSUAN_CONTEXT ctx = opaque; char *command; int rc; - char *value; + unsigned char *value; size_t valuelen; *retstr = NULL; diff --git a/sm/ChangeLog b/sm/ChangeLog index fa618539c..24f71fba6 100644 --- a/sm/ChangeLog +++ b/sm/ChangeLog @@ -1,3 +1,7 @@ +2003-11-12 Werner Koch + + Adjusted for API changes in Libksba. + 2003-10-31 Werner Koch * certchain.c (gpgsm_validate_chain): Changed to use ksba_isotime_t. diff --git a/sm/base64.c b/sm/base64.c index 07f546e85..aef287250 100644 --- a/sm/base64.c +++ b/sm/base64.c @@ -27,9 +27,10 @@ #include #include +#include "gpgsm.h" + #include -#include "gpgsm.h" #include "i18n.h" #ifdef HAVE_DOSISH_SYSTEM @@ -427,7 +428,7 @@ base64_writer_cb (void *cb_value, const void *buffer, size_t count) parm->base64.idx = idx; parm->base64.quad_count = quad_count; - return ferror (fp) ? KSBA_Write_Error:0; + return ferror (fp) ? gpg_error_from_errno (errno) : 0; } static int @@ -506,11 +507,11 @@ gpgsm_create_reader (Base64Context *ctx, if (!*ctx) return OUT_OF_CORE (errno); - r = ksba_reader_new (); - if (!r) + rc = ksba_reader_new (&r); + if (rc) { xfree (*ctx); *ctx = NULL; - return gpg_error (GPG_ERR_ENOMEM); + return rc; } (*ctx)->u.rparm.fp = fp; @@ -537,7 +538,7 @@ gpgsm_create_reader (Base64Context *ctx, { ksba_reader_release (r); xfree (*ctx); *ctx = NULL; - return map_ksba_err (rc); + return rc; } *r_reader = r; @@ -571,11 +572,11 @@ gpgsm_create_writer (Base64Context *ctx, if (!*ctx) return OUT_OF_CORE (errno); - w = ksba_writer_new (); - if (!w) + rc = ksba_writer_new (&w); + if (rc) { xfree (*ctx); *ctx = NULL; - return gpg_error (GPG_ERR_ENOMEM); + return rc; } if (ctrl->create_pem || ctrl->create_base64) @@ -593,7 +594,7 @@ gpgsm_create_writer (Base64Context *ctx, { ksba_writer_release (w); xfree (*ctx); *ctx = NULL; - return map_ksba_err (rc); + return rc; } *r_writer = w; diff --git a/sm/call-agent.c b/sm/call-agent.c index 4d26e3450..acb4ac190 100644 --- a/sm/call-agent.c +++ b/sm/call-agent.c @@ -622,18 +622,18 @@ learn_cb (void *opaque, const void *buffer, size_t length) /* FIXME: this should go into import.c */ - cert = ksba_cert_new (); - if (!cert) + rc = ksba_cert_new (&cert); + if (rc) { - parm->error = gpg_error (GPG_ERR_ENOMEM); + parm->error = rc; return 0; } rc = ksba_cert_init_from_mem (cert, buf, len); if (rc) { - log_error ("failed to parse a certificate: %s\n", ksba_strerror (rc)); + log_error ("failed to parse a certificate: %s\n", gpg_strerror (rc)); ksba_cert_release (cert); - parm->error = map_ksba_err (rc); + parm->error = rc; return 0; } diff --git a/sm/call-dirmngr.c b/sm/call-dirmngr.c index b182b246c..fa7f34f8b 100644 --- a/sm/call-dirmngr.c +++ b/sm/call-dirmngr.c @@ -357,16 +357,16 @@ lookup_cb (void *opaque, const void *buffer, size_t length) return 0; } - cert = ksba_cert_new (); - if (!cert) + rc = ksba_cert_new (&cert); + if (rc) { - parm->error = gpg_error (GPG_ERR_ENOMEM); + parm->error = rc; return 0; } rc = ksba_cert_init_from_mem (cert, buf, len); if (rc) { - log_error ("failed to parse a certificate: %s\n", ksba_strerror (rc)); + log_error ("failed to parse a certificate: %s\n", gpg_strerror (rc)); } else { diff --git a/sm/certchain.c b/sm/certchain.c index 216b72e0e..a25e08219 100644 --- a/sm/certchain.c +++ b/sm/certchain.c @@ -62,7 +62,7 @@ unknown_criticals (KsbaCert cert) } } if (err && err != -1) - rc = map_ksba_err (err); + rc = err; return rc; } @@ -75,7 +75,7 @@ allowed_ca (KsbaCert cert, int *chainlen) err = ksba_cert_is_ca (cert, &flag, chainlen); if (err) - return map_ksba_err (err); + return err; if (!flag) { log_error (_("issuer certificate is not marked as a CA\n")); @@ -94,10 +94,10 @@ check_cert_policy (KsbaCert cert) int any_critical; err = ksba_cert_get_cert_policies (cert, &policies); - if (err == KSBA_No_Data) + if (gpg_err_code (err) == GPG_ERR_NO_DATA) return 0; /* no policy given */ if (err) - return map_ksba_err (err); + return err; /* STRING is a line delimited list of certifiate policies as stored in the certificate. The line itself is colon delimited where the @@ -471,7 +471,7 @@ gpgsm_validate_chain (CTRL ctrl, KsbaCert cert, ksba_isotime_t r_exptime) if (rc) { log_error (_("certificate with invalid validity: %s\n"), - ksba_strerror (rc)); + gpg_strerror (rc)); rc = gpg_error (GPG_ERR_BAD_CERT); goto leave; } @@ -522,7 +522,7 @@ gpgsm_validate_chain (CTRL ctrl, KsbaCert cert, ksba_isotime_t r_exptime) rc = gpgsm_dirmngr_isvalid (subject_cert); if (rc) { - switch (rc) + switch (gpg_err_code (rc)) { case GPG_ERR_CERT_REVOKED: log_error (_("the certificate has been revoked\n")); diff --git a/sm/certcheck.c b/sm/certcheck.c index 35509c67e..852c75e3a 100644 --- a/sm/certcheck.c +++ b/sm/certcheck.c @@ -129,9 +129,9 @@ gpgsm_check_cert_sig (KsbaCert issuer_cert, KsbaCert cert) rc = ksba_cert_hash (cert, 1, HASH_FNC, md); if (rc) { - log_error ("ksba_cert_hash failed: %s\n", ksba_strerror (rc)); + log_error ("ksba_cert_hash failed: %s\n", gpg_strerror (rc)); gcry_md_close (md); - return map_ksba_err (rc); + return rc; } gcry_md_final (md); diff --git a/sm/certlist.c b/sm/certlist.c index eedc99025..0dfe50e04 100644 --- a/sm/certlist.c +++ b/sm/certlist.c @@ -45,7 +45,7 @@ cert_usage_p (KsbaCert cert, int mode) unsigned int use; err = ksba_cert_get_key_usage (cert, &use); - if (err == KSBA_No_Data) + if (gpg_err_code (err) == GPG_ERR_NO_DATA) { if (opt.verbose && mode < 2) log_info (mode? @@ -56,8 +56,8 @@ cert_usage_p (KsbaCert cert, int mode) if (err) { log_error (_("error getting key usage information: %s\n"), - ksba_strerror (err)); - return map_ksba_err (err); + gpg_strerror (err)); + return err; } if (mode == 4) diff --git a/sm/certreqgen.c b/sm/certreqgen.c index efb3e8c1a..06a788ad9 100644 --- a/sm/certreqgen.c +++ b/sm/certreqgen.c @@ -513,9 +513,9 @@ create_request (struct para_data_s *para, KsbaConstSexp public, int rc = 0; const char *s; - cr = ksba_certreq_new (); - if (!cr) - return gpg_error (GPG_ERR_ENOMEM); + err = ksba_certreq_new (&cr); + if (err) + return err; rc = gcry_md_open (&md, GCRY_MD_SHA1, 0); if (rc) @@ -533,8 +533,8 @@ create_request (struct para_data_s *para, KsbaConstSexp public, if (err) { log_error ("error setting the subject's name: %s\n", - ksba_strerror (err)); - rc = map_ksba_err (err); + gpg_strerror (err)); + rc = err; goto leave; } @@ -557,8 +557,8 @@ create_request (struct para_data_s *para, KsbaConstSexp public, if (err) { log_error ("error setting the subject's alternate name: %s\n", - ksba_strerror (err)); - rc = map_ksba_err (err); + gpg_strerror (err)); + rc = err; goto leave; } } @@ -568,8 +568,8 @@ create_request (struct para_data_s *para, KsbaConstSexp public, if (err) { log_error ("error setting the public key: %s\n", - ksba_strerror (err)); - rc = map_ksba_err (err); + gpg_strerror (err)); + rc = err; goto leave; } @@ -578,8 +578,8 @@ create_request (struct para_data_s *para, KsbaConstSexp public, err = ksba_certreq_build (cr, &stopreason); if (err) { - log_error ("ksba_certreq_build failed: %s\n", ksba_strerror (err)); - rc = map_ksba_err (err); + log_error ("ksba_certreq_build failed: %s\n", gpg_strerror (err)); + rc = err; goto leave; } if (stopreason == KSBA_SR_NEED_SIG) @@ -630,8 +630,8 @@ create_request (struct para_data_s *para, KsbaConstSexp public, if (err) { log_error ("failed to store the sig_val: %s\n", - ksba_strerror (err)); - rc = map_ksba_err (err); + gpg_strerror (err)); + rc = err; goto leave; } } diff --git a/sm/decrypt.c b/sm/decrypt.c index 17483aa49..23858efa8 100644 --- a/sm/decrypt.c +++ b/sm/decrypt.c @@ -159,10 +159,10 @@ decrypt_filter (void *arg, /* fixme: Should we issue an error when we have not seen one full block? */ if (!inlen) - return KSBA_Bug; + return gpg_error (GPG_ERR_BUG); if (maxoutlen < 2*parm->blklen) - return KSBA_Bug; + return gpg_error (GPG_ERR_BUG); /* make some space becuase we will later need an extra block at the end */ maxoutlen -= blklen; @@ -174,7 +174,7 @@ decrypt_filter (void *arg, parm->helpblock[i] = ((const char*)inbuf)[j]; inlen -= j; if (blklen > maxoutlen) - return KSBA_Bug; + return gpg_error (GPG_ERR_BUG); if (i < blklen) { parm->helpblocklen = i; @@ -285,10 +285,10 @@ gpgsm_decrypt (CTRL ctrl, int in_fd, FILE *out_fp) goto leave; } - cms = ksba_cms_new (); - if (!cms) + err = ksba_cms_new (&cms); + if (err) { - rc = gpg_error (GPG_ERR_ENOMEM); + rc = err; goto leave; } @@ -296,8 +296,8 @@ gpgsm_decrypt (CTRL ctrl, int in_fd, FILE *out_fp) if (err) { log_debug ("ksba_cms_set_reader_writer failed: %s\n", - ksba_strerror (err)); - rc = map_ksba_err (err); + gpg_strerror (err)); + rc = err; goto leave; } @@ -307,8 +307,8 @@ gpgsm_decrypt (CTRL ctrl, int in_fd, FILE *out_fp) err = ksba_cms_parse (cms, &stopreason); if (err) { - log_debug ("ksba_cms_parse failed: %s\n", ksba_strerror (err)); - rc = map_ksba_err (err); + log_debug ("ksba_cms_parse failed: %s\n", gpg_strerror (err)); + rc = err; goto leave; } @@ -352,8 +352,8 @@ gpgsm_decrypt (CTRL ctrl, int in_fd, FILE *out_fp) &dfparm.ivlen); if (rc) { - log_error ("error getting IV: %s\n", ksba_strerror (err)); - rc = map_ksba_err (err); + log_error ("error getting IV: %s\n", gpg_strerror (err)); + rc = err; goto leave; } @@ -369,7 +369,7 @@ gpgsm_decrypt (CTRL ctrl, int in_fd, FILE *out_fp) break; /* no more recipients */ if (err) log_error ("recp %d - error getting info: %s\n", - recp, ksba_strerror (err)); + recp, gpg_strerror (err)); else { KsbaCert cert = NULL; @@ -461,10 +461,8 @@ gpgsm_decrypt (CTRL ctrl, int in_fd, FILE *out_fp) dfparm.lastblock, dfparm.blklen - npadding); if (rc) - { - rc = map_ksba_err (rc); goto leave; - } + for (i=dfparm.blklen - npadding; i < dfparm.blklen; i++) { if (dfparm.lastblock[i] != npadding) diff --git a/sm/encrypt.c b/sm/encrypt.c index 4eef20c73..616949bf4 100644 --- a/sm/encrypt.c +++ b/sm/encrypt.c @@ -330,16 +330,14 @@ gpgsm_encrypt (CTRL ctrl, CERTLIST recplist, int data_fd, FILE *out_fp) goto leave; } - reader = ksba_reader_new (); - if (!reader) - rc = KSBA_Out_Of_Core; + err = ksba_reader_new (&reader); + if (err) + rc = err; if (!rc) rc = ksba_reader_set_cb (reader, encrypt_cb, &encparm); if (rc) - { - rc = map_ksba_err (rc); goto leave; - } + encparm.fp = data_fp; ctrl->pem_name = "ENCRYPTED MESSAGE"; @@ -350,10 +348,10 @@ gpgsm_encrypt (CTRL ctrl, CERTLIST recplist, int data_fd, FILE *out_fp) goto leave; } - cms = ksba_cms_new (); - if (!cms) + err = ksba_cms_new (&cms); + if (err) { - rc = gpg_error (GPG_ERR_ENOMEM); + rc = err; goto leave; } @@ -361,8 +359,8 @@ gpgsm_encrypt (CTRL ctrl, CERTLIST recplist, int data_fd, FILE *out_fp) if (err) { log_debug ("ksba_cms_set_reader_writer failed: %s\n", - ksba_strerror (err)); - rc = map_ksba_err (err); + gpg_strerror (err)); + rc = err; goto leave; } @@ -374,8 +372,8 @@ gpgsm_encrypt (CTRL ctrl, CERTLIST recplist, int data_fd, FILE *out_fp) if (err) { log_debug ("ksba_cms_set_content_type failed: %s\n", - ksba_strerror (err)); - rc = map_ksba_err (err); + gpg_strerror (err)); + rc = err; goto leave; } @@ -399,8 +397,8 @@ gpgsm_encrypt (CTRL ctrl, CERTLIST recplist, int data_fd, FILE *out_fp) if (err) { log_error ("ksba_cms_set_content_enc_algo failed: %s\n", - ksba_strerror (err)); - rc = map_ksba_err (err); + gpg_strerror (err)); + rc = err; goto leave; } @@ -432,8 +430,8 @@ gpgsm_encrypt (CTRL ctrl, CERTLIST recplist, int data_fd, FILE *out_fp) if (err) { log_error ("ksba_cms_add_recipient failed: %s\n", - ksba_strerror (err)); - rc = map_ksba_err (err); + gpg_strerror (err)); + rc = err; xfree (encval); goto leave; } @@ -443,8 +441,8 @@ gpgsm_encrypt (CTRL ctrl, CERTLIST recplist, int data_fd, FILE *out_fp) if (err) { log_error ("ksba_cms_set_enc_val failed: %s\n", - ksba_strerror (err)); - rc = map_ksba_err (err); + gpg_strerror (err)); + rc = err; goto leave; } } @@ -456,8 +454,8 @@ gpgsm_encrypt (CTRL ctrl, CERTLIST recplist, int data_fd, FILE *out_fp) err = ksba_cms_build (cms, &stopreason); if (err) { - log_debug ("ksba_cms_build failed: %s\n", ksba_strerror (err)); - rc = map_ksba_err (err); + log_debug ("ksba_cms_build failed: %s\n", gpg_strerror (err)); + rc = err; goto leave; } } diff --git a/sm/export.c b/sm/export.c index 93a55debc..73c119fb0 100644 --- a/sm/export.c +++ b/sm/export.c @@ -155,7 +155,7 @@ gpgsm_export (CTRL ctrl, STRLIST names, FILE *fp) rc = ksba_writer_write (writer, image, imagelen); if (rc) { - log_error ("write error: %s\n", ksba_strerror (rc)); + log_error ("write error: %s\n", gpg_strerror (rc)); goto leave; } diff --git a/sm/fingerprint.c b/sm/fingerprint.c index 028c08aab..ec1ed91f2 100644 --- a/sm/fingerprint.c +++ b/sm/fingerprint.c @@ -70,7 +70,7 @@ gpgsm_get_fingerprint (KsbaCert cert, int algo, char *array, int *r_len) rc = ksba_cert_hash (cert, 0, HASH_FNC, md); if (rc) { - log_error ("ksba_cert_hash failed: %s\n", ksba_strerror (rc)); + log_error ("ksba_cert_hash failed: %s\n", gpg_strerror (rc)); gcry_md_close (md); memset (array, 0xff, len); /* better return an invalid fpr than NULL */ return array; @@ -204,7 +204,7 @@ gpgsm_get_keygrip_hexstring (KsbaCert cert) serial number for this. In most cases the serial number is not that large and the resulting string can be passed on an assuan command line. Everything is hexencoded with the serialnumber - delimted from the has by a dot. + delimited from the hash by a dot. The caller must free the string. */ diff --git a/sm/import.c b/sm/import.c index 17dc3d66c..758cd3208 100644 --- a/sm/import.c +++ b/sm/import.c @@ -221,19 +221,15 @@ import_one (CTRL ctrl, struct stats_s *stats, int in_fd) KsbaStopReason stopreason; int i; - cms = ksba_cms_new (); - if (!cms) - { - rc = gpg_error (GPG_ERR_ENOMEM); - goto leave; - } + rc = ksba_cms_new (&cms); + if (rc) + goto leave; rc = ksba_cms_set_reader_writer (cms, reader, NULL); if (rc) { log_error ("ksba_cms_set_reader_writer failed: %s\n", - ksba_strerror (rc)); - rc = map_ksba_err (rc); + gpg_strerror (rc)); goto leave; } @@ -243,8 +239,7 @@ import_one (CTRL ctrl, struct stats_s *stats, int in_fd) rc = ksba_cms_parse (cms, &stopreason); if (rc) { - log_error ("ksba_cms_parse failed: %s\n", ksba_strerror (rc)); - rc = map_ksba_err (rc); + log_error ("ksba_cms_parse failed: %s\n", gpg_strerror (rc)); goto leave; } @@ -265,19 +260,13 @@ import_one (CTRL ctrl, struct stats_s *stats, int in_fd) else if (ct == KSBA_CT_NONE) { /* Failed to identify this message - assume a certificate */ - cert = ksba_cert_new (); - if (!cert) - { - rc = gpg_error (GPG_ERR_ENOMEM); - goto leave; - } + rc = ksba_cert_new (&cert); + if (rc) + goto leave; rc = ksba_cert_read_der (cert, reader); if (rc) - { - rc = map_ksba_err (rc); - goto leave; - } + goto leave; check_and_store (ctrl, stats, cert, 0); } diff --git a/sm/keylist.c b/sm/keylist.c index 548f2a452..7b7402fab 100644 --- a/sm/keylist.c +++ b/sm/keylist.c @@ -68,7 +68,7 @@ print_capabilities (KsbaCert cert, FILE *fp) unsigned int use; err = ksba_cert_get_key_usage (cert, &use); - if (err == KSBA_No_Data) + if (gpg_err_code (err) == GPG_ERR_NO_DATA) { putc ('e', fp); putc ('s', fp); @@ -81,7 +81,7 @@ print_capabilities (KsbaCert cert, FILE *fp) if (err) { log_error (_("error getting key usage information: %s\n"), - ksba_strerror (err)); + gpg_strerror (err)); return; } @@ -328,11 +328,11 @@ list_cert_std (KsbaCert cert, FILE *fp, int have_secret) putc ('\n', fp); kerr = ksba_cert_get_key_usage (cert, &kusage); - if (kerr != KSBA_No_Data) + if (gpg_err_code (kerr) != GPG_ERR_NO_DATA) { fputs (" key usage:", fp); if (kerr) - fprintf (fp, " [error: %s]", ksba_strerror (kerr)); + fprintf (fp, " [error: %s]", gpg_strerror (kerr)); else { if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE)) @@ -358,11 +358,11 @@ list_cert_std (KsbaCert cert, FILE *fp, int have_secret) } kerr = ksba_cert_get_cert_policies (cert, &string); - if (kerr != KSBA_No_Data) + if (gpg_err_code (kerr) != GPG_ERR_NO_DATA) { fputs (" policies: ", fp); if (kerr) - fprintf (fp, "[error: %s]", ksba_strerror (kerr)); + fprintf (fp, "[error: %s]", gpg_strerror (kerr)); else { for (p=string; *p; p++) @@ -381,7 +381,7 @@ list_cert_std (KsbaCert cert, FILE *fp, int have_secret) { fputs (" chain length: ", fp); if (kerr) - fprintf (fp, "[error: %s]", ksba_strerror (kerr)); + fprintf (fp, "[error: %s]", gpg_strerror (kerr)); else if (chainlen == -1) fputs ("unlimited", fp); else diff --git a/sm/misc.c b/sm/misc.c index 3260b90b2..4ffa7153e 100644 --- a/sm/misc.c +++ b/sm/misc.c @@ -26,7 +26,8 @@ #include #include +#include "gpgsm.h" + #include -#include "gpgsm.h" diff --git a/sm/sign.c b/sm/sign.c index 8e7431312..95ce8d5dd 100644 --- a/sm/sign.c +++ b/sm/sign.c @@ -88,8 +88,8 @@ hash_and_copy_data (int fd, gcry_md_hd_t md, KsbaWriter writer) err = ksba_writer_write_octet_string (writer, buffer, nread, 0); if (err) { - log_error ("write failed: %s\n", ksba_strerror (err)); - rc = map_ksba_err (err); + log_error ("write failed: %s\n", gpg_strerror (err)); + rc = err; } } } @@ -114,8 +114,8 @@ hash_and_copy_data (int fd, gcry_md_hd_t md, KsbaWriter writer) err = ksba_writer_write_octet_string (writer, NULL, 0, 1); if (err) { - log_error ("write failed: %s\n", ksba_strerror (err)); - rc = map_ksba_err (err); + log_error ("write failed: %s\n", gpg_strerror (err)); + rc = err; } } @@ -278,8 +278,8 @@ add_certificate_list (CTRL ctrl, KsbaCMS cms, KsbaCert cert) ksba_failure: ksba_cert_release (cert); - log_error ("ksba_cms_add_cert failed: %s\n", ksba_strerror (err)); - return map_ksba_err (err); + log_error ("ksba_cms_add_cert failed: %s\n", gpg_strerror (err)); + return err; } @@ -326,10 +326,10 @@ gpgsm_sign (CTRL ctrl, CERTLIST signerlist, goto leave; } - cms = ksba_cms_new (); - if (!cms) + err = ksba_cms_new (&cms); + if (err) { - rc = gpg_error (GPG_ERR_ENOMEM); + rc = err; goto leave; } @@ -337,8 +337,8 @@ gpgsm_sign (CTRL ctrl, CERTLIST signerlist, if (err) { log_debug ("ksba_cms_set_reader_writer failed: %s\n", - ksba_strerror (err)); - rc = map_ksba_err (err); + gpg_strerror (err)); + rc = err; goto leave; } @@ -349,8 +349,8 @@ gpgsm_sign (CTRL ctrl, CERTLIST signerlist, if (err) { log_debug ("ksba_cms_set_content_type failed: %s\n", - ksba_strerror (err)); - rc = map_ksba_err (err); + gpg_strerror (err)); + rc = err; goto leave; } @@ -386,8 +386,8 @@ gpgsm_sign (CTRL ctrl, CERTLIST signerlist, err = ksba_cms_add_signer (cms, cl->cert); if (err) { - log_error ("ksba_cms_add_signer failed: %s\n", ksba_strerror (err)); - rc = map_ksba_err (err); + log_error ("ksba_cms_add_signer failed: %s\n", gpg_strerror (err)); + rc = err; goto leave; } rc = add_certificate_list (ctrl, cms, cl->cert); @@ -402,8 +402,8 @@ gpgsm_sign (CTRL ctrl, CERTLIST signerlist, if (err) { log_debug ("ksba_cms_add_digest_algo failed: %s\n", - ksba_strerror (err)); - rc = map_ksba_err (err); + gpg_strerror (err)); + rc = err; goto leave; } } @@ -455,8 +455,8 @@ gpgsm_sign (CTRL ctrl, CERTLIST signerlist, if (err) { log_error ("ksba_cms_set_message_digest failed: %s\n", - ksba_strerror (err)); - rc = map_ksba_err (err); + gpg_strerror (err)); + rc = err; goto leave; } } @@ -469,8 +469,8 @@ gpgsm_sign (CTRL ctrl, CERTLIST signerlist, if (err) { log_error ("ksba_cms_set_signing_time failed: %s\n", - ksba_strerror (err)); - rc = map_ksba_err (err); + gpg_strerror (err)); + rc = err; goto leave; } } @@ -480,8 +480,8 @@ gpgsm_sign (CTRL ctrl, CERTLIST signerlist, err = ksba_cms_build (cms, &stopreason); if (err) { - log_debug ("ksba_cms_build failed: %s\n", ksba_strerror (err)); - rc = map_ksba_err (err); + log_debug ("ksba_cms_build failed: %s\n", gpg_strerror (err)); + rc = err; goto leave; } @@ -515,8 +515,8 @@ gpgsm_sign (CTRL ctrl, CERTLIST signerlist, if (err) { log_error ("ksba_cms_set_message_digest failed: %s\n", - ksba_strerror (err)); - rc = map_ksba_err (err); + gpg_strerror (err)); + rc = err; goto leave; } } @@ -546,7 +546,7 @@ gpgsm_sign (CTRL ctrl, CERTLIST signerlist, if (rc) { log_debug ("hashing signed attrs failed: %s\n", - ksba_strerror (rc)); + gpg_strerror (rc)); gcry_md_close (md); goto leave; } @@ -563,8 +563,8 @@ gpgsm_sign (CTRL ctrl, CERTLIST signerlist, if (err) { log_error ("failed to store the signature: %s\n", - ksba_strerror (err)); - rc = map_ksba_err (err); + gpg_strerror (err)); + rc = err; gcry_md_close (md); goto leave; } diff --git a/sm/verify.c b/sm/verify.c index 3c333129b..201fc7b55 100644 --- a/sm/verify.c +++ b/sm/verify.c @@ -135,8 +135,8 @@ gpgsm_verify (CTRL ctrl, int in_fd, int data_fd, FILE *out_fp) } } - cms = ksba_cms_new (); - if (!cms) + err = ksba_cms_new (&cms); + if (err) { rc = gpg_error (GPG_ERR_ENOMEM); goto leave; @@ -146,8 +146,8 @@ gpgsm_verify (CTRL ctrl, int in_fd, int data_fd, FILE *out_fp) if (err) { log_error ("ksba_cms_set_reader_writer failed: %s\n", - ksba_strerror (err)); - rc = map_ksba_err (err); + gpg_strerror (err)); + rc = err; goto leave; } @@ -166,8 +166,8 @@ gpgsm_verify (CTRL ctrl, int in_fd, int data_fd, FILE *out_fp) err = ksba_cms_parse (cms, &stopreason); if (err) { - log_error ("ksba_cms_parse failed: %s\n", ksba_strerror (err)); - rc = map_ksba_err (err); + log_error ("ksba_cms_parse failed: %s\n", gpg_strerror (err)); + rc = err; goto leave; } @@ -250,7 +250,8 @@ gpgsm_verify (CTRL ctrl, int in_fd, int data_fd, FILE *out_fp) char *ctattr; err = ksba_cms_get_issuer_serial (cms, signer, &issuer, &serial); - if (!signer && err == KSBA_No_Data && data_fd == -1 && is_detached) + if (!signer && gpg_err_code (err) == GPG_ERR_NO_DATA + && data_fd == -1 && is_detached) { log_info ("certs-only message accepted\n"); err = 0; @@ -272,11 +273,11 @@ gpgsm_verify (CTRL ctrl, int in_fd, int data_fd, FILE *out_fp) } err = ksba_cms_get_signing_time (cms, signer, sigtime); - if (err == KSBA_No_Data) + if (gpg_err_code (err) == GPG_ERR_NO_DATA) *sigtime = 0; else if (err) { - log_error ("error getting signing time: %s\n", ksba_strerror (err)); + log_error ("error getting signing time: %s\n", gpg_strerror (err)); *sigtime = 0; /* FIXME: we can't encode an error in the time string. */ } @@ -295,7 +296,7 @@ gpgsm_verify (CTRL ctrl, int in_fd, int data_fd, FILE *out_fp) goto next_signer; } } - else if (err == KSBA_No_Data) + else if (gpg_err_code (err) == GPG_ERR_NO_DATA) { assert (!msgdigest); err = 0; @@ -328,7 +329,7 @@ gpgsm_verify (CTRL ctrl, int in_fd, int data_fd, FILE *out_fp) else if (err != -1) { log_error ("error getting content-type attribute: %s\n", - ksba_strerror (err)); + gpg_strerror (err)); goto next_signer; } err = 0; @@ -420,7 +421,7 @@ gpgsm_verify (CTRL ctrl, int in_fd, int data_fd, FILE *out_fp) if (rc) { log_error ("hashing signed attrs failed: %s\n", - ksba_strerror (rc)); + gpg_strerror (rc)); gcry_md_close (md); goto next_signer; } @@ -514,9 +515,9 @@ gpgsm_verify (CTRL ctrl, int in_fd, int data_fd, FILE *out_fp) } rc = 0; if (err) - { - log_error ("ksba error: %s\n", ksba_strerror (err)); - rc = map_ksba_err (rc); + { /* FIXME: still needed? */ + log_error ("ksba error: %s\n", gpg_strerror (err)); + rc = err; } -- cgit From 671f696e55256b7a1788ef379a10edf273baa98b Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 16 Jan 2004 17:39:58 +0000 Subject: * findkey.c (agent_key_from_file): Now return an error code so that we have more detailed error messages in the upper layers. This fixes the handling pinentry's cancel button. * pksign.c (agent_pksign): Changed accordingly. * pkdecrypt.c (agent_pkdecrypt): Ditto. * command.c (cmd_passwd): Ditto. --- TODO | 3 ++- agent/ChangeLog | 9 +++++++++ agent/agent.h | 4 ++-- agent/command.c | 6 +++--- agent/findkey.c | 48 ++++++++++++++++++++++++++++-------------------- agent/pkdecrypt.c | 5 ++--- agent/pksign.c | 7 +++---- 7 files changed, 49 insertions(+), 33 deletions(-) (limited to 'agent/command.c') diff --git a/TODO b/TODO index f1355c03c..12fd998aa 100644 --- a/TODO +++ b/TODO @@ -31,9 +31,10 @@ might want to have an agent context for each service request ** Don't hardcode the use of RSA. * sm/gpgsm.c -** Support --output +** Support --output for all commands ** mark all unimplemented commands and options. ** Print a hint when MD2 is the cause for a problem. +** Implement --default-key * sm/keydb.c ** Check file permissions diff --git a/agent/ChangeLog b/agent/ChangeLog index 0e165e31b..263259f6a 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,12 @@ +2004-01-16 Werner Koch + + * findkey.c (agent_key_from_file): Now return an error code so + that we have more detailed error messages in the upper layers. + This fixes the handling pinentry's cancel button. + * pksign.c (agent_pksign): Changed accordingly. + * pkdecrypt.c (agent_pkdecrypt): Ditto. + * command.c (cmd_passwd): Ditto. + 2003-12-16 Werner Koch * gpg-agent.c (main): Set the prefixes for assuan logging. diff --git a/agent/agent.h b/agent/agent.h index eb4f4e32d..95b0ba4cd 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -130,9 +130,9 @@ void start_command_handler (int, int); /*-- findkey.c --*/ int agent_write_private_key (const unsigned char *grip, const void *buffer, size_t length, int force); -gcry_sexp_t agent_key_from_file (CTRL ctrl, const unsigned char *grip, +gpg_error_t agent_key_from_file (CTRL ctrl, const unsigned char *grip, unsigned char **shadow_info, - int ignore_cache); + int ignore_cache, gcry_sexp_t *result); int agent_key_available (const unsigned char *grip); /*-- query.c --*/ diff --git a/agent/command.c b/agent/command.c index a3e1c514a..0406ea439 100644 --- a/agent/command.c +++ b/agent/command.c @@ -606,9 +606,9 @@ cmd_passwd (ASSUAN_CONTEXT ctx, char *line) return rc; /* we can't jump to leave because this is already an Assuan error code. */ - s_skey = agent_key_from_file (ctrl, grip, &shadow_info, 1); - if (!s_skey && !shadow_info) - rc = gpg_error (GPG_ERR_NO_SECKEY); + rc = agent_key_from_file (ctrl, grip, &shadow_info, 1, &s_skey); + if (rc) + ; else if (!s_skey) { log_error ("changing a smartcard PIN is not yet supported\n"); diff --git a/agent/findkey.c b/agent/findkey.c index db36cb1b9..a9566a2c7 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -198,16 +198,16 @@ unprotect (CTRL ctrl, -/* Return the secret key as an S-Exp after locating it using the grip. - Returns NULL if key is not available or the operation should be - diverted to a token. In the latter case shadow_info will point to - an allocated S-Expression with the shadow_info part from the file. - With IGNORE_CACHE passed as true the passphrase is not taken from - the cache.*/ -gcry_sexp_t +/* Return the secret key as an S-Exp in RESULT after locating it using + the grip. Returns NULL in RESULT if the operation should be + diverted to a token; SHADOW_INFO will point then to an allocated + S-Expression with the shadow_info part from the file. With + IGNORE_CACHE passed as true the passphrase is not taken from the + cache.*/ +gpg_error_t agent_key_from_file (CTRL ctrl, const unsigned char *grip, unsigned char **shadow_info, - int ignore_cache) + int ignore_cache, gcry_sexp_t *result) { int i, rc; char *fname; @@ -217,7 +217,9 @@ agent_key_from_file (CTRL ctrl, size_t len, buflen, erroff; gcry_sexp_t s_skey; char hexgrip[40+4+1]; + int got_shadow_info = 0; + *result = NULL; if (shadow_info) *shadow_info = NULL; @@ -229,28 +231,31 @@ agent_key_from_file (CTRL ctrl, fp = fopen (fname, "rb"); if (!fp) { + rc = gpg_error_from_errno (errno); log_error ("can't open `%s': %s\n", fname, strerror (errno)); xfree (fname); - return NULL; + return rc; } if (fstat (fileno(fp), &st)) { + rc = gpg_error_from_errno (errno); log_error ("can't stat `%s': %s\n", fname, strerror (errno)); xfree (fname); fclose (fp); - return NULL; + return rc; } buflen = st.st_size; buf = xmalloc (buflen+1); if (fread (buf, buflen, 1, fp) != 1) { + rc = gpg_error_from_errno (errno); log_error ("error reading `%s': %s\n", fname, strerror (errno)); xfree (fname); fclose (fp); xfree (buf); - return NULL; + return rc; } rc = gcry_sexp_sscan (&s_skey, &erroff, buf, buflen); @@ -261,15 +266,16 @@ agent_key_from_file (CTRL ctrl, { log_error ("failed to build S-Exp (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (rc)); - return NULL; + return rc; } len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); assert (len); buf = xtrymalloc (len); if (!buf) { + rc = out_of_core (); gcry_sexp_release (s_skey); - return NULL; + return rc; } len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, buf, len); assert (len); @@ -303,26 +309,27 @@ agent_key_from_file (CTRL ctrl, { memcpy (*shadow_info, s, n); rc = 0; + got_shadow_info = 1; } } if (rc) log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc)); } - rc = -1; /* ugly interface: we return an error but keep a value - in shadow_info. */ + else + rc = gpg_error (GPG_ERR_UNUSABLE_SECKEY); break; default: log_error ("invalid private key format\n"); rc = gpg_error (GPG_ERR_BAD_SECKEY); break; } - if (rc) + if (rc || got_shadow_info) { xfree (buf); - return NULL; + return rc; } - /* arggg FIXME: does scan support secure memory? */ + /* Arggg FIXME: does scan support secure memory? */ rc = gcry_sexp_sscan (&s_skey, &erroff, buf, gcry_sexp_canon_len (buf, 0, NULL, NULL)); xfree (buf); @@ -330,10 +337,11 @@ agent_key_from_file (CTRL ctrl, { log_error ("failed to build S-Exp (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (rc)); - return NULL; + return rc; } - return s_skey; + *result = s_skey; + return 0; } /* Return the secret key as an S-Exp after locating it using the grip. diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c index d17c688a0..cc3a2f33f 100644 --- a/agent/pkdecrypt.c +++ b/agent/pkdecrypt.c @@ -64,11 +64,10 @@ agent_pkdecrypt (CTRL ctrl, const char *ciphertext, size_t ciphertextlen, log_printhex ("keygrip:", ctrl->keygrip, 20); log_printhex ("cipher: ", ciphertext, ciphertextlen); } - s_skey = agent_key_from_file (ctrl, ctrl->keygrip, &shadow_info, 0); - if (!s_skey && !shadow_info) + rc = agent_key_from_file (ctrl, ctrl->keygrip, &shadow_info, 0, &s_skey); + if (rc) { log_error ("failed to read the secret key\n"); - rc = gpg_error (GPG_ERR_NO_SECKEY); goto leave; } diff --git a/agent/pksign.c b/agent/pksign.c index d5fe17bb7..34381365e 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -81,12 +81,11 @@ agent_pksign (CTRL ctrl, FILE *outfp, int ignore_cache) if (!ctrl->have_keygrip) return gpg_error (GPG_ERR_NO_SECKEY); - s_skey = agent_key_from_file (ctrl, - ctrl->keygrip, &shadow_info, ignore_cache); - if (!s_skey && !shadow_info) + rc = agent_key_from_file (ctrl, ctrl->keygrip, + &shadow_info, ignore_cache, &s_skey); + if (rc) { log_error ("failed to read the secret key\n"); - rc = gpg_error (GPG_ERR_NO_SECKEY); goto leave; } -- cgit From b11106ebf0e34caae45a68b87fb81f63faf2004f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 29 Jan 2004 20:17:27 +0000 Subject: * agent.h (server_control_s): Add connection_fd field. * command.c (start_command_handler): Init it here. * gpg-agent.c (agent_init_default_ctrl): and here. * call-scd.c: Add the CTRL arg to all functions calling start_scd and pass it to start_scd. Changed all callers (start_scd): Keep track of the current active connection. (agent_reset_scd): New. * command.c (start_command_handler): Call it here. * learncard.c (agent_handle_learn): Add arg CTRL; changed caller. (send_cert_back): Ditto. --- agent/ChangeLog | 15 +++++++- agent/agent.h | 23 ++++++++---- agent/call-scd.c | 107 +++++++++++++++++++++++++++++++++++++++++------------ agent/command.c | 6 ++- agent/divert-scd.c | 15 ++++++-- agent/gpg-agent.c | 2 + agent/learncard.c | 16 ++++---- 7 files changed, 138 insertions(+), 46 deletions(-) (limited to 'agent/command.c') diff --git a/agent/ChangeLog b/agent/ChangeLog index 73b9c4596..4cdaadef6 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,16 @@ +2004-01-29 Werner Koch + + * agent.h (server_control_s): Add connection_fd field. + * command.c (start_command_handler): Init it here. + * gpg-agent.c (agent_init_default_ctrl): and here. + * call-scd.c: Add the CTRL arg to all functions calling start_scd + and pass it to start_scd. Changed all callers + (start_scd): Keep track of the current active connection. + (agent_reset_scd): New. + * command.c (start_command_handler): Call it here. + * learncard.c (agent_handle_learn): Add arg CTRL; changed caller. + (send_cert_back): Ditto. + 2004-01-28 Werner Koch * trustlist.c (agent_marktrusted): Check whether the trustlist is @@ -18,7 +31,7 @@ * findkey.c (agent_key_from_file): Now return an error code so that we have more detailed error messages in the upper layers. - This fixes the handling pinentry's cancel button. + This fixes the handling of pinentry's cancel button. * pksign.c (agent_pksign): Changed accordingly. * pkdecrypt.c (agent_pkdecrypt): Ditto. * command.c (cmd_passwd): Ditto. diff --git a/agent/agent.h b/agent/agent.h index 95b0ba4cd..b70a4ae2d 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -83,6 +83,7 @@ struct server_local_s; struct server_control_s { struct server_local_s *server_local; + int connection_fd; /* -1 or an identifier for the current connection. */ char *display; char *ttyname; char *ttytype; @@ -98,6 +99,7 @@ struct server_control_s { }; typedef struct server_control_s *CTRL; +typedef struct server_control_s *ctrl_t; struct pin_entry_info_s { @@ -194,33 +196,38 @@ int divert_generic_cmd (CTRL ctrl, const char *cmdline, void *assuan_context); /*-- call-scd.c --*/ -int agent_card_learn (void (*kpinfo_cb)(void*, const char *), +int agent_reset_scd (ctrl_t ctrl); +int agent_card_learn (ctrl_t ctrl, + void (*kpinfo_cb)(void*, const char *), void *kpinfo_cb_arg, void (*certinfo_cb)(void*, const char *), void *certinfo_cb_arg, void (*sinfo_cb)(void*, const char *, size_t, const char *), void *sinfo_cb_arg); -int agent_card_serialno (char **r_serialno); -int agent_card_pksign (const char *keyid, +int agent_card_serialno (ctrl_t ctrl, char **r_serialno); +int agent_card_pksign (ctrl_t ctrl, + const char *keyid, int (*getpin_cb)(void *, const char *, char*, size_t), void *getpin_cb_arg, const unsigned char *indata, size_t indatalen, char **r_buf, size_t *r_buflen); -int agent_card_pkdecrypt (const char *keyid, +int agent_card_pkdecrypt (ctrl_t ctrl, + const char *keyid, int (*getpin_cb)(void *, const char *, char*,size_t), void *getpin_cb_arg, const unsigned char *indata, size_t indatalen, char **r_buf, size_t *r_buflen); -int agent_card_readcert (const char *id, char **r_buf, size_t *r_buflen); -int agent_card_readkey (const char *id, unsigned char **r_buf); -int agent_card_scd (const char *cmdline, +int agent_card_readcert (ctrl_t ctrl, + const char *id, char **r_buf, size_t *r_buflen); +int agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf); +int agent_card_scd (ctrl_t ctrl, const char *cmdline, int (*getpin_cb)(void *, const char *, char*, size_t), void *getpin_cb_arg, void *assuan_context); /*-- learncard.c --*/ -int agent_handle_learn (void *assuan_context); +int agent_handle_learn (ctrl_t ctrl, void *assuan_context); #endif /*AGENT_H*/ diff --git a/agent/call-scd.c b/agent/call-scd.c index f205fb074..0bb365ced 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -50,6 +50,17 @@ static ASSUAN_CONTEXT scd_ctx = NULL; #ifdef USE_GNU_PTH static pth_mutex_t scd_lock = PTH_MUTEX_INIT; #endif +/* We need to keep track of the connection currently using the SCD. + For a pipe server this is all a NOP becuase the connection will + always have the conenction indicator -1. agent_reset_scd releases + the active connection; i.e. sets it back to -1, so that a new + connection can start using the SCD. If we eventually allow + multiple SCD session we will either make scdaemon multi-threaded or + fork of a new scdaemon and let it see how it can get access to a + reader. +*/ +static int active_connection_fd = -1; +static int active_connection = 0; /* callback parameter for learn card */ struct learn_parm_s { @@ -162,9 +173,10 @@ atfork_cb (void *opaque, int where) } -/* Fork off the SCdaemon if this has not already been done */ +/* Fork off the SCdaemon if this has not already been done. Note that + this fucntion alos locks the daemon. */ static int -start_scd (void) +start_scd (ctrl_t ctrl) { int rc; const char *pgmname; @@ -182,9 +194,20 @@ start_scd (void) #endif if (scd_ctx) - return 0; /* No need to serialize things because the agent is - expected to tun as a single-thread (or may be in - future using libpth) */ + { + /* If we are not the connection currently using the SCD, return + an error. */ + if (!active_connection) + { + active_connection_fd = ctrl->connection_fd; + active_connection = 1; + } + else if (ctrl->connection_fd != active_connection_fd) + return unlock_scd (gpg_error (GPG_ERR_CONFLICT)); + + /* Okay, we scdaemon already started and used by us. */ + return 0; + } if (opt.verbose) log_info ("no running SCdaemon - starting it\n"); @@ -226,13 +249,45 @@ start_scd (void) return unlock_scd (gpg_error (GPG_ERR_NO_SCDAEMON)); } scd_ctx = ctx; - + active_connection_fd = ctrl->connection_fd; + active_connection = 1; + if (DBG_ASSUAN) log_debug ("connection to SCdaemon established\n"); + return 0; } + +/* Reset the SCD if it has been used. */ +int +agent_reset_scd (ctrl_t ctrl) +{ + int rc = 0; + +#ifdef USE_GNU_PTH + if (!pth_mutex_acquire (&scd_lock, 0, NULL)) + { + log_error ("failed to acquire the SCD lock for reset\n"); + return gpg_error (GPG_ERR_INTERNAL); + } +#endif + if (active_connection && active_connection_fd == ctrl->connection_fd) + { + if (scd_ctx) + rc = assuan_transact (scd_ctx, "RESET", NULL, NULL, + NULL, NULL, NULL, NULL); + active_connection_fd = -1; + active_connection = 0; + } + + return unlock_scd (map_assuan_err (rc)); +} + + + + static AssuanError learn_status_cb (void *opaque, const char *line) @@ -264,7 +319,8 @@ learn_status_cb (void *opaque, const char *line) /* Perform the learn command and return a list of all private keys stored on the card. */ int -agent_card_learn (void (*kpinfo_cb)(void*, const char *), +agent_card_learn (ctrl_t ctrl, + void (*kpinfo_cb)(void*, const char *), void *kpinfo_cb_arg, void (*certinfo_cb)(void*, const char *), void *certinfo_cb_arg, @@ -274,7 +330,7 @@ agent_card_learn (void (*kpinfo_cb)(void*, const char *), int rc; struct learn_parm_s parm; - rc = start_scd (); + rc = start_scd (ctrl); if (rc) return rc; @@ -330,12 +386,12 @@ get_serialno_cb (void *opaque, const char *line) /* Return the serial number of the card or an appropriate error. The serial number is returned as a hexstring. */ int -agent_card_serialno (char **r_serialno) +agent_card_serialno (ctrl_t ctrl, char **r_serialno) { int rc; char *serialno = NULL; - rc = start_scd (); + rc = start_scd (ctrl); if (rc) return rc; @@ -405,7 +461,8 @@ inq_needpin (void *opaque, const char *line) /* Create a signature using the current card */ int -agent_card_pksign (const char *keyid, +agent_card_pksign (ctrl_t ctrl, + const char *keyid, int (*getpin_cb)(void *, const char *, char*, size_t), void *getpin_cb_arg, const unsigned char *indata, size_t indatalen, @@ -420,7 +477,7 @@ agent_card_pksign (const char *keyid, size_t sigbuflen; *r_buf = NULL; - rc = start_scd (); + rc = start_scd (ctrl); if (rc) return rc; @@ -476,11 +533,12 @@ agent_card_pksign (const char *keyid, /* Decipher INDATA using the current card. Note that the returned value is */ int -agent_card_pkdecrypt (const char *keyid, - int (*getpin_cb)(void *, const char *, char*, size_t), - void *getpin_cb_arg, - const unsigned char *indata, size_t indatalen, - char **r_buf, size_t *r_buflen) +agent_card_pkdecrypt (ctrl_t ctrl, + const char *keyid, + int (*getpin_cb)(void *, const char *, char*, size_t), + void *getpin_cb_arg, + const unsigned char *indata, size_t indatalen, + char **r_buf, size_t *r_buflen) { int rc, i; char *p, line[ASSUAN_LINELENGTH]; @@ -489,7 +547,7 @@ agent_card_pkdecrypt (const char *keyid, size_t len; *r_buf = NULL; - rc = start_scd (); + rc = start_scd (ctrl); if (rc) return rc; @@ -531,7 +589,8 @@ agent_card_pkdecrypt (const char *keyid, /* Read a certificate with ID into R_BUF and R_BUFLEN. */ int -agent_card_readcert (const char *id, char **r_buf, size_t *r_buflen) +agent_card_readcert (ctrl_t ctrl, + const char *id, char **r_buf, size_t *r_buflen) { int rc; char line[ASSUAN_LINELENGTH]; @@ -539,7 +598,7 @@ agent_card_readcert (const char *id, char **r_buf, size_t *r_buflen) size_t len; *r_buf = NULL; - rc = start_scd (); + rc = start_scd (ctrl); if (rc) return rc; @@ -567,7 +626,7 @@ agent_card_readcert (const char *id, char **r_buf, size_t *r_buflen) /* Read a key with ID and return it in an allocate buffer pointed to by r_BUF as a valid S-expression. */ int -agent_card_readkey (const char *id, unsigned char **r_buf) +agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf) { int rc; char line[ASSUAN_LINELENGTH]; @@ -575,7 +634,7 @@ agent_card_readkey (const char *id, unsigned char **r_buf) size_t len, buflen; *r_buf = NULL; - rc = start_scd (); + rc = start_scd (ctrl); if (rc) return rc; @@ -642,14 +701,14 @@ pass_data_thru (void *opaque, const void *buffer, size_t length) mechanism to pass everything verbatim to SCDAEMOPN. The PIN inquirey is handled inside gpg-agent. */ int -agent_card_scd (const char *cmdline, +agent_card_scd (ctrl_t ctrl, const char *cmdline, int (*getpin_cb)(void *, const char *, char*, size_t), void *getpin_cb_arg, void *assuan_context) { int rc; struct inq_needpin_s inqparm; - rc = start_scd (); + rc = start_scd (ctrl); if (rc) return rc; diff --git a/agent/command.c b/agent/command.c index 0406ea439..aec48e194 100644 --- a/agent/command.c +++ b/agent/command.c @@ -579,9 +579,10 @@ cmd_get_confirmation (ASSUAN_CONTEXT ctx, char *line) static int cmd_learn (ASSUAN_CONTEXT ctx, char *line) { + ctrl_t ctrl = assuan_get_pointer (ctx); int rc; - rc = agent_handle_learn (has_option (line, "--send")? ctx : NULL); + rc = agent_handle_learn (ctrl, has_option (line, "--send")? ctx : NULL); if (rc) log_error ("command learn failed: %s\n", gpg_strerror (rc)); return map_to_assuan_status (rc); @@ -771,6 +772,7 @@ start_command_handler (int listen_fd, int fd) else { rc = assuan_init_connected_socket_server (&ctx, fd); + ctrl.connection_fd = fd; } if (rc) { @@ -816,6 +818,8 @@ start_command_handler (int listen_fd, int fd) } } + /* Reset the SCD if needed. */ + agent_reset_scd (&ctrl); assuan_deinit_server (ctx); if (ctrl.display) diff --git a/agent/divert-scd.c b/agent/divert-scd.c index 283150ad3..455d23068 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -81,7 +81,7 @@ ask_for_card (CTRL ctrl, const unsigned char *shadow_info, char **r_kid) for (;;) { - rc = agent_card_serialno (&serialno); + rc = agent_card_serialno (ctrl, &serialno); if (!rc) { log_debug ("detected card with S/N %s\n", serialno); @@ -108,6 +108,13 @@ ask_for_card (CTRL ctrl, const unsigned char *shadow_info, char **r_kid) if (!rc) { + /* We better reset the SCD now. This is kludge requred + because the scdaemon is currently not always able to + detect the presence of a card. With a fully working + scdaemon this would not be required; i.e. the pkcs#15 + support does not require it becuase OpenSC correclty + detects a present card. */ + agent_reset_scd (ctrl); if (asprintf (&desc, "%s:%%0A%%0A" " \"%.*s\"", @@ -230,7 +237,7 @@ divert_pksign (CTRL ctrl, if (rc) return rc; - rc = agent_card_pksign (kid, getpin_cb, ctrl, + rc = agent_card_pksign (ctrl, kid, getpin_cb, ctrl, data, ndata, &sigval, &siglen); if (!rc) *r_sig = sigval; @@ -294,7 +301,7 @@ divert_pkdecrypt (CTRL ctrl, if (rc) return rc; - rc = agent_card_pkdecrypt (kid, getpin_cb, ctrl, + rc = agent_card_pkdecrypt (ctrl, kid, getpin_cb, ctrl, ciphertext, ciphertextlen, &plaintext, &plaintextlen); if (!rc) @@ -310,7 +317,7 @@ divert_pkdecrypt (CTRL ctrl, int divert_generic_cmd (CTRL ctrl, const char *cmdline, void *assuan_context) { - return agent_card_scd (cmdline, getpin_cb, ctrl, assuan_context); + return agent_card_scd (ctrl, cmdline, getpin_cb, ctrl, assuan_context); } diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 47420117e..22537aa3b 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -784,6 +784,8 @@ agent_exit (int rc) void agent_init_default_ctrl (struct server_control_s *ctrl) { + ctrl->connection_fd = -1; + /* Note we ignore malloc errors because we can't do much about it and the request will fail anyway shortly after this initialization. */ diff --git a/agent/learncard.c b/agent/learncard.c index a76f2652a..76e8986f8 100644 --- a/agent/learncard.c +++ b/agent/learncard.c @@ -255,13 +255,13 @@ make_shadow_info (const char *serialno, const char *idstring) } static int -send_cert_back (const char *id, void *assuan_context) +send_cert_back (ctrl_t ctrl, const char *id, void *assuan_context) { int rc; char *derbuf; size_t derbuflen; - rc = agent_card_readcert (id, &derbuf, &derbuflen); + rc = agent_card_readcert (ctrl, id, &derbuf, &derbuflen); if (rc) { log_error ("error reading certificate: %s\n", @@ -287,7 +287,7 @@ send_cert_back (const char *id, void *assuan_context) /* Perform the learn operation. If ASSUAN_CONTEXT is not NULL all new certificates are send via Assuan */ int -agent_handle_learn (void *assuan_context) +agent_handle_learn (ctrl_t ctrl, void *assuan_context) { int rc; struct kpinfo_cb_parm_s parm; @@ -313,12 +313,12 @@ agent_handle_learn (void *assuan_context) memset (&sparm, 0, sizeof sparm); /* Check whether a card is present and get the serial number */ - rc = agent_card_serialno (&serialno); + rc = agent_card_serialno (ctrl, &serialno); if (rc) goto leave; /* now gather all the available info */ - rc = agent_card_learn (kpinfo_cb, &parm, certinfo_cb, &cparm, + rc = agent_card_learn (ctrl, kpinfo_cb, &parm, certinfo_cb, &cparm, sinfo_cb, &sparm); if (!rc && (parm.error || cparm.error || sparm.error)) rc = parm.error? parm.error : cparm.error? cparm.error : sparm.error; @@ -354,7 +354,7 @@ agent_handle_learn (void *assuan_context) if (assuan_context) { - rc = send_cert_back (citem->id, assuan_context); + rc = send_cert_back (ctrl, citem->id, assuan_context); if (rc) goto leave; citem->done = 1; @@ -380,7 +380,7 @@ agent_handle_learn (void *assuan_context) continue; /* unknown - store it */ - rc = agent_card_readkey (item->id, &pubkey); + rc = agent_card_readkey (ctrl, item->id, &pubkey); if (rc) { log_debug ("agent_card_readkey failed: %s\n", gpg_strerror (rc)); @@ -430,7 +430,7 @@ agent_handle_learn (void *assuan_context) } if (!citem) { - rc = send_cert_back (item->id, assuan_context); + rc = send_cert_back (ctrl, item->id, assuan_context); if (rc) goto leave; } -- cgit From cbff0b05e54d7aa022c88e76dddd8d5106cc3536 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 13 Feb 2004 17:06:34 +0000 Subject: * command.c (cmd_setkeydesc): New. (register_commands): Add command SETKEYDESC. (cmd_pksign, cmd_pkdecrypt): Use the key description. (reset_notify): Reset the description. * findkey.c (unprotect): Add arg DESC_TEXT. (agent_key_from_file): Ditto. * pksign.c (agent_pksign): Ditto. * pkdecrypt.c (agent_pkdecrypt): Ditto. Made CIPHERTEXT an unsigned char*. --- NEWS | 7 +++- agent/ChangeLog | 10 +++++ agent/agent.h | 30 ++++++++------ agent/command.c | 114 ++++++++++++++++++++++++++++++++++++++++-------------- agent/findkey.c | 16 ++++---- agent/pkdecrypt.c | 6 ++- agent/pksign.c | 4 +- 7 files changed, 134 insertions(+), 53 deletions(-) (limited to 'agent/command.c') diff --git a/NEWS b/NEWS index 41edd66af..07216f555 100644 --- a/NEWS +++ b/NEWS @@ -5,8 +5,11 @@ Noteworthy changes in version 1.9.5 Cleaned up the build system to better comply with the coding standards. - * The --import command is now able to autodetect pkcs#12 files and - import secret and private keys from this file format. + * [gpgsm] The --import command is now able to autodetect pkcs#12 + files and import secret and private keys from this file format. + + * [gpgsm] The pinentry will no present a description of the key for + whom the passphrase is requests. Noteworthy changes in version 1.9.4 (2004-01-30) diff --git a/agent/ChangeLog b/agent/ChangeLog index b3c5d73d6..f064bc17c 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,5 +1,15 @@ 2004-02-13 Werner Koch + * command.c (cmd_setkeydesc): New. + (register_commands): Add command SETKEYDESC. + (cmd_pksign, cmd_pkdecrypt): Use the key description. + (reset_notify): Reset the description. + * findkey.c (unprotect): Add arg DESC_TEXT. + (agent_key_from_file): Ditto. + * pksign.c (agent_pksign): Ditto. + * pkdecrypt.c (agent_pkdecrypt): Ditto. Made CIPHERTEXT an + unsigned char*. + * protect-tool.c (main): New options --no-fail-on-exist, --homedir. (store_private_key): Use them here. diff --git a/agent/agent.h b/agent/agent.h index b70a4ae2d..a849e873f 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -132,18 +132,20 @@ void start_command_handler (int, int); /*-- findkey.c --*/ int agent_write_private_key (const unsigned char *grip, const void *buffer, size_t length, int force); -gpg_error_t agent_key_from_file (CTRL ctrl, const unsigned char *grip, +gpg_error_t agent_key_from_file (ctrl_t ctrl, + const char *desc_text, + const unsigned char *grip, unsigned char **shadow_info, int ignore_cache, gcry_sexp_t *result); int agent_key_available (const unsigned char *grip); /*-- query.c --*/ -int agent_askpin (CTRL ctrl, +int agent_askpin (ctrl_t ctrl, const char *desc_text, struct pin_entry_info_s *pininfo); -int agent_get_passphrase (CTRL ctrl, char **retpass, +int agent_get_passphrase (ctrl_t ctrl, char **retpass, const char *desc, const char *prompt, const char *errtext); -int agent_get_confirmation (CTRL ctrl, const char *desc, const char *ok, +int agent_get_confirmation (ctrl_t ctrl, const char *desc, const char *ok, const char *cancel); /*-- cache.c --*/ @@ -154,16 +156,18 @@ void agent_unlock_cache_entry (void **cache_id); /*-- pksign.c --*/ -int agent_pksign (CTRL ctrl, FILE *outfp, int ignore_cache); +int agent_pksign (ctrl_t ctrl, const char *desc_text, + FILE *outfp, int ignore_cache); /*-- pkdecrypt.c --*/ -int agent_pkdecrypt (CTRL ctrl, const char *ciphertext, size_t ciphertextlen, +int agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, + const unsigned char *ciphertext, size_t ciphertextlen, FILE *outfp); /*-- genkey.c --*/ -int agent_genkey (CTRL ctrl, +int agent_genkey (ctrl_t ctrl, const char *keyparam, size_t keyparmlen, FILE *outfp); -int agent_protect_and_store (CTRL ctrl, gcry_sexp_t s_skey); +int agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey); /*-- protect.c --*/ int agent_protect (const unsigned char *plainkey, const char *passphrase, @@ -181,18 +185,20 @@ int agent_get_shadow_info (const unsigned char *shadowkey, /*-- trustlist.c --*/ int agent_istrusted (const char *fpr); int agent_listtrusted (void *assuan_context); -int agent_marktrusted (CTRL ctrl, const char *name, const char *fpr, int flag); +int agent_marktrusted (ctrl_t ctrl, const char *name, + const char *fpr, int flag); /*-- divert-scd.c --*/ -int divert_pksign (CTRL ctrl, +int divert_pksign (ctrl_t ctrl, const unsigned char *digest, size_t digestlen, int algo, const unsigned char *shadow_info, unsigned char **r_sig); -int divert_pkdecrypt (CTRL ctrl, +int divert_pkdecrypt (ctrl_t ctrl, const unsigned char *cipher, const unsigned char *shadow_info, char **r_buf, size_t *r_len); -int divert_generic_cmd (CTRL ctrl, const char *cmdline, void *assuan_context); +int divert_generic_cmd (ctrl_t ctrl, + const char *cmdline, void *assuan_context); /*-- call-scd.c --*/ diff --git a/agent/command.c b/agent/command.c index aec48e194..2fa182f63 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1,5 +1,5 @@ /* command.c - gpg-agent command handler - * Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. + * Copyright (C) 2001, 2002, 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -50,6 +50,8 @@ struct server_local_s { ASSUAN_CONTEXT assuan_ctx; int message_fd; int use_cache_for_signing; + char *keydesc; /* Allocated description fro the next key + operation. */ }; @@ -59,11 +61,14 @@ struct server_local_s { static void reset_notify (ASSUAN_CONTEXT ctx) { - CTRL ctrl = assuan_get_pointer (ctx); + ctrl_t ctrl = assuan_get_pointer (ctx); memset (ctrl->keygrip, 0, 20); ctrl->have_keygrip = 0; ctrl->digest.valuelen = 0; + + xfree (ctrl->server_local->keydesc); + ctrl->server_local->keydesc = NULL; } @@ -78,6 +83,18 @@ has_option (const char *line, const char *name) return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n))); } +/* Replace all '+' by a blank. */ +static void +plus_to_blank (char *s) +{ + for (; *s; s++) + { + if (*s == '+') + *s = ' '; + } +} + + /* Parse a hex string. Return an Assuan error code or 0 on success and the length of the parsed string in LEN. */ static int @@ -179,7 +196,7 @@ cmd_listtrusted (ASSUAN_CONTEXT ctx, char *line) static int cmd_marktrusted (ASSUAN_CONTEXT ctx, char *line) { - CTRL ctrl = assuan_get_pointer (ctx); + ctrl_t ctrl = assuan_get_pointer (ctx); int rc, n, i; char *p; char fpr[41]; @@ -245,7 +262,7 @@ static int cmd_sigkey (ASSUAN_CONTEXT ctx, char *line) { int rc; - CTRL ctrl = assuan_get_pointer (ctx); + ctrl_t ctrl = assuan_get_pointer (ctx); rc = parse_keygrip (ctx, line, ctrl->keygrip); if (rc) @@ -255,6 +272,50 @@ cmd_sigkey (ASSUAN_CONTEXT ctx, char *line) } +/* SETKEYDESC plus_percent_escaped_string: + + Set a description to be used for the next PKSIGN or PKDECRYPT + operation if this operation requires the entry of a passphrase. If + this command is not used a default text will be used. Note, that + this description implictly selects the label used for the entry + box; if the string contains the string PIN (which in general will + not be translated), "PIN" is used, other wiese the translation of + 'passphrase" is used. The description string should not contain + blanks unless they are percent or '+' escaped. + + The descrition is only valid for the next PKSIGN or PKDECRYPT + operation. +*/ +static int +cmd_setkeydesc (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + char *desc, *p; + + for (p=line; *p == ' '; p++) + ; + desc = p; + p = strchr (desc, ' '); + if (p) + *p = 0; /* We ignore any garbage; we might late use it for other args. */ + + if (!desc || !*desc) + return set_error (Parameter_Error, "no description given"); + + /* Note, that we only need to replace the + characters and should + leave the other escaping in place because the escaped string is + send verbatim to the pinentry which does the unescaping (but not + the + replacing) */ + plus_to_blank (desc); + + xfree (ctrl->server_local->keydesc); + ctrl->server_local->keydesc = xtrystrdup (desc); + if (!ctrl->server_local->keydesc) + return map_to_assuan_status (gpg_error_from_errno (errno)); + return 0; +} + + /* SETHASH The client can use this command to tell the server about the data @@ -265,7 +326,7 @@ cmd_sethash (ASSUAN_CONTEXT ctx, char *line) int rc; size_t n; char *p; - CTRL ctrl = assuan_get_pointer (ctx); + ctrl_t ctrl = assuan_get_pointer (ctx); unsigned char *buf; char *endp; int algo; @@ -307,16 +368,19 @@ cmd_pksign (ASSUAN_CONTEXT ctx, char *line) { int rc; int ignore_cache = 0; - CTRL ctrl = assuan_get_pointer (ctx); + ctrl_t ctrl = assuan_get_pointer (ctx); if (opt.ignore_cache_for_signing) ignore_cache = 1; else if (!ctrl->server_local->use_cache_for_signing) ignore_cache = 1; - rc = agent_pksign (ctrl, assuan_get_data_fp (ctx), ignore_cache); + rc = agent_pksign (ctrl, ctrl->server_local->keydesc, + assuan_get_data_fp (ctx), ignore_cache); if (rc) log_error ("command pksign failed: %s\n", gpg_strerror (rc)); + xfree (ctrl->server_local->keydesc); + ctrl->server_local->keydesc = NULL; return map_to_assuan_status (rc); } @@ -328,7 +392,7 @@ static int cmd_pkdecrypt (ASSUAN_CONTEXT ctx, char *line) { int rc; - CTRL ctrl = assuan_get_pointer (ctx); + ctrl_t ctrl = assuan_get_pointer (ctx); unsigned char *value; size_t valuelen; @@ -338,10 +402,13 @@ cmd_pkdecrypt (ASSUAN_CONTEXT ctx, char *line) if (rc) return rc; - rc = agent_pkdecrypt (ctrl, value, valuelen, assuan_get_data_fp (ctx)); + rc = agent_pkdecrypt (ctrl, ctrl->server_local->keydesc, + value, valuelen, assuan_get_data_fp (ctx)); xfree (value); if (rc) log_error ("command pkdecrypt failed: %s\n", gpg_strerror (rc)); + xfree (ctrl->server_local->keydesc); + ctrl->server_local->keydesc = NULL; return map_to_assuan_status (rc); } @@ -363,7 +430,7 @@ cmd_pkdecrypt (ASSUAN_CONTEXT ctx, char *line) static int cmd_genkey (ASSUAN_CONTEXT ctx, char *line) { - CTRL ctrl = assuan_get_pointer (ctx); + ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char *value; size_t valuelen; @@ -381,16 +448,6 @@ cmd_genkey (ASSUAN_CONTEXT ctx, char *line) } -static void -plus_to_blank (char *s) -{ - for (; *s; s++) - { - if (*s == '+') - *s = ' '; - } -} - /* GET_PASSPHRASE [ ] This function is usually used to ask for a passphrase to be used @@ -405,7 +462,7 @@ plus_to_blank (char *s) static int cmd_get_passphrase (ASSUAN_CONTEXT ctx, char *line) { - CTRL ctrl = assuan_get_pointer (ctx); + ctrl_t ctrl = assuan_get_pointer (ctx); int rc; const char *pw; char *response; @@ -530,15 +587,15 @@ cmd_clear_passphrase (ASSUAN_CONTEXT ctx, char *line) command uses a syntax which helps clients to use the agent with minimum effort. The agent either returns with an error or with a OK. Note, that the length of DESCRIPTION is implicitly limited by - the maximum length of a command. DESCRIPTION should not conmtain - ant spaces, those must be encoded either percent escaped or simply + the maximum length of a command. DESCRIPTION should not contain + any spaces, those must be encoded either percent escaped or simply as '+'. */ static int cmd_get_confirmation (ASSUAN_CONTEXT ctx, char *line) { - CTRL ctrl = assuan_get_pointer (ctx); + ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *desc = NULL; char *p; @@ -596,7 +653,7 @@ cmd_learn (ASSUAN_CONTEXT ctx, char *line) static int cmd_passwd (ASSUAN_CONTEXT ctx, char *line) { - CTRL ctrl = assuan_get_pointer (ctx); + ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char grip[20]; gcry_sexp_t s_skey = NULL; @@ -607,7 +664,7 @@ cmd_passwd (ASSUAN_CONTEXT ctx, char *line) return rc; /* we can't jump to leave because this is already an Assuan error code. */ - rc = agent_key_from_file (ctrl, grip, &shadow_info, 1, &s_skey); + rc = agent_key_from_file (ctrl, NULL, grip, &shadow_info, 1, &s_skey); if (rc) ; else if (!s_skey) @@ -633,7 +690,7 @@ cmd_passwd (ASSUAN_CONTEXT ctx, char *line) static int cmd_scd (ASSUAN_CONTEXT ctx, char *line) { - CTRL ctrl = assuan_get_pointer (ctx); + ctrl_t ctrl = assuan_get_pointer (ctx); int rc; rc = divert_generic_cmd (ctrl, line, ctx); @@ -646,7 +703,7 @@ cmd_scd (ASSUAN_CONTEXT ctx, char *line) static int option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value) { - CTRL ctrl = assuan_get_pointer (ctx); + ctrl_t ctrl = assuan_get_pointer (ctx); if (!strcmp (key, "display")) { @@ -715,6 +772,7 @@ register_commands (ASSUAN_CONTEXT ctx) { "HAVEKEY", cmd_havekey }, { "SIGKEY", cmd_sigkey }, { "SETKEY", cmd_sigkey }, + { "SETKEYDESC", cmd_setkeydesc }, { "SETHASH", cmd_sethash }, { "PKSIGN", cmd_pksign }, { "PKDECRYPT", cmd_pkdecrypt }, diff --git a/agent/findkey.c b/agent/findkey.c index f145daef1..14ca38448 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -1,5 +1,5 @@ /* findkey.c - locate the secret key - * Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. + * Copyright (C) 2001, 2002, 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -138,9 +138,10 @@ try_unprotect_cb (struct pin_entry_info_s *pi) /* Unprotect the canconical encoded S-expression key in KEYBUF. GRIP should be the hex encoded keygrip of that key to be used with the - caching mechanism. */ + caching mechanism. DESC_TEXT may be set to override the default + description used for the pinentry. */ static int -unprotect (CTRL ctrl, +unprotect (CTRL ctrl, const char *desc_text, unsigned char **keybuf, const unsigned char *grip, int ignore_cache) { struct pin_entry_info_s *pi; @@ -184,7 +185,7 @@ unprotect (CTRL ctrl, arg.unprotected_key = NULL; pi->check_cb_arg = &arg; - rc = agent_askpin (ctrl, NULL, pi); + rc = agent_askpin (ctrl, desc_text, pi); if (!rc) { assert (arg.unprotected_key); @@ -203,9 +204,10 @@ unprotect (CTRL ctrl, diverted to a token; SHADOW_INFO will point then to an allocated S-Expression with the shadow_info part from the file. With IGNORE_CACHE passed as true the passphrase is not taken from the - cache.*/ + cache. DESC_TEXT may be set to present a custom description for the + pinentry. */ gpg_error_t -agent_key_from_file (CTRL ctrl, +agent_key_from_file (CTRL ctrl, const char *desc_text, const unsigned char *grip, unsigned char **shadow_info, int ignore_cache, gcry_sexp_t *result) { @@ -286,7 +288,7 @@ agent_key_from_file (CTRL ctrl, case PRIVATE_KEY_CLEAR: break; /* no unprotection needed */ case PRIVATE_KEY_PROTECTED: - rc = unprotect (ctrl, &buf, grip, ignore_cache); + rc = unprotect (ctrl, desc_text, &buf, grip, ignore_cache); if (rc) log_error ("failed to unprotect the secret key: %s\n", gpg_strerror (rc)); diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c index 72f81778e..eaa2b2254 100644 --- a/agent/pkdecrypt.c +++ b/agent/pkdecrypt.c @@ -35,7 +35,8 @@ Try to get the key from CTRL and write the decoded stuff back to OUTFP. */ int -agent_pkdecrypt (CTRL ctrl, const char *ciphertext, size_t ciphertextlen, +agent_pkdecrypt (CTRL ctrl, const char *desc_text, + const unsigned char *ciphertext, size_t ciphertextlen, FILE *outfp) { gcry_sexp_t s_skey = NULL, s_cipher = NULL, s_plain = NULL; @@ -64,7 +65,8 @@ agent_pkdecrypt (CTRL ctrl, const char *ciphertext, size_t ciphertextlen, log_printhex ("keygrip:", ctrl->keygrip, 20); log_printhex ("cipher: ", ciphertext, ciphertextlen); } - rc = agent_key_from_file (ctrl, ctrl->keygrip, &shadow_info, 0, &s_skey); + rc = agent_key_from_file (ctrl, desc_text, + ctrl->keygrip, &shadow_info, 0, &s_skey); if (rc) { log_error ("failed to read the secret key\n"); diff --git a/agent/pksign.c b/agent/pksign.c index 34381365e..8bec33c0b 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -70,7 +70,7 @@ do_encode_md (const byte * md, size_t mdlen, int algo, gcry_sexp_t * r_hash) /* SIGN whatever information we have accumulated in CTRL and write it back to OUTFP. */ int -agent_pksign (CTRL ctrl, FILE *outfp, int ignore_cache) +agent_pksign (CTRL ctrl, const char *desc_text, FILE *outfp, int ignore_cache) { gcry_sexp_t s_skey = NULL, s_hash = NULL, s_sig = NULL; unsigned char *shadow_info = NULL; @@ -81,7 +81,7 @@ agent_pksign (CTRL ctrl, FILE *outfp, int ignore_cache) if (!ctrl->have_keygrip) return gpg_error (GPG_ERR_NO_SECKEY); - rc = agent_key_from_file (ctrl, ctrl->keygrip, + rc = agent_key_from_file (ctrl, desc_text, ctrl->keygrip, &shadow_info, ignore_cache, &s_skey); if (rc) { -- cgit From a425334f4884a6cd5f95976cf1d0950305f40691 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Sat, 21 Feb 2004 13:05:22 +0000 Subject: * command.c (cmd_passwd): Take acount of a key description. * genkey.c (reenter_compare_cb): Do not set the error text. (agent_protect_and_store, agent_genkey): Force a re-enter after a non-matching passphrase. * query.c (agent_askpin): Add new arg INITIAL_ERRTEXT; changed all callers. --- agent/ChangeLog | 10 ++++++++++ agent/agent.h | 3 ++- agent/command.c | 5 ++++- agent/divert-scd.c | 2 +- agent/findkey.c | 2 +- agent/genkey.c | 32 ++++++++++++++++++++++++++------ agent/query.c | 17 +++++++++++++++-- 7 files changed, 59 insertions(+), 12 deletions(-) (limited to 'agent/command.c') diff --git a/agent/ChangeLog b/agent/ChangeLog index 241a60964..f05685b0d 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,13 @@ +2004-02-21 Werner Koch + + * command.c (cmd_passwd): Take acount of a key description. + + * genkey.c (reenter_compare_cb): Do not set the error text. + (agent_protect_and_store, agent_genkey): Force a re-enter after a + non-matching passphrase. + * query.c (agent_askpin): Add new arg INITIAL_ERRTEXT; changed + all callers. + 2004-02-19 Werner Koch * protect-tool.c: New options --have-cert and --prompt. diff --git a/agent/agent.h b/agent/agent.h index a849e873f..a4312e081 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -141,7 +141,8 @@ int agent_key_available (const unsigned char *grip); /*-- query.c --*/ int agent_askpin (ctrl_t ctrl, - const char *desc_text, struct pin_entry_info_s *pininfo); + const char *desc_text, const char *inital_errtext, + struct pin_entry_info_s *pininfo); int agent_get_passphrase (ctrl_t ctrl, char **retpass, const char *desc, const char *prompt, const char *errtext); diff --git a/agent/command.c b/agent/command.c index 2fa182f63..1d1ae9704 100644 --- a/agent/command.c +++ b/agent/command.c @@ -664,7 +664,8 @@ cmd_passwd (ASSUAN_CONTEXT ctx, char *line) return rc; /* we can't jump to leave because this is already an Assuan error code. */ - rc = agent_key_from_file (ctrl, NULL, grip, &shadow_info, 1, &s_skey); + rc = agent_key_from_file (ctrl, ctrl->server_local->keydesc, + grip, &shadow_info, 1, &s_skey); if (rc) ; else if (!s_skey) @@ -675,6 +676,8 @@ cmd_passwd (ASSUAN_CONTEXT ctx, char *line) else rc = agent_protect_and_store (ctrl, s_skey); + xfree (ctrl->server_local->keydesc); + ctrl->server_local->keydesc = NULL; gcry_sexp_release (s_skey); xfree (shadow_info); if (rc) diff --git a/agent/divert-scd.c b/agent/divert-scd.c index 9c512bdcf..72cf338fe 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -202,7 +202,7 @@ getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf) info? info:"", info? "')":"") < 0) desc = NULL; - rc = agent_askpin (ctrl, desc?desc:info, pi); + rc = agent_askpin (ctrl, desc?desc:info, NULL, pi); free (desc); if (!rc) { diff --git a/agent/findkey.c b/agent/findkey.c index 14ca38448..9866b54b9 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -185,7 +185,7 @@ unprotect (CTRL ctrl, const char *desc_text, arg.unprotected_key = NULL; pi->check_cb_arg = &arg; - rc = agent_askpin (ctrl, desc_text, pi); + rc = agent_askpin (ctrl, desc_text, NULL, pi); if (!rc) { assert (arg.unprotected_key); diff --git a/agent/genkey.c b/agent/genkey.c index 1417abb02..3c56ba33e 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -1,5 +1,5 @@ /* pksign.c - Generate a keypair - * Copyright (C) 2002, 2003 Free Software Foundation, Inc. + * Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -79,7 +79,6 @@ reenter_compare_cb (struct pin_entry_info_s *pi) if (!strcmp (pin1, pi->pin)) return 0; /* okay */ - pi->cb_errtext = _("does not match - try again"); return -1; } @@ -109,6 +108,7 @@ agent_genkey (CTRL ctrl, const char *keyparam, size_t keyparamlen, const char *text1 = _("Please enter the passphrase to%0A" "to protect your new key"); const char *text2 = _("Please re-enter this passphrase"); + const char *initial_errtext = NULL; pi = gcry_calloc_secure (2, sizeof (*pi) + 100); pi2 = pi + (sizeof *pi + 100); @@ -119,9 +119,19 @@ agent_genkey (CTRL ctrl, const char *keyparam, size_t keyparamlen, pi2->check_cb = reenter_compare_cb; pi2->check_cb_arg = pi->pin; - rc = agent_askpin (ctrl, text1, pi); + next_try: + rc = agent_askpin (ctrl, text1, initial_errtext, pi); + initial_errtext = NULL; if (!rc) - rc = agent_askpin (ctrl, text2, pi2); + { + rc = agent_askpin (ctrl, text2, NULL, pi2); + if (rc == -1) + { /* The re-entered one did not match and the user did not + hit cancel. */ + initial_errtext = _("does not match - try again"); + goto next_try; + } + } if (rc) return rc; if (!*pi->pin) @@ -212,6 +222,7 @@ agent_protect_and_store (CTRL ctrl, gcry_sexp_t s_skey) { const char *text1 = _("Please enter the new passphrase"); const char *text2 = _("Please re-enter this passphrase"); + const char *initial_errtext = NULL; pi = gcry_calloc_secure (2, sizeof (*pi) + 100); pi2 = pi + (sizeof *pi + 100); @@ -222,9 +233,18 @@ agent_protect_and_store (CTRL ctrl, gcry_sexp_t s_skey) pi2->check_cb = reenter_compare_cb; pi2->check_cb_arg = pi->pin; - rc = agent_askpin (ctrl, text1, pi); + next_try: + rc = agent_askpin (ctrl, text1, initial_errtext, pi); if (!rc) - rc = agent_askpin (ctrl, text2, pi2); + { + rc = agent_askpin (ctrl, text2, NULL, pi2); + if (rc == -1) + { /* The re-entered one did not match and the user did not + hit cancel. */ + initial_errtext = _("does not match - try again"); + goto next_try; + } + } if (rc) return rc; if (!*pi->pin) diff --git a/agent/query.c b/agent/query.c index 28873775a..145aaca00 100644 --- a/agent/query.c +++ b/agent/query.c @@ -250,7 +250,8 @@ all_digitsp( const char *s) numbers. */ int agent_askpin (CTRL ctrl, - const char *desc_text, struct pin_entry_info_s *pininfo) + const char *desc_text, const char *initial_errtext, + struct pin_entry_info_s *pininfo) { int rc; char line[ASSUAN_LINELENGTH]; @@ -289,6 +290,17 @@ agent_askpin (CTRL ctrl, if (rc) return unlock_pinentry (map_assuan_err (rc)); + + if (initial_errtext) + { + snprintf (line, DIM(line)-1, "SETERROR %s", initial_errtext); + line[DIM(line)-1] = 0; + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_pinentry (map_assuan_err (rc)); + } + for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++) { memset (&parm, 0, sizeof parm); @@ -301,7 +313,8 @@ agent_askpin (CTRL ctrl, snprintf (line, DIM(line)-1, "SETERROR %s (try %d of %d)", errtext, pininfo->failed_tries+1, pininfo->max_tries); line[DIM(line)-1] = 0; - rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (map_assuan_err (rc)); errtext = NULL; -- cgit From 1db08a412cc349bc38786de78ed460cf71f8467f Mon Sep 17 00:00:00 2001 From: Moritz Schulte Date: Sun, 26 Sep 2004 21:48:13 +0000 Subject: 2004-09-25 Moritz Schulte * agent.h: Declare: agent_pksign_do. (struct server_control_s): New member: raw_value. * pksign.c (do_encode_md): New argument: raw_value; support generation of raw (non-pkcs1) data objects; adjust callers. (agent_pksign_do): New function, based on code ripped out from agent_pksign. (agent_pksign): Use agent_pksign_do. * command.c (start_command_handler): Set ctrl.digest.raw_value. --- agent/ChangeLog | 13 ++++++ agent/agent.h | 3 ++ agent/command.c | 1 + agent/pksign.c | 136 +++++++++++++++++++++++++++++++++++++++----------------- 4 files changed, 113 insertions(+), 40 deletions(-) (limited to 'agent/command.c') diff --git a/agent/ChangeLog b/agent/ChangeLog index 470c70162..032308608 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,16 @@ +2004-09-25 Moritz Schulte + + * agent.h: Declare: agent_pksign_do. + (struct server_control_s): New member: raw_value. + + * pksign.c (do_encode_md): New argument: raw_value; support + generation of raw (non-pkcs1) data objects; adjust callers. + (agent_pksign_do): New function, based on code ripped + out from agent_pksign. + (agent_pksign): Use agent_pksign_do. + + * command.c (start_command_handler): Set ctrl.digest.raw_value. + 2004-09-09 Werner Koch * gpg-agent.c (check_for_running_agent): New. diff --git a/agent/agent.h b/agent/agent.h index 89fc4285e..8112c258a 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -95,6 +95,7 @@ struct server_control_s { int algo; unsigned char value[MAX_DIGEST_LEN]; int valuelen; + int raw_value: 1; } digest; char keygrip[20]; int have_keygrip; @@ -159,6 +160,8 @@ void agent_unlock_cache_entry (void **cache_id); /*-- pksign.c --*/ +int agent_pksign_do (CTRL ctrl, const char *desc_text, + gcry_sexp_t *signature_sexp, int ignore_cache); int agent_pksign (ctrl_t ctrl, const char *desc_text, FILE *outfp, int ignore_cache); diff --git a/agent/command.c b/agent/command.c index 1d1ae9704..02bbf3429 100644 --- a/agent/command.c +++ b/agent/command.c @@ -854,6 +854,7 @@ start_command_handler (int listen_fd, int fd) ctrl.server_local->assuan_ctx = ctx; ctrl.server_local->message_fd = -1; ctrl.server_local->use_cache_for_signing = 1; + ctrl.digest.raw_value = 0; if (DBG_ASSUAN) assuan_set_log_stream (ctx, log_get_stream ()); diff --git a/agent/pksign.c b/agent/pksign.c index acde66029..11e964837 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -32,41 +32,61 @@ static int -do_encode_md (const byte * md, size_t mdlen, int algo, gcry_sexp_t * r_hash) +do_encode_md (const byte * md, size_t mdlen, int algo, gcry_sexp_t * r_hash, + int raw_value) { gcry_sexp_t hash; - const char *s; - char tmp[16+1]; - int i, rc; + int rc; - s = gcry_md_algo_name (algo); - if (s && strlen (s) < 16) + if (! raw_value) + { + const char *s; + char tmp[16+1]; + int i; + + s = gcry_md_algo_name (algo); + if (s && strlen (s) < 16) + { + for (i=0; i < strlen (s); i++) + tmp[i] = tolower (s[i]); + tmp[i] = '\0'; + } + + rc = gcry_sexp_build (&hash, NULL, + "(data (flags pkcs1) (hash %s %b))", + tmp, mdlen, md); + } + else { - for (i=0; i < strlen (s); i++) - tmp[i] = tolower (s[i]); - tmp[i] = '\0'; + gcry_mpi_t mpi; + + rc = gcry_mpi_scan (&mpi, GCRYMPI_FMT_USG, md, mdlen, NULL); + if (! rc) + { + rc = gcry_sexp_build (&hash, NULL, + "(data (flags raw) (value %m))", + mpi); + gcry_mpi_release (mpi); + } + } - rc = gcry_sexp_build (&hash, NULL, - "(data (flags pkcs1) (hash %s %b))", - tmp, - mdlen, md); + *r_hash = hash; return rc; } -/* SIGN whatever information we have accumulated in CTRL and write it - back to OUTFP. */ +/* SIGN whatever information we have accumulated in CTRL and return + the signature S-Expression. */ int -agent_pksign (CTRL ctrl, const char *desc_text, FILE *outfp, int ignore_cache) +agent_pksign_do (CTRL ctrl, const char *desc_text, + gcry_sexp_t *signature_sexp, int ignore_cache) { - gcry_sexp_t s_skey = NULL, s_hash = NULL, s_sig = NULL; + gcry_sexp_t s_skey = NULL, s_sig = NULL; unsigned char *shadow_info = NULL; - int rc; - char *buf = NULL; - size_t len; + unsigned int rc = 0; /* FIXME: gpg-error? */ - if (!ctrl->have_keygrip) + if (! ctrl->have_keygrip) return gpg_error (GPG_ERR_NO_SECKEY); rc = agent_key_from_file (ctrl, desc_text, ctrl->keygrip, @@ -77,32 +97,47 @@ agent_pksign (CTRL ctrl, const char *desc_text, FILE *outfp, int ignore_cache) goto leave; } - if (!s_skey) - { /* divert operation to the smartcard */ - unsigned char *sigbuf; + if (! s_skey) + { + /* divert operation to the smartcard */ + + unsigned char *buf = NULL; + size_t len = 0; rc = divert_pksign (ctrl, ctrl->digest.value, ctrl->digest.valuelen, ctrl->digest.algo, - shadow_info, &sigbuf); + shadow_info, &buf); if (rc) { log_error ("smartcard signing failed: %s\n", gpg_strerror (rc)); goto leave; } - len = gcry_sexp_canon_len (sigbuf, 0, NULL, NULL); + len = gcry_sexp_canon_len (buf, 0, NULL, NULL); assert (len); - buf = sigbuf; + + rc = gcry_sexp_sscan (&s_sig, NULL, buf, len); + xfree (buf); + if (rc) + { + log_error ("failed to convert sigbuf returned by divert_pksign " + "into S-Exp: %s", gpg_strerror (rc)); + goto leave; + } } else - { /* no smartcard, but a private key */ + { + /* no smartcard, but a private key */ + + gcry_sexp_t s_hash = NULL; /* put the hash into a sexp */ rc = do_encode_md (ctrl->digest.value, ctrl->digest.valuelen, ctrl->digest.algo, - &s_hash); + &s_hash, + ctrl->digest.raw_value); if (rc) goto leave; @@ -114,6 +149,7 @@ agent_pksign (CTRL ctrl, const char *desc_text, FILE *outfp, int ignore_cache) /* sign */ rc = gcry_pk_sign (&s_sig, s_hash, s_skey); + gcry_sexp_release (s_hash); if (rc) { log_error ("signing failed: %s\n", gpg_strerror (rc)); @@ -125,26 +161,46 @@ agent_pksign (CTRL ctrl, const char *desc_text, FILE *outfp, int ignore_cache) log_debug ("result: "); gcry_sexp_dump (s_sig); } - - len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, NULL, 0); - assert (len); - buf = xmalloc (len); - len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, buf, len); - assert (len); } + leave: + + *signature_sexp = s_sig; + + gcry_sexp_release (s_skey); + xfree (shadow_info); + + return rc; +} + +/* SIGN whatever information we have accumulated in CTRL and write it + back to OUTFP. */ +int +agent_pksign (CTRL ctrl, const char *desc_text, FILE *outfp, int ignore_cache) +{ + gcry_sexp_t s_sig = NULL; + char *buf = NULL; + size_t len = 0; + int rc = 0; + + rc = agent_pksign_do (ctrl, desc_text, &s_sig, ignore_cache); + if (rc) + goto leave; + + len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, NULL, 0); + assert (len); + buf = xmalloc (len); + len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, buf, len); + assert (len); + /* FIXME: we must make sure that no buffering takes place or we are in full control of the buffer memory (easy to do) - should go into assuan. */ fwrite (buf, 1, len, outfp); leave: - gcry_sexp_release (s_skey); - gcry_sexp_release (s_hash); gcry_sexp_release (s_sig); xfree (buf); - xfree (shadow_info); + return rc; } - - -- cgit From 18fd4964f66ab297a5540f38f5dd6fb22b8e4572 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 20 Dec 2004 08:32:56 +0000 Subject: * call-scd.c (init_membuf, put_membuf, get_membuf): Removed. We now use the identical implementation from ../common/membuf.c. * pksign.c (agent_pksign): Changed arg OUTFP to OUTBUF and use membuf functions to return the value. * pkdecrypt.c (agent_pkdecrypt): Ditto. * genkey.c (agent_genkey): Ditto. * command.c (cmd_pksign, cmd_pkdecrypt, cmd_genkey): Replaced assuan_get_data_fp() by a the membuf scheme. (clear_outbuf, write_and_clear_outbuf): New. * membuf.c (put_membuf): Wipe out buffer after a failed realloc. --- agent/ChangeLog | 13 +++++++++ agent/agent.h | 7 ++--- agent/call-scd.c | 79 ++++--------------------------------------------------- agent/command.c | 64 +++++++++++++++++++++++++++++++++++++++++--- agent/genkey.c | 18 +++++-------- agent/pkdecrypt.c | 22 +++++++++------- agent/pksign.c | 8 +++--- common/ChangeLog | 4 +++ common/membuf.c | 5 ++++ 9 files changed, 112 insertions(+), 108 deletions(-) (limited to 'agent/command.c') diff --git a/agent/ChangeLog b/agent/ChangeLog index 1c56529b8..f2b82466c 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,16 @@ +2004-12-20 Werner Koch + + * call-scd.c (init_membuf, put_membuf, get_membuf): Removed. We + now use the identical implementation from ../common/membuf.c. + + * pksign.c (agent_pksign): Changed arg OUTFP to OUTBUF and use + membuf functions to return the value. + * pkdecrypt.c (agent_pkdecrypt): Ditto. + * genkey.c (agent_genkey): Ditto. + * command.c (cmd_pksign, cmd_pkdecrypt, cmd_genkey): Replaced + assuan_get_data_fp() by a the membuf scheme. + (clear_outbuf, write_and_clear_outbuf): New. + 2004-12-19 Werner Koch * query.c (initialize_module_query): New. diff --git a/agent/agent.h b/agent/agent.h index e6c8e2a35..241b37b05 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -33,6 +33,7 @@ #include #include "../common/util.h" #include "../common/errors.h" +#include "membuf.h" /* Convenience function to be used instead of returning the old GNUPG_Out_Of_Core. */ @@ -166,16 +167,16 @@ void agent_unlock_cache_entry (void **cache_id); int agent_pksign_do (CTRL ctrl, const char *desc_text, gcry_sexp_t *signature_sexp, int ignore_cache); int agent_pksign (ctrl_t ctrl, const char *desc_text, - FILE *outfp, int ignore_cache); + membuf_t *outbuf, int ignore_cache); /*-- pkdecrypt.c --*/ int agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, const unsigned char *ciphertext, size_t ciphertextlen, - FILE *outfp); + membuf_t *outbuf); /*-- genkey.c --*/ int agent_genkey (ctrl_t ctrl, - const char *keyparam, size_t keyparmlen, FILE *outfp); + const char *keyparam, size_t keyparmlen, membuf_t *outbuf); int agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey); /*-- protect.c --*/ diff --git a/agent/call-scd.c b/agent/call-scd.c index 90c625f8b..619a549f9 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -82,75 +82,6 @@ struct inq_needpin_s { void *getpin_cb_arg; }; -struct membuf { - size_t len; - size_t size; - char *buf; - int out_of_core; -}; - - - -/* A simple implementation of a dynamic buffer. Use init_membuf() to - create a buffer, put_membuf to append bytes and get_membuf to - release and return the buffer. Allocation errors are detected but - only returned at the final get_membuf(), this helps not to clutter - the code with out of core checks. */ - -static void -init_membuf (struct membuf *mb, int initiallen) -{ - mb->len = 0; - mb->size = initiallen; - mb->out_of_core = 0; - mb->buf = xtrymalloc (initiallen); - if (!mb->buf) - mb->out_of_core = 1; -} - -static void -put_membuf (struct membuf *mb, const void *buf, size_t len) -{ - if (mb->out_of_core) - return; - - if (mb->len + len >= mb->size) - { - char *p; - - mb->size += len + 1024; - p = xtryrealloc (mb->buf, mb->size); - if (!p) - { - mb->out_of_core = 1; - return; - } - mb->buf = p; - } - memcpy (mb->buf + mb->len, buf, len); - mb->len += len; -} - -static void * -get_membuf (struct membuf *mb, size_t *len) -{ - char *p; - - if (mb->out_of_core) - { - xfree (mb->buf); - mb->buf = NULL; - return NULL; - } - - p = mb->buf; - *len = mb->len; - mb->buf = NULL; - mb->out_of_core = 1; /* don't allow a reuse */ - return p; -} - - /* This function must be called once to initialize this module. This @@ -468,7 +399,7 @@ agent_card_serialno (ctrl_t ctrl, char **r_serialno) static AssuanError membuf_data_cb (void *opaque, const void *buffer, size_t length) { - struct membuf *data = opaque; + membuf_t *data = opaque; if (buffer) put_membuf (data, buffer, length); @@ -519,7 +450,7 @@ agent_card_pksign (ctrl_t ctrl, { int rc, i; char *p, line[ASSUAN_LINELENGTH]; - struct membuf data; + membuf_t data; struct inq_needpin_s inqparm; size_t len; unsigned char *sigbuf; @@ -591,7 +522,7 @@ agent_card_pkdecrypt (ctrl_t ctrl, { int rc, i; char *p, line[ASSUAN_LINELENGTH]; - struct membuf data; + membuf_t data; struct inq_needpin_s inqparm; size_t len; @@ -643,7 +574,7 @@ agent_card_readcert (ctrl_t ctrl, { int rc; char line[ASSUAN_LINELENGTH]; - struct membuf data; + membuf_t data; size_t len; *r_buf = NULL; @@ -679,7 +610,7 @@ agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf) { int rc; char line[ASSUAN_LINELENGTH]; - struct membuf data; + membuf_t data; size_t len, buflen; *r_buf = NULL; diff --git a/agent/command.c b/agent/command.c index 02bbf3429..9fc08e211 100644 --- a/agent/command.c +++ b/agent/command.c @@ -50,7 +50,7 @@ struct server_local_s { ASSUAN_CONTEXT assuan_ctx; int message_fd; int use_cache_for_signing; - char *keydesc; /* Allocated description fro the next key + char *keydesc; /* Allocated description for the next key operation. */ }; @@ -58,6 +58,41 @@ struct server_local_s { +/* Release the memory buffer MB but first wipe out the used memory. */ +static void +clear_outbuf (membuf_t *mb) +{ + void *p; + size_t n; + + p = get_membuf (mb, &n); + if (p) + { + memset (p, 0, n); + xfree (p); + } +} + + +/* Write the content of memory buffer MB as assuan data to CTX and + wipe the buffer out afterwards. */ +static gpg_error_t +write_and_clear_outbuf (assuan_context_t ctx, membuf_t *mb) +{ + assuan_error_t ae; + void *p; + size_t n; + + p = get_membuf (mb, &n); + if (!p) + return gpg_error (GPG_ERR_ENOMEM); + ae = assuan_send_data (ctx, p, n); + memset (p, 0, n); + xfree (p); + return map_assuan_err (ae); +} + + static void reset_notify (ASSUAN_CONTEXT ctx) { @@ -369,14 +404,21 @@ cmd_pksign (ASSUAN_CONTEXT ctx, char *line) int rc; int ignore_cache = 0; ctrl_t ctrl = assuan_get_pointer (ctx); + membuf_t outbuf; if (opt.ignore_cache_for_signing) ignore_cache = 1; else if (!ctrl->server_local->use_cache_for_signing) ignore_cache = 1; + init_membuf (&outbuf, 512); + rc = agent_pksign (ctrl, ctrl->server_local->keydesc, - assuan_get_data_fp (ctx), ignore_cache); + &outbuf, ignore_cache); + if (rc) + clear_outbuf (&outbuf); + else + rc = write_and_clear_outbuf (ctx, &outbuf); if (rc) log_error ("command pksign failed: %s\n", gpg_strerror (rc)); xfree (ctrl->server_local->keydesc); @@ -395,6 +437,7 @@ cmd_pkdecrypt (ASSUAN_CONTEXT ctx, char *line) ctrl_t ctrl = assuan_get_pointer (ctx); unsigned char *value; size_t valuelen; + membuf_t outbuf; /* First inquire the data to decrypt */ rc = assuan_inquire (ctx, "CIPHERTEXT", @@ -402,9 +445,15 @@ cmd_pkdecrypt (ASSUAN_CONTEXT ctx, char *line) if (rc) return rc; + init_membuf (&outbuf, 512); + rc = agent_pkdecrypt (ctrl, ctrl->server_local->keydesc, - value, valuelen, assuan_get_data_fp (ctx)); + value, valuelen, &outbuf); xfree (value); + if (rc) + clear_outbuf (&outbuf); + else + rc = write_and_clear_outbuf (ctx, &outbuf); if (rc) log_error ("command pkdecrypt failed: %s\n", gpg_strerror (rc)); xfree (ctrl->server_local->keydesc); @@ -434,14 +483,21 @@ cmd_genkey (ASSUAN_CONTEXT ctx, char *line) int rc; unsigned char *value; size_t valuelen; + membuf_t outbuf; /* First inquire the parameters */ rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM); if (rc) return rc; - rc = agent_genkey (ctrl, value, valuelen, assuan_get_data_fp (ctx)); + init_membuf (&outbuf, 512); + + rc = agent_genkey (ctrl, value, valuelen, &outbuf); xfree (value); + if (rc) + clear_outbuf (&outbuf); + else + rc = write_and_clear_outbuf (ctx, &outbuf); if (rc) log_error ("command genkey failed: %s\n", gpg_strerror (rc)); return map_to_assuan_status (rc); diff --git a/agent/genkey.c b/agent/genkey.c index 3c56ba33e..17d85f77c 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -88,7 +88,7 @@ reenter_compare_cb (struct pin_entry_info_s *pi) KEYPARAM */ int agent_genkey (CTRL ctrl, const char *keyparam, size_t keyparamlen, - FILE *outfp) + membuf_t *outbuf) { gcry_sexp_t s_keyparam, s_key, s_private, s_public; struct pin_entry_info_s *pi, *pi2; @@ -171,7 +171,8 @@ agent_genkey (CTRL ctrl, const char *keyparam, size_t keyparamlen, gcry_sexp_release (s_key); s_key = NULL; /* store the secret key */ - log_debug ("storing private key\n"); + if (DBG_CRYPTO) + log_debug ("storing private key\n"); rc = store_key (s_private, pi? pi->pin:NULL, 0); xfree (pi); pi = NULL; gcry_sexp_release (s_private); @@ -182,7 +183,8 @@ agent_genkey (CTRL ctrl, const char *keyparam, size_t keyparamlen, } /* return the public key */ - log_debug ("returning public key\n"); + if (DBG_CRYPTO) + log_debug ("returning public key\n"); len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, NULL, 0); assert (len); buf = xtrymalloc (len); @@ -195,15 +197,7 @@ agent_genkey (CTRL ctrl, const char *keyparam, size_t keyparamlen, } len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, buf, len); assert (len); - if (fwrite (buf, len, 1, outfp) != 1) - { - gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); - log_error ("error writing public key: %s\n", strerror (errno)); - gcry_sexp_release (s_private); - gcry_sexp_release (s_public); - xfree (buf); - return tmperr; - } + put_membuf (outbuf, buf, len); gcry_sexp_release (s_public); xfree (buf); diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c index eaa2b2254..7a93e58f8 100644 --- a/agent/pkdecrypt.c +++ b/agent/pkdecrypt.c @@ -37,7 +37,7 @@ int agent_pkdecrypt (CTRL ctrl, const char *desc_text, const unsigned char *ciphertext, size_t ciphertextlen, - FILE *outfp) + membuf_t *outbuf) { gcry_sexp_t s_skey = NULL, s_cipher = NULL, s_plain = NULL; unsigned char *shadow_info = NULL; @@ -88,11 +88,16 @@ agent_pkdecrypt (CTRL ctrl, const char *desc_text, log_error ("smartcard decryption failed: %s\n", gpg_strerror (rc)); goto leave; } - /* FIXME: don't use buffering and change the protocol to return - a complete S-expression and not just a part. */ - fprintf (outfp, "%u:", (unsigned int)len); - fwrite (buf, 1, len, outfp); - putc (0, outfp); + /* FIXME: Change the protocol to return a complete S-expression + and not just a part. */ + { + char tmpbuf[50]; + + sprintf (tmpbuf, "%u:", (unsigned int)len); + put_membuf (outbuf, tmpbuf, strlen (tmpbuf)); + put_membuf (outbuf, buf, len); + put_membuf (outbuf, "", 1); + } } else { /* No smartcard, but a private key */ @@ -119,10 +124,7 @@ agent_pkdecrypt (CTRL ctrl, const char *desc_text, buf = xmalloc (len); len = gcry_sexp_sprint (s_plain, GCRYSEXP_FMT_CANON, buf, len); assert (len); - /* FIXME: we must make sure that no buffering takes place or we are - in full control of the buffer memory (easy to do) - should go - into assuan. */ - fwrite (buf, 1, len, outfp); + put_membuf (outbuf, buf, len); } diff --git a/agent/pksign.c b/agent/pksign.c index 11e964837..3337e188c 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -176,7 +176,8 @@ agent_pksign_do (CTRL ctrl, const char *desc_text, /* SIGN whatever information we have accumulated in CTRL and write it back to OUTFP. */ int -agent_pksign (CTRL ctrl, const char *desc_text, FILE *outfp, int ignore_cache) +agent_pksign (CTRL ctrl, const char *desc_text, + membuf_t *outbuf, int ignore_cache) { gcry_sexp_t s_sig = NULL; char *buf = NULL; @@ -193,10 +194,7 @@ agent_pksign (CTRL ctrl, const char *desc_text, FILE *outfp, int ignore_cache) len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, buf, len); assert (len); - /* FIXME: we must make sure that no buffering takes place or we are - in full control of the buffer memory (easy to do) - should go - into assuan. */ - fwrite (buf, 1, len, outfp); + put_membuf (outbuf, buf, len); leave: gcry_sexp_release (s_sig); diff --git a/common/ChangeLog b/common/ChangeLog index 336b3928a..eeba09341 100644 --- a/common/ChangeLog +++ b/common/ChangeLog @@ -1,3 +1,7 @@ +2004-12-20 Werner Koch + + * membuf.c (put_membuf): Wipe out buffer after a failed realloc. + 2004-12-19 Werner Koch * maperror.c (map_assuan_err_with_source): Oops, args were swapped. diff --git a/common/membuf.c b/common/membuf.c index 69e4ab908..75f6bdb2a 100644 --- a/common/membuf.c +++ b/common/membuf.c @@ -60,6 +60,11 @@ put_membuf (membuf_t *mb, const void *buf, size_t len) if (!p) { mb->out_of_core = errno; + /* Wipe out what we already accumulated. This is required + in case we are storing sensitive data here. The membuf + API does not provide another way to cleanup after an + error. */ + memset (mb->buf, 0, mb->len); return; } mb->buf = p; -- cgit From 01f3f2515834876e2131d077e34c3cae4f9a2dc0 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 21 Dec 2004 19:05:15 +0000 Subject: * preset-passphrase.c (preset_passphrase): Handle --passphrase. * Makefile.am (gpg_preset_passphrase_LDADD): Reorder libs so that pwquery may use stuff from jnlib. Conditionally add -lwsock2 (gpg_protect_tool_LDADD): Ditto. * preset-passphrase.c (main): Use default_homedir(). (main) [W32]: Initialize sockets. * simple-pwquery.c (agent_open) [W32]: Implement for W32. (readline) [W32]: Use recv instead of read. (writen) [W32]: Use send instead of write. (my_stpcpy): Define a stpcpy replacement so that this file continues to be self-contained. (agent_send_all_options) [W32]: Don't call ttyname. * gnupg-badge-openpgp.eps, gnupg-badge-openpgp.jpg: New * gnupg.texi: Add a logo. * sysnotes.texi: New. * gpgsm.c (main): Use default_homedir(). (main) [W32]: Default to disabled CRL checks. * gpgconf-comp.c (get_config_pathname) [DOSISH]: Detect absolute pathnames with a drive letter. --- agent/ChangeLog | 29 +- agent/Makefile.am | 18 +- agent/agent.h | 1 + agent/cache.c | 9 +- agent/command.c | 61 +- agent/gpg-agent.c | 5 + agent/preset-passphrase.c | 293 ++ common/ChangeLog | 14 + common/simple-pwquery.c | 124 +- common/simple-pwquery.h | 3 + doc/ChangeLog | 6 + doc/Makefile.am | 7 +- doc/gnupg-badge-openpgp.eps | 7798 +++++++++++++++++++++++++++++++++++++++++++ doc/gnupg-badge-openpgp.jpg | Bin 0 -> 63450 bytes doc/gnupg.texi | 10 + doc/gpg-agent.texi | 2 +- doc/gpgsm.texi | 2 +- doc/scdaemon.texi | 2 +- doc/sysnotes.texi | 107 + doc/tools.texi | 504 ++- jnlib/w32-afunix.c | 7 +- sm/ChangeLog | 1 + sm/gpgsm.c | 3 + tools/ChangeLog | 5 + tools/gpgconf-comp.c | 6 + 25 files changed, 8986 insertions(+), 31 deletions(-) create mode 100644 agent/preset-passphrase.c create mode 100644 doc/gnupg-badge-openpgp.eps create mode 100644 doc/gnupg-badge-openpgp.jpg create mode 100644 doc/sysnotes.texi (limited to 'agent/command.c') diff --git a/agent/ChangeLog b/agent/ChangeLog index cf3569264..d835c8e60 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,8 +1,33 @@ +2004-12-21 Werner Koch + + * preset-passphrase.c (preset_passphrase): Handle --passphrase. + + * Makefile.am (gpg_preset_passphrase_LDADD): Reorder libs so that + pwquery may use stuff from jnlib. Conditionally add -lwsock2 + (gpg_protect_tool_LDADD): Ditto. + + * preset-passphrase.c (main): Use default_homedir(). + (main) [W32]: Initialize sockets. + +2004-12-21 Marcus Brinkmann + + * Makefile.am (libexec_PROGRAMS): Add gpg-preset-passphrase. + (gpg_preset_passphrase_SOURCES, gpg_preset_passphrase_LDADD): New + targets. + * agent.h (opt): New member allow_cache_passphrase. + * cache.c (housekeeping): Check if R->ttl is not negative. + (agent_put_cache): Allow ttl to be negative. + * command.c (parse_hexstring): Allow something to follow the + hexstring. + (cmd_cache_passphrase): New function. + (register_commands): Add it. + * gpg-agent.c: Handle --allow-preset-passphrase. + * preset-passphrase.c: New file. + 2004-12-21 Werner Koch * gpg-agent.c (main): Use default_homedir(). - * protect-tool.c (main): Ditto. - + * protect-tool.c (main): Ditto. 2004-12-20 Werner Koch diff --git a/agent/Makefile.am b/agent/Makefile.am index 6f3ea70d2..4cedbe74e 100644 --- a/agent/Makefile.am +++ b/agent/Makefile.am @@ -19,7 +19,7 @@ ## Process this file with automake to produce Makefile.in bin_PROGRAMS = gpg-agent -libexec_PROGRAMS = gpg-protect-tool +libexec_PROGRAMS = gpg-protect-tool gpg-preset-passphrase AM_CPPFLAGS = -I$(top_srcdir)/common -I$(top_srcdir)/intl @@ -53,8 +53,20 @@ gpg_protect_tool_SOURCES = \ protect.c \ minip12.c minip12.h -gpg_protect_tool_LDADD = ../jnlib/libjnlib.a \ - ../common/libcommon.a ../common/libsimple-pwquery.a \ +gpg_protect_tool_LDADD = ../common/libsimple-pwquery.a \ + ../jnlib/libjnlib.a ../common/libcommon.a \ $(LIBGCRYPT_LIBS) -lgpg-error @LIBINTL@ +if HAVE_W32_SYSTEM +gpg_protect_tool_LDADD += -lwsock32 +endif +gpg_preset_passphrase_SOURCES = \ + preset-passphrase.c + +gpg_preset_passphrase_LDADD = ../common/libsimple-pwquery.a \ + ../jnlib/libjnlib.a ../common/libcommon.a \ + $(LIBGCRYPT_LIBS) -lgpg-error @LIBINTL@ +if HAVE_W32_SYSTEM +gpg_preset_passphrase_LDADD += -lwsock32 +endif diff --git a/agent/agent.h b/agent/agent.h index 241b37b05..7d6bf9f47 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -63,6 +63,7 @@ struct { int ignore_cache_for_signing; int allow_mark_trusted; + int allow_preset_passphrase; int keep_tty; /* don't switch the TTY (for pinentry) on request */ int keep_display; /* don't switch the DISPLAY (for pinentry) on request */ } opt; diff --git a/agent/cache.c b/agent/cache.c index 8017b1414..b6762edd0 100644 --- a/agent/cache.c +++ b/agent/cache.c @@ -39,7 +39,7 @@ struct cache_item_s { ITEM next; time_t created; time_t accessed; - int ttl; /* max. lifetime given in seonds */ + int ttl; /* max. lifetime given in seonds, -1 one means infinite */ int lockcount; struct secret_data_s *pw; char key[1]; @@ -88,7 +88,8 @@ housekeeping (void) /* first expire the actual data */ for (r=thecache; r; r = r->next) { - if (!r->lockcount && r->pw && r->accessed + r->ttl < current) + if (!r->lockcount && r->pw + && r->ttl >= 0 && r->accessed + r->ttl < current) { if (DBG_CACHE) log_debug (" expired `%s' (%ds after last access)\n", @@ -118,7 +119,7 @@ housekeeping (void) Expire old and unused entries after 30 minutes */ for (rprev=NULL, r=thecache; r; ) { - if (!r->pw && r->accessed + 60*30 < current) + if (!r->pw && r->ttl >= 0 && r->accessed + 60*30 < current) { if (r->lockcount) { @@ -194,7 +195,7 @@ agent_put_cache (const char *key, const char *data, int ttl) log_debug ("agent_put_cache `%s'\n", key); housekeeping (); - if (ttl < 1) + if (ttl == 1) ttl = opt.def_cache_ttl; if (!ttl) return 0; diff --git a/agent/command.c b/agent/command.c index 9fc08e211..dc8a4a158 100644 --- a/agent/command.c +++ b/agent/command.c @@ -141,7 +141,7 @@ parse_hexstring (ASSUAN_CONTEXT ctx, const char *string, size_t *len) /* parse the hash value */ for (p=string, n=0; hexdigitp (p); p++, n++) ; - if (*p) + if (*p != ' ' && *p != '\t' && *p) return set_error (Parameter_Error, "invalid hexstring"); if ((n&1)) return set_error (Parameter_Error, "odd number of digits"); @@ -741,6 +741,64 @@ cmd_passwd (ASSUAN_CONTEXT ctx, char *line) return map_to_assuan_status (rc); } +/* PRESET_PASSPHRASE + + Set the cached passphrase/PIN for the key identified by the keygrip + to passwd for the given time, where -1 means infinite and 0 means + the default (currently only a timeout of -1 is allowed, which means + to never expire it). If passwd is not provided, ask for it via the + pinentry module. */ +static int +cmd_preset_passphrase (ASSUAN_CONTEXT ctx, char *line) +{ + int rc; + unsigned char grip[20]; + char *grip_clear = NULL; + char *passphrase = NULL; + int ttl; + + if (!opt.allow_preset_passphrase) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + + rc = parse_keygrip (ctx, line, grip); + if (rc) + return rc; + + /* FIXME: parse_keygrip should return a tail pointer. */ + grip_clear = line; + while (*line && (*line != ' ' && *line != '\t')) + line++; + if (!*line) + return map_to_assuan_status (gpg_error (GPG_ERR_MISSING_VALUE)); + *line = '\0'; + line++; + while (*line && (*line == ' ' || *line == '\t')) + line++; + + /* Currently, only infinite timeouts are allowed. */ + ttl = -1; + if (line[0] != '-' || line[1] != '1') + return map_to_assuan_status (gpg_error (GPG_ERR_NOT_IMPLEMENTED)); + line++; + line++; + while (!(*line != ' ' && *line != '\t')) + line++; + + /* If there is a passphrase, use it. Currently, a passphrase is + required. */ + if (*line) + passphrase = line; + else + return map_to_assuan_status (gpg_error (GPG_ERR_NOT_IMPLEMENTED)); + + rc = agent_put_cache (grip_clear, passphrase, ttl); + + if (rc) + log_error ("command preset_passwd failed: %s\n", gpg_strerror (rc)); + + return map_to_assuan_status (rc); +} + /* SCD @@ -837,6 +895,7 @@ register_commands (ASSUAN_CONTEXT ctx) { "PKDECRYPT", cmd_pkdecrypt }, { "GENKEY", cmd_genkey }, { "GET_PASSPHRASE", cmd_get_passphrase }, + { "PRESET_PASSPHRASE", cmd_preset_passphrase }, { "CLEAR_PASSPHRASE", cmd_clear_passphrase }, { "GET_CONFIRMATION", cmd_get_confirmation }, { "LISTTRUSTED", cmd_listtrusted }, diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 2c3d834a5..e76623f75 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -89,6 +89,7 @@ enum cmd_and_opt_values oIgnoreCacheForSigning, oAllowMarkTrusted, + oAllowPresetPassphrase, oKeepTTY, oKeepDISPLAY }; @@ -141,6 +142,8 @@ static ARGPARSE_OPTS opts[] = { N_("do not use the PIN cache when signing")}, { oAllowMarkTrusted, "allow-mark-trusted", 0, N_("allow clients to mark keys as \"trusted\"")}, + { oAllowPresetPassphrase, "allow-preset-passphrase", 0, + N_("allow presetting passphrase")}, {0} }; @@ -392,6 +395,8 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) case oAllowMarkTrusted: opt.allow_mark_trusted = 1; break; + case oAllowPresetPassphrase: opt.allow_preset_passphrase = 1; break; + default: return 0; /* not handled */ } diff --git a/agent/preset-passphrase.c b/agent/preset-passphrase.c new file mode 100644 index 000000000..6a9f07a3e --- /dev/null +++ b/agent/preset-passphrase.c @@ -0,0 +1,293 @@ +/* preset-passphrase.c - A tool to preset a passphrase. + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LOCALE_H +#include +#endif +#ifdef HAVE_LANGINFO_CODESET +#include +#endif +#ifdef HAVE_DOSISH_SYSTEM +#include /* for setmode() */ +#endif +#ifdef HAVE_W32_SYSTEM +#include /* To initialize the sockets. fixme */ +#endif + +#define JNLIB_NEED_LOG_LOGV +#include "agent.h" +#include "minip12.h" +#include "simple-pwquery.h" +#include "i18n.h" +#include "sysutils.h" + + +enum cmd_and_opt_values +{ aNull = 0, + oVerbose = 'v', + oPassphrase = 'P', + + oPreset = 'c', + oForget = 'f', + + oNoVerbose = 500, + + oHomedir, + +aTest }; + + +static const char *opt_homedir; +static const char *opt_passphrase; + +static ARGPARSE_OPTS opts[] = { + + { 301, NULL, 0, N_("@Options:\n ") }, + + { oVerbose, "verbose", 0, "verbose" }, + { oPassphrase, "passphrase", 2, "|STRING|use passphrase STRING" }, + { oPreset, "preset", 256, "preset passphrase"}, + { oForget, "forget", 256, "forget passphrase"}, + + { oHomedir, "homedir", 2, "@" }, + {0} +}; + + +static const char * +my_strusage (int level) +{ + const char *p; + switch (level) + { + case 11: p = "gpg-preset-passphrase (GnuPG)"; + break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n"); + break; + case 1: + case 40: + p = _("Usage: gpg-preset-passphrase [options] KEYGRIP (-h for help)\n"); + break; + case 41: + p = _("Syntax: gpg-preset-passphrase [options] KEYGRIP\n" + "Password cache maintenance\n"); + break; + + default: p = NULL; + } + return p; +} + + + +static void +i18n_init (void) +{ +#ifdef USE_SIMPLE_GETTEXT + set_gettext_file( PACKAGE_GT ); +#else +#ifdef ENABLE_NLS + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE_GT, LOCALEDIR); + textdomain (PACKAGE_GT); +#endif +#endif +} + + +static gpg_error_t +map_spwq_error (int err) +{ + switch (err) + { + case 0: + return 0; + case SPWQ_OUT_OF_CORE: + return gpg_error_from_errno (ENOMEM); + case SPWQ_IO_ERROR: + return gpg_error_from_errno (EIO); + case SPWQ_PROTOCOL_ERROR: + return gpg_error (GPG_ERR_PROTOCOL_VIOLATION); + case SPWQ_ERR_RESPONSE: + return gpg_error (GPG_ERR_INV_RESPONSE); + case SPWQ_NO_AGENT: + return gpg_error (GPG_ERR_NO_AGENT); + case SPWQ_SYS_ERROR: + return gpg_error_from_errno (errno); + case SPWQ_GENERAL_ERROR: + default: + return gpg_error (GPG_ERR_GENERAL); + } +} + + +static void +preset_passphrase (const char *keygrip) +{ + int rc; + char *line; + /* FIXME: Use secure memory. */ + char passphrase[500]; + + if (!opt_passphrase) + { + rc = read (0, passphrase, sizeof (passphrase) - 1); + if (rc < 0) + { + log_error ("reading passphrase failed: %s\n", + gpg_strerror (gpg_error_from_errno (errno))); + return; + } + passphrase[rc] = '\0'; + line = strchr (passphrase, '\n'); + if (line) + { + line--; + if (line > passphrase && line[-1] == '\r') + line--; + *line = '\0'; + } + + /* FIXME: How to handle empty passwords? */ + } + + rc = asprintf (&line, "PRESET_PASSPHRASE %s -1 %s\n", keygrip, + opt_passphrase? opt_passphrase : passphrase); + if (rc < 0) + { + log_error ("caching passphrase failed: %s\n", + gpg_strerror (gpg_error_from_errno (errno))); + return; + } + if (!opt_passphrase) + wipememory (passphrase, sizeof (passphrase)); + + rc = map_spwq_error (simple_query (line)); + if (rc) + { + log_error ("caching passphrase failed: %s\n", gpg_strerror (rc)); + return; + } + + wipememory (line, strlen (line)); + free (line); +} + + +static void +forget_passphrase (const char *keygrip) +{ + int rc; + char *line; + + rc = asprintf (&line, "CLEAR_PASSPHRASE %s\n", keygrip); + if (rc < 0) + { + log_error ("clearing passphrase failed: %s\n", + gpg_strerror (gpg_error_from_errno (errno))); + return; + } + free (line); +} + + +int +main (int argc, char **argv) +{ + ARGPARSE_ARGS pargs; + int cmd = 0; + const char *keygrip = NULL; + + set_strusage (my_strusage); + log_set_prefix ("gpg-preset-passphrase", 1); + + /* Try to auto set the character set. */ + set_native_charset (NULL); + +#ifdef HAVE_W32_SYSTEM + /* Fixme: Need to initialize the Windows sockets: This should be + moved to another place and we should make sure that it won't get + doen twice, like when Pth is used too. */ + { + WSADATA wsadat; + WSAStartup (0x202, &wsadat); + } +#endif + + i18n_init (); + + opt_homedir = default_homedir (); + + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1; /* (do not remove the args) */ + while (arg_parse (&pargs, opts) ) + { + switch (pargs.r_opt) + { + case oVerbose: opt.verbose++; break; + case oHomedir: opt_homedir = pargs.r.ret_str; break; + + case oPreset: cmd = oPreset; break; + case oForget: cmd = oForget; break; + case oPassphrase: opt_passphrase = pargs.r.ret_str; break; + + default : pargs.err = 2; break; + } + } + if (log_get_errorcount(0)) + exit(2); + + if (argc == 1) + keygrip = *argv; + else + usage (1); + + if (cmd == oPreset) + preset_passphrase (keygrip); + else if (cmd == oForget) + forget_passphrase (keygrip); + else + log_error ("one of the options --preset or --forget must be given\n"); + + agent_exit (0); + return 8; /*NOTREACHED*/ +} + + +void +agent_exit (int rc) +{ + rc = rc? rc : log_get_errorcount(0)? 2 : 0; + exit (rc); +} diff --git a/common/ChangeLog b/common/ChangeLog index 6a381b300..3e060258a 100644 --- a/common/ChangeLog +++ b/common/ChangeLog @@ -1,3 +1,17 @@ +2004-12-21 Werner Koch + + * simple-pwquery.c (agent_open) [W32]: Implement for W32. + (readline) [W32]: Use recv instead of read. + (writen) [W32]: Use send instead of write. + (my_stpcpy): Define a stpcpy replacement so that this file + continues to be self-contained. + (agent_send_all_options) [W32]: Don't call ttyname. + +2004-12-21 Marcus Brinkmann + + * simple-pwquery.h (simple_query): Add prototype. + * simple-pwquery.c (simple_query): New function. + 2004-12-21 Werner Koch * signal.c (got_fatal_signal, got_usr_signal) diff --git a/common/simple-pwquery.c b/common/simple-pwquery.c index e7f992a88..0b70ddecc 100644 --- a/common/simple-pwquery.c +++ b/common/simple-pwquery.c @@ -1,5 +1,5 @@ /* simple-pwquery.c - A simple password query cleint for gpg-agent - * Copyright (C) 2002 Free Software Foundation, Inc. + * Copyright (C) 2002, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -40,6 +40,10 @@ #ifdef HAVE_LOCALE_H #include #endif +#ifdef HAVE_W32_SYSTEM +#include "../jnlib/w32-afunix.h" +#endif + #define SIMPLE_PWQUERY_IMPLEMENTATION 1 #include "simple-pwquery.h" @@ -63,6 +67,25 @@ #endif + + + + +#ifndef HAVE_STPCPY +static char * +my_stpcpy(char *a,const char *b) +{ + while( *b ) + *a++ = *b++; + *a = 0; + + return (char*)a; +} +#define stpcpy(a,b) my_stpcpy((a), (b)) +#endif + + + /* Write NBYTES of BUF to file descriptor FD. */ static int writen (int fd, const void *buf, size_t nbytes) @@ -72,7 +95,11 @@ writen (int fd, const void *buf, size_t nbytes) while (nleft > 0) { - nwritten = write( fd, buf, nleft ); +#ifdef HAVE_W32_SYSTEM + nwritten = send (fd, buf, nleft, 0); +#else + nwritten = write (fd, buf, nleft); +#endif if (nwritten < 0) { if (errno == EINTR) @@ -102,7 +129,11 @@ readline (int fd, char *buf, size_t buflen) while (nleft > 0) { +#ifdef HAVE_W32_SYSTEM + int n = recv (fd, buf, nleft, 0); +#else int n = read (fd, buf, nleft); +#endif if (n < 0) { if (errno == EINTR) @@ -182,8 +213,10 @@ agent_send_all_options (int fd) } dft_ttyname = getenv ("GPG_TTY"); +#ifndef HAVE_W32_SYSTEM if ((!dft_ttyname || !*dft_ttyname) && ttyname (0)) dft_ttyname = ttyname (0); +#endif if (dft_ttyname && *dft_ttyname) { if ((rc=agent_send_option (fd, "ttyname", dft_ttyname))) @@ -259,9 +292,6 @@ agent_send_all_options (int fd) static int agent_open (int *rfd) { -#ifdef HAVE_W32_SYSTEM - return SPWQ_NO_AGENT; /* FIXME */ -#else int rc; int fd; char *infostr, *p; @@ -286,7 +316,7 @@ agent_open (int *rfd) strcpy (p, infostr); infostr = p; - if ( !(p = strchr ( infostr, ':')) || p == infostr + if ( !(p = strchr ( infostr, PATHSEP_C)) || p == infostr || (p-infostr)+1 >= sizeof client_addr.sun_path ) { #ifdef SPWQ_USE_LOGGING @@ -296,7 +326,7 @@ agent_open (int *rfd) } *p++ = 0; - while (*p && *p != ':') + while (*p && *p != PATHSEP_C) p++; prot = *p? atoi (p+1) : 0; if ( prot != 1) @@ -306,8 +336,13 @@ agent_open (int *rfd) #endif return SPWQ_PROTOCOL_ERROR; } - - if( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ) + +#ifdef HAVE_W32_SYSTEM + fd = _w32_sock_new (AF_UNIX, SOCK_STREAM, 0); +#else + fd = socket (AF_UNIX, SOCK_STREAM, 0); +#endif + if (fd == -1) { #ifdef SPWQ_USE_LOGGING log_error ("can't create socket: %s\n", strerror(errno) ); @@ -321,7 +356,12 @@ agent_open (int *rfd) len = (offsetof (struct sockaddr_un, sun_path) + strlen(client_addr.sun_path) + 1); - if (connect (fd, (struct sockaddr*)&client_addr, len ) == -1) +#ifdef HAVE_W32_SYSTEM + rc = _w32_sock_connect (fd, (struct sockaddr*)&client_addr, len ); +#else + rc = connect (fd, (struct sockaddr*)&client_addr, len ); +#endif + if (rc == -1) { #ifdef SPWQ_USE_LOGGING log_error ( _("can't connect to `%s': %s\n"), infostr, strerror (errno)); @@ -353,12 +393,11 @@ agent_open (int *rfd) *rfd = fd; return 0; -#endif } /* Copy text to BUFFER and escape as required. Return a pointer to - the end of the new buffer. NOte that BUFFER must be large enough + the end of the new buffer. Note that BUFFER must be large enough to keep the entire text; allocataing it 3 times the size of TEXT is sufficient. */ static char * @@ -505,3 +544,64 @@ simple_pwquery (const char *cacheid, spwq_free (pw); return result; } + + +/* Perform the simple query QUERY (which must be new-line and 0 + terminated) and return the error code. */ +int +simple_query (const char *query) +{ + int fd = -1; + int nread; + char response[500]; + int rc; + + rc = agent_open (&fd); + if (rc) + goto leave; + + rc = writen (fd, query, strlen (query)); + if (rc) + goto leave; + + /* get response */ + nread = readline (fd, response, 499); + if (nread < 0) + { + rc = -nread; + goto leave; + } + if (nread < 3) + { + rc = SPWQ_PROTOCOL_ERROR; + goto leave; + } + + if (response[0] == 'O' && response[1] == 'K') + /* OK, do nothing. */; + else if ((nread > 7 && !memcmp (response, "ERR 111", 7) + && (response[7] == ' ' || response[7] == '\n') ) + || ((nread > 4 && !memcmp (response, "ERR ", 4) + && (strtoul (response+4, NULL, 0) & 0xffff) == 99)) ) + { + /* 111 is the old Assuan code for canceled which might still + be in use by old installations. 99 is GPG_ERR_CANCELED as + used by modern gpg-agents; 0xffff is used to mask out the + error source. */ +#ifdef SPWQ_USE_LOGGING + log_info (_("canceled by user\n") ); +#endif + } + else + { +#ifdef SPWQ_USE_LOGGING + log_error (_("problem with the agent\n")); +#endif + rc = SPWQ_ERR_RESPONSE; + } + + leave: + if (fd != -1) + close (fd); + return rc; +} diff --git a/common/simple-pwquery.h b/common/simple-pwquery.h index 5947c42b5..c7ebf9401 100644 --- a/common/simple-pwquery.h +++ b/common/simple-pwquery.h @@ -57,6 +57,9 @@ char *simple_pwquery (const char *cacheid, const char *description, int *errorcode); +/* Perform the simple query QUERY (which must be new-line and 0 + terminated) and return the error code. */ +int simple_query (const char *query); #define SPWQ_OUT_OF_CORE 1 #define SPWQ_IO_ERROR 2 diff --git a/doc/ChangeLog b/doc/ChangeLog index 9a9e213a3..41d6b811b 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,9 @@ +2004-12-21 Werner Koch + + * gnupg-badge-openpgp.eps, gnupg-badge-openpgp.jpg: New + * gnupg.texi: Add a logo. + * sysnotes.texi: New. + 2004-11-05 Werner Koch * debugging.texi (Common Problems): Curses pinentry problem. diff --git a/doc/Makefile.am b/doc/Makefile.am index d44765a2e..988bbf849 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -1,4 +1,4 @@ -# Copyright (C) 2002 Free Software Foundation, Inc. +# Copyright (C) 2002, 2004 Free Software Foundation, Inc. # # This file is part of GnuPG. # @@ -18,11 +18,14 @@ ## Process this file with automake to produce Makefile.in +EXTRA_DIST = gnupg-badge-openpgp.eps gnupg-badge-openpgp.jpg + info_TEXINFOS = gnupg.texi gnupg_TEXINFOS = \ gpg.texi gpgsm.texi gpg-agent.texi scdaemon.texi assuan.texi \ - tools.texi debugging.texi glossary.texi contrib.texi gpl.texi + tools.texi debugging.texi glossary.texi contrib.texi gpl.texi \ + sysnotes.texi DISTCLEANFILES = gnupg.tmp gnupg.ops diff --git a/doc/gnupg-badge-openpgp.eps b/doc/gnupg-badge-openpgp.eps new file mode 100644 index 000000000..8edbaa617 --- /dev/null +++ b/doc/gnupg-badge-openpgp.eps @@ -0,0 +1,7798 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: GIMP PostScript file plugin V 1.10 by Peter Kirchgessner +%%Title: /home/wk/gnupg-badge-openpgp.eps +%%CreationDate: Thu May 4 10:58:15 2000 +%%DocumentData: Clean7Bit +%%LanguageLevel: 2 +%%Pages: 1 +%%BoundingBox: 14 14 250 250 +%%EndComments +%%BeginProlog +% Use own dictionary to avoid conflicts +10 dict begin +%%EndProlog +%%Page: 1 1 +% Translate for offset +14.173228 14.173228 translate +% Translate to begin of first scanline +0.000000 235.680000 translate +235.680000 -235.680000 scale +% Image geometry +491 491 8 +% Transformation matrix +[ 491 0 0 491 0 0 ] +% Strings to hold RGB-samples per scanline +/rstr 491 string def +/gstr 491 string def +/bstr 491 string def +{currentfile /ASCII85Decode filter /RunLengthDecode filter rstr readstring pop} +{currentfile /ASCII85Decode filter /RunLengthDecode filter gstr readstring pop} +{currentfile /ASCII85Decode filter /RunLengthDecode filter bstr readstring pop} +true 3 +%%BeginData: 538041 ASCII Bytes +colorimage +JcFO*s8Mfn"T8)mr;QTn!<2ut"TA>srVl]o#6">np\=R`kPk_ar;-9cq"j^aoEFs_q>:*fjo>>Z +!W;rhs8W'#rVlirq>^Kos8;oos87HJgAc[~> +JcFL)!<2urs8;lpqu?]qrr!0&q>UBnrVuoss8N#ss8W)rs82fqr;c`nq%EAjmd0/tgXXWn_SEgo +ZEUO:YkkX)Y8FOM['mHQ\\5o'cI:M)kN_R0q"aXYrr;urrrE&prrW/srqlWsr;HWprr2p#rr<#t +rpKg]rdk+4s*t~> +JcFa0"T.i_q"sO[s8Dro!;?Bir;R!!qYU6is8Mrr+8l!3q=saWna,K$j58S8^UCAGU7%7*Kn+Sq +EGoZ/AGTg9@38uiAnYmnDfpM`Ng?H@\\QG^Knq>5aCk5Tr~> +JcFR+s8E)trr2`nrr2co"T8;us8W)trr;usr;Qcqp\u$-q"*n9hqQc6eC2mtg>:ZBhV[8OjQ63p +)<0W6i8EMMhW*VRhVHr>f@JI%g#2/eoCVnNrrE&rs8W)rs8W'"s8N#qrrDuqs8Mutr;HVGs4[L'~> +JcFC&s8E#urVlZn#lXZ!rql]lrr;usrr<#t!<)iqs8;oqrqnJIp@@>&f$24J['-I,VP9f\Vl6Jg +US=NZV5C#`USOZ\USOcaV59u`US=Z`USFW\rhBgsVldAAajAYun+QbW')VV*rVZQjrVl`orr)ip +qYL3jrquotrVlcq"9/?"qsOL^rdk+3s*t~> +JcFa0"TA2nqu-$as8<0"qY^$P37=B\j7?sm>MA7oUmEHZegQ(b4c_U.#oio]LlnauMLq>1'gs8W)u +r;$0erVlfkrrW/sp\t'hrV6EkrIP"-s*t~> +JcFa0s8E#rrr)usqt^3c!W2fjrri?!q>C0irW)ops8MutrV$6qq"*b.g>h5mi=b//hq$?0f@\^+ +e_Jm6hVdGUk5" +JcF^/!<2Qhs8;urrr)isrVc`trVZWnrr;rqs$m"aq"jg\pup\gbe^]cSu/0s`qY^Bn +rquforWN2trVcZnrqHHmrdk+%s*t~> +JcFL)rr)ls#6"GrqYL*dr;?Zrr;$?mrVZ\#rr)]`lfmEeg;LM%SW8)7?tE\L>$>-7?tF+jCi*p$ +BPhU(E,p)DG^b*lIXlltIX63_FEMSDEGT<"@qB:`?t!GI=]ed/>$PBAASus\UU7ngj6#Uln+QYU +r:Bpkrr)iqs8Doorri5qqYU5Bs4dR(~> +JcFa0$3'htr;?Qnr;6 +JcF-t"TA8rrVl`p"oS5rrr<#trW)lqrVua7q!?nhah5*_Vkg)`VPU/dVlHo-]>;M5f&>uepAX^h +p@n@XqYU-bq#C0ip]1'fp*o\/q>C'UhU9H_^q6tVW2-5_SXuOLTq@pPWj97Xf]2JqqYL-dqu6Tp +qu6Nn!W;qJs3:Ro~> +JcFO*#6"Gqqtp?jrqccnqtpBkqu6ZprVnDFq"XUWo^V84iQo6CT9"_LDJEfp@p`ME>$G?FCN4ZZ +Q_LRf[^EKK['[7?[K3kKqRljAs1A?DrjjPe\@K2ZWgAWkH$!q9?<0s);c-S!?=IDVDg?ttTWu;_ +io]Oomdp5NrV$6krqHEnrVQTmJcFL)J,~> +JcFa0"oeDpr;?Qks8W)tr;ZclrrrE%rr2cnqu6j!p[7G0h>Z7:hq?Z:hW*njo'HAWrr)6a!<2Ti +&b"f=hV-Z>hr*DLi8iqUlLb&Nrseo'r;6Bequ$Bhr;-Elr;Qs"qtU!arqZQrrqu]nJcFp5J,~> +JcF!ps8Muq!;ucp$N0l$qt&q!ah+s[rLjRqUSb&r]Z8FQp%JC["o8&pqYL*gr;linrr*'!r;?Hg +q>C3grVR*(r;$-_qY^9krVH?brq@T'hTW^CZE:(,WMZGaTq\ +JcFO*#6"GrqYU6inc's7qt^$Wna,Dj_Q&uXHu3>#?s[>SCN+NNO-lTP]=bbd\@T>c^;'Z_$,OEo +`5KR3_ns:d`VmaQ`#nU'>\8;AT +JcF^/rr2Zls83&tqYL-frq6:$rT`1fdb*I7hV-cJn+ZeSs8W#sqZ-Wps8W)trrN-!rVcconbrRe +rr)j&rqu`nr;?NlrqQKsp\"%@k2cO$#isq!g=Y*9meHVX#QFZ#s8Murrql]uqt^'brdk+(s*t~> +JcFR+s8N&s"8r,rrqQKsrr)flr;6Kl)u0C$iQ\s8V5C5hV4sch\\lY_p%J(Np%eOarVlisrr2p% +rr)fprr;uqrrN-!nbrOer;Zfqs8E-!r;?Nmr[@[>p\+:Qo$Y^t\$)g.US+ +JcFI(rVcBg,lRW5na,AnhpB$4NI,PYBk(IO>%MVuLU+A"]">_p`lQb`5KX7a2lBG +bK\;Un]E`1`5Tj@aiDB=`Pod8`lH0@a2>[)\[A`FMgoe]>ujj% +JcF7"s83-!rr2imqYU6grseqdgZ.2IgXss'fB;Vrqu6ZpoDejhqu?]qqu?Kjrqu`l!<)`ns8E6& +rr;urr;H(QGio&M>h":CA!<.QLcMrC~> +JcFa0s8N#rs8;oo"8r,rrqQL&rqu]mr:]gFcEEt?Undpe$a$hYdba6\q"OU[r;6HprVZTlr;uur +rr)lsrr)fqrq-6hrX/W(rr)cnrVliqrql]rr;Q]qs8F2?rr2lno]b,X]sau`!~> +JcF^/s8DN3[^iu^^AYkN`5BR5`r3p\_ns=0a8X*Y`W+$Z +bQ#cdbPK?[aSa'[`l,jq`;[dXaN=G*.EffN`Poj8_SEq%^V@CgVj`WfCL:1F<`rg>Anc+4TY]@@ +n+$#BqYU0es8W&ms8W#tqu6Emqu)*GkPp&~> +JcF7"!ri,qq#:cleC3"-hVmJSlhCAVq>^Ko"T%ogq>C6kl2M:kqYU-dqYU-equ$?hqu-Kirr`9# +s8Muo$N0o$qYC!crr)iprr2fp%fQ4kiSE;Lk2YC\jP8AWlMgk`JcF[.J,~> +JcFa0!ri/rqYgHnq>V3/r;6 +JcF^/s8Dcnr;APNq"FFSo'bl(bcHA6A7/D8;,^qJM3OpK]u7n.a2Q!;aN)9=_o':)_Sa70`l?'= +a2Z*;`5V\prQ#u$aN)9?`Q,s<`Q6'?`lQ0Aa2Z-<`lH3Ba3)TJrlGMjb0%`B_o0R7`Q8"us2Z.s +\#GgUK5t=#='K$<@VBUtJZ-`Gi9'Fuq"t$grri;tqu-Kis8W&sqZ-SGs6'E4~> +JcG!7!<)os!<2lq%0$2&qu6WqrVuoqr;6El$iKVbhr!8Cf\Pf]rV6C#rqlNcp%A+Uqu$Bkqu?]q +!;uKhr;I0'rqucnrqucnrqucnrqcQerqH?grqQL%p[I>)jQZ$slKIL#q"k"&rVl]os8N&urr<#t +s8Dups8ITLjSs`~> +JcG!7!<)os!ri6"r;H]pr;Rl;s8W)urr;usqWQ>A[\BOlUo1N8cemsWq"ad`rr)ihrrN-!rVcs" +rr;rprqlZls8Duss8N)unGi%Xqu?Kks8Dus/,]5>q"!\"a0VY9T:MLCStr[2g$SV4qu6Wps8;lq +qYpHkrr)irrVucprdk+1s*t~> +JcG!7!<)os!<2cn-N*c;q"=FYq>^3VjPeh0VN64@ARf+YD1.;6W3WtF]Y;4u_oBd?rQGboaN2B@ +`P]U4`Q#p=b0'P$qo08i`l?!;`Q#m:`Q#m:`Q#m:aNXV-rQP5_!6FuYq90i'aM59p\@]>YWg8E[ +?rp<(9i"\pFHE]NiTp%2qu6`ps8Mlp!<%KKir=N~> +JcFm4%fZG+rr)inrqu]kqYL!dq>UQ[f$i=Oh?)p4rq$-or;69`p%S@\rqu]nqZ-QhrrW2tr;QTn +r:Bpfr;QTjr;6Kkr;?Q^rsSenkj@Qmi83/ +JcFm4#6+T#rr)ipp\u94qu$9^ilA3uVPU)aYeefImJ-_UqYgBiqY^ +JcFm41&h.Nrr)ios8W&rqYBgXp@Ih?m,?C&R!NLY?s?fHHB+W$]Z7t3aiDB<_Sa:1`lQ=%bl>i] +aSX-Xbl5c_a:lS7`l?!;`Q#m:`Q#m:`Q#m:aNXY.rQP8`r5\r^bfp(/rlk8^,fn$F`l?'A`kfO( +YG$r!@T-,u9iPG5FFUO9iT]e(q"t$iqu)*GgAc[~> +JcFs6s8Mus$i]trqYL*gqYBs^rr)j&qsik'f@nm9oBcMWrquctrquTkq#C-mqu$Hkqt'U`qssXb +rqcZorqZKkroX4ho]tGjh:p]AiSs.mr;Q^(rr;utrqH?fq"t'hJcFa0J,~> +JcFs6s8Mio!WE#qrttY3q<5u3Yc4:iX0KLom.'lLr;-*YrVuiqquZirrVc`qrrE&trVulms8DrV +s8)]rs8N#ns8;lnrVZTnq^MIBrr)ZjrVcZkqt\se\?Dj+T:r!X[_p_bp\aabrVuops8Vuqs8;lo +rr.KKkPp&~> +JcFs6s8Moq-iWu9p%\=ZrV6$Pho2.?G\gboAS-+GTrbc@aN;THdEg+^aiDEA`qm[S_[sf,aNDcR +cdC(bc-4>ScdC/>ci;8ibl5a!aNDTF`lQ0@`lQ0@`lQ0@`lQ0>`V[[NaSs?]aSs0\`P]Um_'[.F +c-4ATbf[rB^q[Us]"5DDNdGYQ=]SX1BRGrEb1YqCqYC*irdk+)s*t~> +JcG!7s8;rsrVm$"q=sjdqYpicdbE^CjlGP%qu6Zqr;QitrVl9c!W;ioq>^?lquZlpq#:$eq#C0g +qsXF_rql`orqZKkrq??lrr2irrqud"rqc?[oD&7`$N',Jjl5F^g=G*Hqu6j!q"ssdrdk+/s*t~> +JcG!7s8;rspAb0l%eoqpaJGZ)U7nEdcLCT&rVm0&r;?HjqYU3hrVlfprVliq"8r,rrqcWrrr;uV +s8)`cs8;itrVlfqr +JcG!7s8;rsqYq]:qY'RPn`K&iVgEAm@:3P[MQ!_]]XkksaNW#[eBQ1\rltJds2Y2[rPefS$cU-* +aND`Ocd0k\bl5`bc2Q&hc2Preb500gb/h`Hb/h`Hb/h`Hb/jFupr[_/YH^i7 +JcFg2s8<'"rql]lrs.Z@hqcf0jneoT"oA,pqtpUHoo)/C^rVZWns8W#rr;?Hh!VuTkr;Z^&s8;flqt^$]p\k'frsJJ_hq[)GhrikGoDJUjr:]XX +JcFa0J,~> +JcFg2s8Dut(B+:4rquuF#TqJ?[TuZX!rVlis +rUp3ir;HPEs5s?3~> +JcFg2s8Dut)Z9X8qX`RiUi^!PA7TM4U:S1R^VIOu`l?*BrQH/&b0%uScHXPWbfn5PaN2NKccs\V +aMu3<_u@XT`r='W`r3mT_[j`-b08#Pb08#Pb08#Pb09e+rlG)You6^G1!%VdcHXSTa2Z'9`Q63G +bfe8P`l>j(Y,mFu?s-Q3;dj6PQH.eEo)&Iequ?]pqtg8Bs6'E4~> +JcFm4s8Dcn#P6g&hVdGXo)&FeqYpKpr;HNlquH`qs8N#pr;Z`ss8N#q!<2fo"9/5qqu-Hmrq?@' +r;QWnr;QWnr;QWnr;QWkq>L3jr;Q]ms8N#sr;QTlq>^6iq#pWor;?Qnrs8T!qYU6ir;6EersA2Y +h;78PkgRiNJcFO*J,~> +JcFm4s8Dut'`S(.qtTL-[%X1aUp%esnbr@bqtL'hs8Mrr#QFc&rr)iqs8)]prVZZnrVuitrVlQk +r;Q`ps83E+rr;utrr;utrr;utrr;ujs82f^rrE&trrW/srVc`rrr2rtrr +JcFm4s8Dut)Z00sn`nf>NcAN/=(6TL[)0c(a26!Fc-"/Mr5o/`b/_TDrlY/_rQ>/d$I-f;`l5j3 +_8F10rPnZP!5n`R!QN4Ib5]Q]`r!aR`r*pSaq)\>aiMQDb/hWBaND`Nb/aM(s2Ptr_RQY-F(8*E +='BBLE2h:Fl1=]IqYpNoJcFm4J,~> +JcFm4!;u]n#4C=!e(EgTqu6m"r;Q`rqY^C6hs7uforr;fnrqlcnrVZ]poDejirqufn +')_b.s8N&ts8N&ts8N&ts8N&to)JUdq"tHtr;?QorVc`orVZZqr;QWrqtg0gq>C7!q!?klhWNtU +iUm$I!rVckJcFp5J,~> +JcFm4!;uir&GGqcftOJ]TVoEJiq33Ks8Dor"9/)ks8;fsrr<#trr;osrql`prqQNnrqlWor;?Nl +rql`Xrr)crrr)]mrr;rrs8W,ts8N6$rVlcorr3*"r;6Kkru(_0p\=[^p[["VWLoo`V4ONrg@=b2 +q>UEkJcFm4J,~> +JcFm4!;uir)"$.uYE![:?Y"A=TsVYSa2H!;b0A,K`m4S+!6>)Y!6>)Xs24uX`lJ%trl>)ZrPefV +rQ+lY!6G)XrQ+lYiQ2&Aqoo&^rQGPi`l#^5a2Q*?rl5;caiDB;`5]j=rlGqr`59L.] +MuWYQo)JUdqu6Zqqu6oucIq+.fB3&0rs8T$s8;ijq>:-ersSc$q"ad`rVcZlrVcQkrquZj!rW&s +pAb0l#Q4Jrq>'g]ro3tJrr +JcG!7,6%WBqu-H`i1m0MUS>-CnFHVWrVZQmqu?]qrr)iqq>1'hpAb0ln,NFe!<)fmrr)fpqu?Hj +!<;lo')hh.rr)iprr)iprr)fprVc`opAY*l"o\H#s8N&sr;uoqrr)isrVZ[5rVZQjrqQ6_n%YZp +TqJ'PU8m8Nqu6Wqrr2`nr;M9IpA]X~> +JcG`Jli/X:qXa7DhSXSXEFW6jLoISm_8X@3b/;<@`lH3EbK%]HbK7iE`Q#m:a2c3>`5KXl`WsK! +_8F73aN4A'rQGGibfIfF`r='Zb59B`b/jS%rl>Ab`P][7aN2B?rPnZS#fas,b08,Tbg$+1qoT8e +a2>j7`l?*Aa2e1u,K.UB_oB^8aj8>[d`fnU`lQ!0\upEb=]83t>%DiiiTg+5rUTsirVQQhrrE"L +s*t~> +N;j%[r;?Hhrqufqr;Zfrs8NW/s8W#mr;6?gq>UBnr;QWlrs&&Pgsj^'mJHq^rr^Hkr;-Bhr;HWnrrE&srr`3!rr2fm)ZKd8qZ$Noqu?NlrVufls8N#trr;ut +rr;urrr3T)rqufqs8;opr;HZms82iqs7QBis8N#pr;Zcnrs\c(s81m:k2kabf%9ZnrrrDuq>C6k +q#:Bmqu6Blqtu$F!<7Q~> +O8f:\s8M`lrr2oss8E0!rr;usrVm`5s8W)qs8Drpq=s0lW26AhZ-2A.rV6?jrVulrs8W)trVc`p +r;ZTl!W;iorsA]$s8W)urVulsrVcrur;HWnrr3'!rVufpr;R3)qu?Wpr;Z`qrVufmrr;rr"TA>u +rqucq%efo"s8N&rs8;fns8;opq>^0fs8N)uqu?Worr!]6pAFd<[&9aqV5Boce+W_;rVlisrr2rt +q>U$ds8IcQJ,~> +OoPI[oDejc!;lWm#Q=PrrVulsrqcX+q(qn_u%FRaSa3Ybl>ibaT'9[b5KE] +aplG2`lZ<@_90d:aMu?BrQ#Glc-OYU`5os?`l-!Ab5TWbaoof*b/D0rb6u5(]#2S:b/)3=`lA#! +rlQ"t_o0L7^<"ICbfe/RaMu9B_oTa;`Q.nrq8iKRrlFuZs2b5[&]iD@aM*gN=&Dan?=7iLcJeBo +r +NW9%X!;uins8;lqqu6Zqrr3'!rVl`m"9&5prVZ[#qV]uZhV@Ppqu6p!qtp`MJ,~> +O8f4Zo`#9ts8W)us8N#trr)Zm'D_P(rqQ?_h7KV^U85c_oDeger;?Qqrr;Hds8Vuq!W2lqrseu+ +r;Z`qs82cps8N#trr2rrs8N8us8N&urV6Blq[WZ*r;Z]ps8N&mqZ$Bks82fprrN,pr;R6*s82Zj +s8Duqs8DuqrSmhVrtGD1q=adUca]gBVl62_]BAh`q#1*i"oeJtqu$Hes8DqOs*t~> +OoPI[oD\mhqYg9j"9&8urVlg5rr;lprql9Ri4Y1j@p)uhT=DhW^r4=6rQ+uXqoJWV"N\a,`l@nu ++j%sPb/VB<`llEBaMl3Fb/hZDaND`Qc,n&Bb/h`Na2l@"b5]No_SOL@bga_M`PTa9aND38`r4!Y +ap-)5a1o4%aSs?lb0.iFaNhiJ`lZ0?_o1QU)9gOVbJC]uVLiM^:gR^>BSsmhm-F9 +NW9"WrqufqrVuoqr;R$$p\Xmdqu$Hgrs&#Rf\G?=nbW:crWiAur;?Qns8V9]#ljo&r;Q`rqu-No +!W;ijrWN0!rr;cmr:p9krs\l's7lWmp&Fsis8DunrX&W"qu?]ps7uNgrr2rt#l=T$rVufqrVtjU +"T&&rrqcQm#kd!1kNM*hg\:[H$hsMnq>:0ks8Dlnrdk+Fs*t~> +JcG]K!<2ut+8c';s8Dlps8N#ts8MulkJF:*grr)lqrq$-irr2rnrrU-frr2ourqu`pqYgNqrr2ir +reLN(~> +M#[GPqu?Zos8N)urr;uq)?'C,qtp0YiQ%1)?XdhpRDZ_P^VI_)`Q6.!b5]QR`W4*Z`qd\!ai;9: +`lZEA`5K^;aN;ZKaMub/MQE +`lH6Ebg4YXcHaVXb0.oMrQ+`QnAlHd_8474^W"7)_R>M@@U<5CAS[XPiTp:3p&>$hrqZWnJcGcM +J,~> +NW9"Wq#CBk!<2or"9/5prV6BsrTDblhW"D,s8W'%rVZQjr;QZplM_=orVQNkrVlijs8W&trqHHg +rWiK!s8Dlqq>^9i&,c,#p](*dqt^9fs8;`hs7uWtqZ$Tnqt^3j%K?8%rr;onr;?Qmrr2rWrr1*ir;;-Gq#>j~> +JcG`Lrr2lrs8Er:rr2lpq"amai5)7hU8Z0"q>'pdqtg3erVl`p"TJAurr2Bdrr2os#QFZ"rr2rt +pAFpnp](3krr2rtrW`Dus82Wlq#(0jrX\r"s7cQhq"XU`q#C9hqZ$Bj"oA9!r;6rr<#srr)lsrr2lmrr;osreLN(~> +M>mSSrVQWnr;Zcn!<)co)#Emklg!EDP\k, +OoPCZp&G'i!<2`m!W)`mrs&>Rf%f'AqYU9mrVccrqulurr;Q]ns6BU`r<<3#s8Dilrr3,trVuio +q>LWos8;H=\D[2Yrr +K`D&NrVmo;s8N#trVlfrq>:0cr:ndBTV\Qoe*Q`)s8W&prql`orr2lrrq?Bjrr)lsrr<#urr2p! +rqlTlrs/H$s8DlorVl`p#Pe>pmaAN"q>L?lrr +MuNkWqtp?gs82`o!WDlnp_`l-s8N&sl/U?mF^efgEKI61_TBp<^r+7laoK]abl>icaSs3V`W*sV +a:-)-`Q$!?`l>s8`Q#ps`>m7M`kT=1ai_lQb/VE>`l>sHf$Usm[Dg%u_8XLpaq2kEb0SCgH%:@/ +e&]bKccF9,aT'7!b07oKf%&7!cH4;XcGd]CaN;WJbK%]Db0%fHrPnfTrPnEL)oft:_oU'HcH45L +_Rd.aV1N8b +OoGO_r;Q]_rrDumrrqZ6f\#0YrVmH-rqQUEm%K?:=-7:#^.aRefrV??drYPG2s8Mfjnb`+]q"jpgrr)clrql]ms8V*X +qu6s#qu$Kks8Vonrr39$kMb=]lJ^dsrVuosqYpQprVQZpKE$H~> +LAq8Qrr;oqrr3*"p\t0kr>#8%n&D6&UT;H'q"a^_rqlQks8W#prr2usrVlcorr2coqu6Qorr)ru +rVlisrr:3krVZTmr;QWos8;lis7-(8q>LC9jp!gILStMgKWQ=B$r;?Tprr)fp +rr;unrrE%Qs*t~> +N;itXqtpBhrVQKl,lRT7nbDtVp\=IDd[YDq@:Ob`\%KJoaMbp5bJqE;aihoNc-?73rQ>/]qSrQV +s2lt`lQa3)R'aT'6d_TC0Rdauamcd_s;%+EA>`l#m8aMuHMb0'_)rPniUrPn?J*mDmPaN)?@ +a25g6\\#_iZ]As-:/Y8'H`# +OoPI\p&>$kr;Zcqq>UWff%\j5lMLScr;-M31oDeWQ8gmPPO.jSNqXXOYq>^Enqu-ENrW<-!qu6U% +rr;umq>^Kkq>U?m#Nab'lf$mgpAY'pqYU*drquctrquZmquH_Ns*t~> +O8f4Zrr3*"s8N#prr`5trVl]o%fQ+tqW#MhW2Qu>m/I"XqYpQprVuos!<;HcrVZ`qrr<#t!WE#s +s8W!&s8DZgrUTsfpAY*jrr2p%q2Z[>++Eq]s8N#trso&'rCnCV)AaT1qu$Kms8N!!s8N#srtb5+ +p6.rk:2#TX_qb%Vq=j[arVucnqtg +NW0+Zqtp6hqtg9grt55,pZgnu_3\nLBQAj3`5TF2rlYYmcHaDMbg+GUbg$+1rQ>,`s2t8\A]i6A +c-OYU`l5s?bg";L`PfX5bK7cRf?;Ug_TKm_nrmnZ?K\^>>A=>Q,2POq#13mrVcTnrIOtKs*t~> +L&V8Sr;?QmrrW2trql^!k185>jnSiT#la`"qYBpbq=ag_"o7riqu?]TrX\u,rVZ]qm+//+BU/,f +rVZKj#PUk=)CO,1s7uX)qu?PM,:4EZ,Tn;Js8)cmq>M3-s8Jf@-QjQS-lk#g,V3M]RD%8Eq#C +OoPI\r;Zfr!WW/srr`9!rr2lr')qe+qr58dV5:QFnbN4^qu?]prr2usqu6`ss8VKcrVZits8W)r +r=K#.rVul`eY\^EP4/GLrVuorrWrQ'oL9u<,1?2`s8N#trt##,pk9b4(aC%=*5;ILs82fp!WW/u +rtkV5r1G2W*$#tD)(ZmP-X_Ok]%-?Frr)NhrVuEe"T8<"rVcWn'`7q1rVu]np@nCR]VM +NW9%U"8_olrVQWprVQU5q=*V(]SpiE@;h.4^rja?cHXMRbf[uJaN2O'aTB`2bl,]`aSsE[b5]]c +c!erCb/VHBc-aqba2>j6cd',"KmcO6aOA2G_o9dBbfI]Cd4c.8%^jI%_nj@8bf[rE`RELV#R^\F +'+YMHe&K\Pbf\#HaMu6=_U6nd%M&XC!"/cB$4.;:Bm?,Qb0//Tq9&ZWrPnELs2lk=cH"#Ja2uEI +a2?'CaN;6?]Y;1iH!40G;HS"2bNes(rVQKgqY9p`qu-JGs*t~> +L]7b]qtg-apA+U`s8VomrrqK:*hqZ$Tpr;QuVi8!>Ih=LRE!;QBi!WMtNs*t~> +OT5@[r;Qlus8W)trttb8s8Dusq#:*aqV8EUVl-cRmeZhWs8W)rs8N#rs8W'&rVlipqu-Nns#L&X +qtpEnrr<#qs8;iqq#C9is8;olpA=mfs8Mrroe.=1*$MQcq>^Kks8DusrVXDT)Bh]Kq#:#Xs"N#8GN2_s8N&u!<2ut0)keD;]-Yr+X%mI*ZZ+9)]0S;+!aLOo)/OdrVu]nr;HZo +s8Dutqu$EmrVHNlqYgNorqHEorr)for;Za/s8Munpt2XFTVnN]`UNa#rVu]mrs&H%s82irrJ(?&~> +NrK7\qtg3eo`#^*s7Y^5Y^:(qA8e'F_8O@cd9nMaN)]9&e#%. +PnON=2:p0ra3;cI`lQ3@`kogI4Ui&.#R_"N'GhQ(*Z5n5'+m!Obfn8PaV)n>dEKeYaih`FbJM9> +c,[cDa83mWa8X0Tb500o`l5d,[Zb<2>%;#RPLA[[o(_kUqtg +M#ReYq>C-cq=sUXp\b$prRKN[e`co1#5\5nq>:*hr;Qlur;Q]ks8Mm*q#:9mrqufqr;-?kq>^Hm +s"O?Ed[k*.oDejgs7lWH-R9iR/Dp=%r;Zfrq#C0fIO$*\V#LDlr;Zcrp&G'grqB4f-OJ&k9.V/5 +Qi-g^qu6Qo/b1P@+"P=VXbLL\/0Q>i+!`'U+ +OoPI\qZ$Tnr;RK0s82iqr:9^;WM?Vl_"%9nrVulpqu?]qs8<#us8W)trr!N.rVuosrr;utrr;uq +s8Mrmr;ZTirtYG)d$nO"o)Jafs7cQF,9@a:-JS@prr4JHp]('dGo[kEU&=rhr;Z`qo`+sgrq/l+ ++X5&!7OArqP5>.Wrr2rtrr2lr/ak/1(ad)BWI\PH,oIaJ(EFJ9)B=J/qZ$HkqtU$frquZnqZ$To +r;?Qos82imrqHEprr)fnrqc`prqud4rqcBW]VD3qUSjpTq>C6er;Z]nrr2clL&ZZ~> +O8fC^qtg0drV-=0rr)]cjQ!^qBl%.$X1>g^c-+8Rc-"2M`l@tr!64lV"38F(ao98._T'[@aMuBD +`l,m@_o]p>b0eJS`i=DWA\ua8aOJD]WsTCN$PpIu_7[_0c-+SRcXmn%#(k=O`5^*IdDX>P_9C4a +()7@]eM7sQ(OX`UaiqoI`Q6-@`l6-H(D[PnUoo_n:c07/*$>q3'G1ZYB[4Q@aj&)ScI:.aaj.j) +`WsQ(`lQEI`QI_lpr`QV)TKb4]t(7i>Zas0DgKO^m.BoDqtKj[qY0jbLAuc~> +MZ<_Us83-!qYBs]q>'sersmoEjON;js82irrr)`krq??lrqQLkrqQEjqu?3brVuZks8Voos8)Tl +p%SLa^aUNP:](1irr<#rrjO6r.3M=\r;Zfkrr;fopb*g5/bJu;r;ZZoq>^Kgs8/-3+W>Y"rjO-k ++tWK_r\40Gi@#Wf/bo)8r;Z`di7"<[Al9ft,:<0`s8%;3bkq>3s82]ns8)corVucps8;lprVuls +quciorr2cos8N&rs8E9'rr2ikq>L-grs%uRl1!frgAV$RrVliqK)^?~> +OoPI\q#;$,rVuors8N&rq"`UL?nq>^Klr;ZKbs89VY*umrLs8;lrrVZW#+Vu:>8,3#^s7cKlq#C%:)At!pqtg9kqu6Bjs7cQi +PTq([FS#F:*toS?oDS[fs8W,us8OeLr8&V2)(>1Op\XsfnD;N^N+,L%*ZZHurr;eEPKN^to`+db +s8VurrVccns8W#rrVccq"onT%s8N&krW3&uqu7K/rr22qU7IpATr$E6qu6QorqucorVZYMs*t~> +O8f@]qtg0dqYq`;q>:$gp$(;8CLC[^J\/n/`6?QPbK7cE`5BF/_o9X:aS<[+t*ErVQTp +rJ(?&~> +N;rnVs8;orqu6p!s8UO+i8OM2rrW0!q#:'hr9XFgrr)flr;QTlrr39!s82fpr;Z]mrVn)=s5kLP +-R8!Ss8Vfms7lSA-6O?VCA@Q"s8Vimrj*ji-\;9s)#jI5rVuljs8:q!+!N*$rVlLc.3TpsqYU-g +#lXf$dN^.bAbZ3(,l.B\nl/gbeLq>:3drVuirs8Mijs7uZns8Dlpq#:Qt +r;?Hhr;Q]q"oeDon+lkZrs%uZq=*G%g\h*OrW2opL&ZZ~> +OoPI\q>UHoqYpoor9U5UTVKHns8Dors8Dutr;Q]os82rsrr2fn!WW,trr2otrr2p'q#C6jrqufp +rVca?qu>b!+X%olq>^Kes8VlkGTRe>+D1?Aqu?]ir;<`='I&#*ruD%9s8DuspAb$L)AsJ;lMLMN +8g5,mMYR)Jrr<#srWiK$c5duH@J0X",l7K>s7lKgoBIJ?(F&bVq>\hh.jK/@p\Fj`rVuirs8Mij +s7uZns8Dlp"o\H!rr)forW3&urVulrrr3Q-s8MronCOt;V4jHUXR>f[rVlils8IfRJ,~> +O8f@]qtg3gqu7N3q"F@OioJ4&B3ADeYf=]+c,n2Pb/hT?rP8]U`Q$!AaN44tE66);`5BR7`5Kj> +cc"5Qbfn5R`50@2`6l]N\H*0T$cL05ccan_`nFr<'+kjKc,7EAe&p.gOqeQ>?.$d:aN2WLd*0AU +c+F&d',B$Ie'Kfd()1r>aiqiF`Q63C`Q63,&Khi[cHaYS^r+76^W6>u&IT-Z%aE#4dAR$P"BCQ2 +d+628`s0`/aMYs=_u@aWa8X0ZapuM4`Poa4`Q$!Abg"AQ`r +N;rqW"8r/qqYC-oq:Oc\o_nghp]C-erqcWsrqu]np&A(irVuoqr;6ElqYC0cqY9a]s8)cooDeag +rVfS:+XFMms8N&qrr;ro>9kI<-'J<-s7--hnm*:a-,]WYp&G$kr;ZTlrUj>?,q5AXs82i!+se9X +m/Qq]0E(_HSg5:%J,fQHqYgElrVuoqs6M$X)CtLXs7?2?+WVj[eG]4Cr;ZTmr;Q`rr;HZnrql`m +rr)lnrWE)us7cEj#3=e+mG[?prr3,tr;?TlLAuc~> +OoGI\rqcX1r;$F6)s8N&rs7uZl +nl?hS,F7p#qZ!T9*Z$!as8Drss8Dp)qZ!!)'-;N!s82Zlr;Rf8s6(RH&LI)Is76&9*>p"KdJN_= +r;ZTmr;Q`rr;HZbrWW9"rVc`pq>V3'qY^-dnC4G0St_XQ\)[5^qu?]pq1ep"~> +O8f@]qtg3gr;Rl9nE]H'iQ[Tp@pt;/_TL'Caih]G`Q#j4^;%P(aNDa+b503Z`dc%G`QH9?`5BU= +aN2oUdEg%adE'GH[`->6ajDjR&eL/JcHXkaaN`)`5S+YD&TuaNcb%WYbX)m[$Cf/V^Wk!K`ll9D +d*Fic',QDTfZ_m'%N#ckb1"VV_o'R>@jh:Mbd(jnerU!;qTOJ +,~> +NrT+Xs82lqq>UTkf[e[Grr36"q"jsgs8W)prrE&tqZ6Worr3#us8;k1s8;osrr)lsqY^Biqn[t\ +/Sj,;s7QEgpAam^Jg;TdK)GQCs8W#lrquX?*[;[M^]+31s8211,UZ=_r;?Tos8;ijs8Vhc+=8F% +q>^9jq#=,6-6[$rrVc`ps8N#rrqqg%-RGG[r;QQm*r#W\,U4gCqZ$Els7R]i+X(Lkrr)lps8Dup +p](9grr2rtrr2lrs8Mups8Mrrqu-g"qZ$Qps8;fp#3=n3jPn_\Jc>`MJ,~> +OoPI\qu?]q&cMM&rVcKZe>].:`:NX"r;?Qirri?"rVulrs7ZFqs8Muss8Mrrs7uQlq"VOR8L\?* +q#C$eqtC'apO+&%*.Rk`qZ$Toq>U9d+Vu(4*4u.Irr;i[+WV[sq#(*jrr;oqq>^KfRi3@k4S8[K +s7cLb)]fo-p&+ggs8N!(s87cp*ZgmGqu-Bk*r#QW*uuV,q>^ +O8f4Yrqc]orVn);oBkbpbboGf>(!Qb`lcEHc-F\_`l#a3_8+"0b08,Tbf]_%s2K<%a3)KAbKe5S +f$`0CAhFK`[Em@=c,.?F_96I))%p9#b/i5__nF.9eI)g'&e^qiai)HLb75=*0$hVWdEKtY`4aFE +c[lQ?'J/,$bgFJ_2A7#@9$YZ"_8FC=b/M9@E"jH4I`TdYcHF;H_SX=1d]Wrf*^[uj'u\MPe-?Zo +#Y<@lbg+/Qa32H-53`mDoOd)s&)O]tOU:L.b1f'W82K`?Q~> +NrT.Ys82osp\XsnqpjNRkPbDcq#CBns8Vuorr!-!q>:-hqtp^-es8W&t\fDn=;#((hq#CBnrqlHN-R:#]-0"b#qu;O!)C_*Ms8)ZkqZ$Knr:oM( +,9e;Arp]s`s7WW@+!E,[r;?Qns8W)ts8-=R(bWZ#qZ$TmqYpNkrr3bI-"pbPq>^?hs88r<+:*is6S7pjQP=VrVllrLAuc~> +OoPI\qu6]jrVcX$n_9e8W5SK*s8W#qrs/Q!qY^?lrVlZns8E0$rr2rsqu7o?s8Dfnrq?B^^j."J +(`sY9:&Xhdo`"mjrVs8@(EH6Es8D]jrtG>(i?90\(EVH/s8;nO*YTcaq#C?j)>X=3s8D]R+WMC> +[/04iq>^,g*>]V?bkqA;s8W'Is8-1G%k#*eqZ$TmqYpNks8W)1,%Y)Dq>^?gs7iK-)Ajp7o)8Ue +rUp-h$24Drr;HWprr2llrr<#irr +NrL^0qtp?lqu?]kn*&9,BkD1=^V@n4_90p>aN2BA`5KX7aNVrTbfn5PaN2BBaSX+\aN;NFa2cb/hNC7i)d]S]Aanb/M'U!i)p +r.b6%~> +PQ1[^s8N)urqlfor;?R(nBoD6mf*7drV6-dq"ssg$3'o"q>1!`qu$BkrqH=pqu-Nls8VZ@Ijcin +,9n0Q+sSDIrVlisrql`nk:Ru$,jbF.r;ZNks82ikZ7-h>,$XmCr:ru3-7/!Xqu?Zqs8N#tq=neL +,9S.JS=Q+=QCsZi-6"16qu$Elrr4hSqu'&3,pLb:s8Dors8;iqs82Zmq"t*erVucprqZ@q,UXX* +r;ZTmrr;oirjHjWq"ajdrr)orq>UElqu6Wqs8Mup(&e12rV6Elq=O^Yrr<#shrX1fgY_u%s8N#q +s8Dqfs*t~> +ScAZfq>UHoqYq!'q=jaVd'07D]A!&UrVuoq"TSK!rVZZp!<2ips8Moq!<2rs>5eL"qu$K]dWm&b +*ZZ7:*#fk>\GH.&s8Moqqr9%B(a//As8;ols8W#spT>_U)B2`Ps8;U\*ZlKfrql`prr;uts7uA& +)B0M4C7(HWRZsD/)]fY5=8MmprVn_Rr;8o'*#m6(s8N#ts8Dors8)Qkp\Fd_rr;orr:]bb*[)@h +q>^9jrr;ohrO$URp\=Xarr)orq>UEooDSmks8Donrr3N,s8)cpr7A$GUnF6[gACjHs8N#trrE%\ +s*t~> +U&Y,jp](9lr;QcqrVmu6n*&?&LMppQT#&ImbK%]=^rFI;b/qcJbKJ/VccjW4ap?).`PojbPccX5QM+ +U&Y/lp](9ms830$s8MrnqYBsdrr3,RhUUN^rVuoss82cos8R';s7ZKir;?BgrVQWoq>C9fs7u]o +qZ$Hmqu?B`jH*7a+sS?Z+!XT-,Te=Rq>^kh:C]IgZ/>6 +r;6?j#la\tr;Z`qs8Mup!<2lqs8JnqJ,~> +Sc8cjrVZZn!<2or!<2lqs83?%o]r^Kns8MusrVcP@*Z5t +p&+XcrVucms8)Vj+;u1:*?Z7C*?cRK)'fq2*?_ihrr2p(o`+p`,9@OCpAXsh.K9AHs82ios7lQm +r;Zfms7Xb_'c\E`p&G!jqt&0F,9S$T=h4J0qu?Wbrq69qrr<#tr;?Qnru(e2`iGT)URnLLrr)`o +s82cnr;Q]orr<#rq>UNqs8J_lJ,~> +UAkApqtg3gq>UHnrqc`lrVQU'rU/=RCMII/X1ZEk`;RXVaNDa+c"P>G`mW)UbJqTEajA>R`Pf@( +^sUEHb.u'Ah:^Q)FZKsD'GV;e#W3)0'c:6.e]?%Z`l6!I3>`J>[a32WKbJV9?>n.G_&7j>E +UC[qK?eX)UcH"&D`m;lZM@9t0()If!&e,?M%7D0L'cn'OaMl6?ah,gAcPIQ9'Ze/2aMu'5^V\(2 +bK%lMd`TbW_SjF6f[[>="V_f+f#ttRai^_W%h/jU4Hn.;a2u9uaSF!WaS!RQa;<"Cc-4DS`kf +U&Y/lpAYEtr;Q`qrV6^Kms5k^P*[^d_r;HKis8VumrqQEks7cQlrqcZp +qZ$TcK.A&b./s)@p\ojq*ZcRO-+ +Sc8]hrr)iqqu6`ss7uZo')VLi\tc$th=L@>s8Musrr)iqrr)iqs8N6#r;ZZnrr3)qp]('es!%@> +rr;ijfo&3;*Z-(B,U$s6r9KRg)&"=mrVu]ks8N&nHN"lbK_tcJ'`\+2rVZ]mq-4sW+'s^r.643(`j\?ZN'\!s7lW^rqQNmrWE,sr;HWp)ZK[)p +U&Y/k!rMrpqYpQorVHZorql]p')(P4MJ-I-RBscUc,R]>`Pop@rltJes2o6)bKA)J_oKjIbfeDN +_7dS%c-=DL`PL!:GVfd;$l0Hk'3CXUb'k0I&K09JahYp;^W=f5)&`urai)-:c-4SY_SjF<57S,<% +$B1W&JPbPf@nWq`PfmFajS6@%2BHj$l0Hi&J5Zd%1a-n&J75Wbg"2GaiMEH_A=%'&B_l0ahks6_ +nNn*_T'jF`RN)P_SjF3d+?HM&ePZkj3,Nbemf*o$5*dTQ.a@+c+h +UAk;or;QZp"T/#lr;QWo!WDomrri5PgYD&js8W&trrE#ss8;kFqZ$Hfrr)]kq"adbrr2]krr)ls +qYpMs9.2,6+sA0U>.F/'p&>!U,q'f^Zhj_%r;ZTmqmmso,>@t5rr<#tqu?]mrVu@7+t"EUc6a_\ +:&aqfs8DurqZ$NoDB(#GB?SlSP,"VguRMKrVlruqYpBl!<2ut +rVlZns8JnqJ,~> +TE"rjq#C?m&,lD)rVlcdpVrc#Yg<@Bp\t3mr;ZcsrVlisrqud"rql`qrVuln%K6>,qu?]qs8Vrp +s/eik,QAi++X1!rnG`+[roP7D(*TpYs8W&tqZ$B")]0Sdq>UBn5Q:Q[s82cpm3)]hTK +p\t3ls8Mlpqu1:F+;eS.da!lFR[9;)SnDCL*uu_op\t-jrr;`?*?H/2o_ne;r;6?is8MrrqY9se +p](-js8;cos8!QW)&7\pqPGM[(*FhAT)JK^rVuHf!<;opqZ$Tprr3#urVl[*r;ZZfm(f +T)\fgr;ZfqrVllrrVmAifT]`SF-aeS\]E":`l.ksrl]3%aMu6=^W=C2b/_HAaNi;gf?qIP^V\:E +aN2DK-Ogb($j[1E2NZ&/_8t!D'He))O3#D]`6,g9d%(md&M;8K`lH?Ja32ZCaj\H@$kisX]G)"j +/ChqhcGdiB`6Zlf;[=3M9WGS.F+esfG^)W/;+qE6&JKE=e]l7T`l"hf)]2SrajA\ibfRlGccXDX +bf\2U`QQ6>a2lHRf[Km7%h!7(e;FnL"qV=JJ(Mlcb/hWDo#LjOaSj9]aSNpUa8sH)rlQD*`l6!2 +ZDMkL<*<[E[,'d;rVuorrVZWms8DutqtTmYqtpBqrVHBhVuM8~> +UAt5ls8N9%qt^$arql]sqZ$Hkrs8,Ni7S)0rVuosrW)orrrW,us8Drss#U&Os8Vlnq=M(3BVbA% +qtpEgs8W#sqN<-L*$$A-WTa0TrVQWmr;'PD+XD=-q>UBnqZ$HT.ioo_kPkL$rr;rsq>^Hopuj+I +,pjT_*@.6Vr;6Nls8Dros10L"*A$XXqZ$TepAFgbr;62%-QjHYlMghYs8;o"*??MJp&FmcqYgHo +rqcZpnPcUbq>^Kor;Q`pqu->m+"S]_ZW/7n,9n^]l21ARs8Viks81gT"8qrirqucrr;HX"p#Y8t +lJUt,rrW/srr2rtrqufrrqQKnrhf^G~> +U&Y,kq#C?m&H2A%pA+R[k.RLdXn_Ses8W)s"T85us8;Zl/c>YKqu?Qe[V]WNp&G!is7u]pr;ZVU ++W_I<,Z^o.q"t$es8Dlj;]ckuFnbb3rs8N%qVie;*$XMDs)J&'m^nmip_(aJ2?s7lWms/[%Q+)LQKq>0pbs8W)prr;FaXS)Ag +s8Muss8Dflpo>>\*Z_C[*Z5q<-?82Dqtg?mq#16ioDSaipAXmf(&n..qYU3es8)cgcEa:;T:`^R +p&=shrrW,pr;Q]mrri?"r;HVis*t~> +RK!9crqlZo?iL*'qXipdL0Rtt]YDJ0aMc'<`lcNKaNM]F`l5j6_S*P&bJqiMa,WeeM:U>&`Q#[; +dE9YPAHWAL%1bFR_TL!D`Po[7dR>,k&6-]c_8aU3aNhaB&J>Sgc,n&Nb\tJ@_9pNUcbp1q',M0$% +LdO-aMl08`l,s@esHjV&/`R;cdgXgc-t+heCW;I%LNOSbLk=abeqSW"V;Y[bg4JXc-Y%jbfJ)Za +=Dtgb0\JW`5^$Eb0J.`!Z2[bQoYch&e>XV_pHNA`PfF,bf01p!li=(rQP;arlFuVrQ,#]'$/)/_ +mmCcMf<')>\/f,h=1.5rqZZjp\k*qqY'RRpA4[ds8)fpVuM8~> +T`55qr;6us8)`p$LZs5gA1dKrr;loqu6]rrVZXFs8Dors7l +QN$sarr3Z2qX4:Xo":cnX5*aTrVu`lrr<#srW2usq#=.eq#BlR-mKl],$"@7s8W)rpAb'eg*RgV +,HgY6rVlcps8Dusqj/3>)_CX]rVlirs8;[a*#T[)r;Q]mrr;onrql`nm^=DV(`jJ)-KY1)rso&. +rr;rj-lsEPOn\hRoD\b\rr;lqq"*en*Z65Np\Y!gs8A3/)Ant4o`"miqu$Eks8)W=,pFCqp\k-j +rVuorrquRo(E=A:+!;IB=2sk-s8DusrVuoos7uZjs8W)srVHNirrE&srVca/q#']oTq@sJT#UR) +s8Moqs8;fhrrE&tr;chms*t~> +S,WZjqY9dZqu$J>q#0U+LL""nR`3:bb/D<=a2lHKc-4AR`l5j3_SX++`l,pLbBXF'&/c-FeBlCZ +`6-0Lb0%%"%h9GCdEg+bcd'nTa3)na%LraPaOA2N`l,pAei"-S%\(hf`Q-'>]uA:AeBZ$u$k!gm +'a>5[_oKa6_Sa=;cd,Vu#S%VubJ_K:dE^(^cHF_`d*)Os%2`d2da,eJa(u>#(6Qtbb0/)Vd*Kn[ +b0mEu!s!&Uc-=;LcHjVR`lU4D()Rf#(_RENXjG><_8UEkqYp?pqY'XXqk3t>~> +TDo#jq"X[]rrN,sr;R)PjPBS1rVQ3drVQWprr<#urVukGs8Mroq"F^\Ppdmq+^0gqu? +P5c'lp\FXDU9(34p@nUbs8;fnrs&H"r;Q`prql_&qt^9cPU7Oh*#]J,.:W;Vs8N&os8;or8/iZb +JGo +UAk;nqYU6k"oJ)gpA4ads*=V=l+gtA;L#FXaMlBAaj%iEaND`NcHaPN_nj:/_o9L2e'`CM%L3X_ +&.oaKeBZ(Qd)jSUdF^2b)@1-5b0/&Rcc3uOeA=6a%L<-da3VrJa2Zce6W@j&dc4rb/V< +TDo#jp\Fgds8W)rrr_TEin+20"8_clqu-Nprr)fqrrN)trr"_OqYC']Qn'C"+s\0O-7'BPbP(l3 +s8Vrps72'r*?c^%s8;ols8Vo&E@E4W*@>Ons8W)urr2gD\g&CE:%\Jbs8Mumrr<#qrVQWcCa1&E ++i=$Squ?Wpqu?Wps7H-9l2LKQrVZ]ns8;iqrV6Edl>(-D/T(Faqu-?G,:+?S,:"N^-SJ)/N1\GC +RjfF)Pl1O\s8MomrVucppJ)\Q+X`fSs7cQjpAb*kqYpNko`*qMrr3H,q=jRVq>^Ens8Murr;Q^" +hqd_]i9gOD"S; +U&P,lr;Qcrrr)cp&H;Uh];2F*p\Opir;ZWks7uWls82rts8Dor+TD<9rUcC(*u,e<(E+A;&L,Ho +r;?TpqYpNaLE[QU+nbj7)YsF5p9ABX*uPkU'es8N#r! +UAk;lo_/7`"8_`frVlg5rVQ +TDnulqu$Bls8Mus%dVBkk4&ENq"FU_qZ$Torr6g6s8W)sr;Q]orVZQgpR@D,9Xr$q"F^$E[i4Y+X80[D#"&0qtpEks8N&[.3]c]mJd%`qu?Zpr:g6krr;ll +;'dbjr;ZWnrr2rqrr;ipqu?Wn;#gOpqZ$Hgs8;oorVuZmrqlHip%n^er;Zfn_HAN:.O$,c+s%pT +,U+Ng+^Hnrr`)f +q>C6l#5?p6nDE*jrr3)trr2ioqZ6WoT)X<~> +U&P,lr;Zcp!<2ut#lX]!e#]@L_=[[*=o/0spAb0lrr;urr;?Qns8W)rqY-C'*>BJ4*#9P/)AO;6 +D#+#/qu-QnrVt[u'cnC4qtg$b]P]#6*Z?"<,\ZlLr;-U6jrr2rqrr;ipqu?Tls8N&ps82Wlr;ZZms7cQkq=OXWq>^Hns8Vl.;^`Y4*ZuXG()e8: +*?ZgL(`":)q>^BkrVm`6s7b4h*#BRJqYU6kqu?WorVuosqZ$Toqu66fp\t3m!ri/sqYpWls7uWn% +fQG*n]RVqUn"F/pAb0joDedgVuM8~> +UAk>mo_/7_rrW/rrVlh?qt014Xa+DiH*5!(aN_oLdETPM`l?*EcHXSUaN2KHc-XnhJ.iM)(`4,+ +*uH.9'*q.Ob0@uIaiDNQ^^pnk%]Rq!_TeBD&eGlo'Fk[Idalmka32QIaNqU2'bV8*cc42H`P]j@ +`QlKBaj/R!%hpeMd`Bf7b$2m,ai)EDccjVZaN_ZHa2Q?Db/MKMak,(ldFZOab/1p6bL*.T*YAtd +"UktV(DdJb$lTcp%M"iMe&oVL`P]U1aiV:0)ANk&dDsGVajJ5QaiV`I`66HJb0'\(p;R0Q`lS/% +rlkDb"3AL%`W!jsaNDZLbg+;Wa32K?aMZ$6Z@l4O=BK^IjRi<@q>($nrVH +SGrckqtpBmrr)j'nC#kCq#CBns8W)r!<2rs55PU?kqu$HiqXg=(,pFBN,9%pR@j<-A)(6cT +qtg?mrVlcppJ;nS,C9(%L+">o*[)UL,)=M:s82`nrr3E*r;ZOc+X8>>rr;rsq>LX!rqYa0kkk50 +rr;p3r;6$;\tjsh/g_Jj,U4K[+as8DlqqZ$Nos82cps8;NgrVZ]p +s8NZ0r;6 +T`5/orVlcorVomss8;`L_5=HPiVr`Hs8;lqs8W)srVu`os7uZmrVlisqtfoR+X%jD(`jG9+($'/ +*YftFc2.;:s8N&trq/r'*$9Ct`.B6W*u>h4*$'[Uo)JR`rVlisrrr)lr +rr2lkrqud)rr)lns8Dutr;Q`rr:p6ks8N#rs8W)ur;ciprt550s7tEZTU_UL]BoFos7uZlrr2ur +qZ$TpVZ2/~> +T`5,kp\=[art58,n`.';ASHI]]tqJ1cd0hVrl,/`ai)HJa3+D'>KY:cSH%H(1u0mAIAqZ%hoQd$"g0[e]c:_cH48O`6?IX&/Gn@ccO5LaN2NHaMu3< +aj&2M[`6kDccs_Ybl5cbaSa$ZaND[(bQ,faa=#-ScdC(`bfn5L_nj=8dba2uH=`6$'>aSX-[ +a8j6YaSO'ZaT'6k`Pf[:c,-&j;GL8+Nm?_SqYgEorVZZtqtTs`V#Pr~> +UAk8np\b$mq=XI[rs.]AfAci1r:p3is8;ip:\t+fs8N&uqZ$3cnX_3J,p+!K+WE#&p@k%!*$ZCH +BDql-rVuoqrr'#D+rVmQ+!;UQ*?c^R+JIcjr;HQnrV63ds8MroKI.ocL&1iKqtU0irr`&ns8CXM +(]XI3rr<#oo_/7Wq:`i[NbD-5.S@JUr;$ +U&Q&.s8Doqs8W#ms8Mum_5a<@li6\Ps8VrmrVl`p:\t+fs8N&uqZ$9fnXV$C+W;%8*#C)np%=[l +(EOA7B)DW*rr<#srqri=*YftA*?#b=()e56)P#XXqYU0irV63ds8W#oJKPsOJbf?Fqu-NmrrE&t +rri)ms8;ijrql`orW)orrr)lprW<-!rqud5r;- +U&P8ps8)Nfrr6g6rU]7-QZZeST>%hKbfn2Rc-48J_o9X6cGn/Nb0S)Sa4&:a'F,Hj&K)>l(Ojuk +Kb4n6%M27\bf.N>`PKXBN=?U7*!urY$l06c(`!U;^spi]c-aYO`Q6`s2Y2[rP\fWb5TUPc-4>O`l5s;aND`NaMuBF`l#^6c-Xtmf[e?e`R*,i +^SuTo7PGJn32G6Rc-!r=^VRn1cJ2G9&eT34`P97*_oBd?rQPSkb/hZHb/h[&`Xp2,a32ZGcH*o? +`5]d:b/VO"b5B?Yb5]Q_`r +UAkQ!r;6?err<#trr2p"hV$02p](-i56(QZrVHQiq>:&q +T)TQ&r;HWps8McLWhc?(o`+phs8VrorVlfos!msFrVQWmqu-Gu;]Zl!(`FJ6-(OT$rVQ@5*>ob0 +-JJLsrr<#rs81gu*r[)h,pFTQ*@):7*?c_.T]#P9rr3E+r;ZfoZ6^V8;>C+irquirr;R*$qu-Qp +rVZWns7lTls7uZjs8W)us8N#trri?$rr)fort581qu6?fqYpBcqssIRqu?Zpr;R*&qskaB,Tjer +rqcZlrr<#srqud,r;ZZorVQHks8Mios8Dutqu6Tp#6+T!qu$Bkr;Zfrrr3T-rr;uinu&LZSZ&+O +qY^0grgWq<~> +R/]nUh6Cj,@Xtc!a2lTM_oB^;b0%`A^VS(6aiD9?`Q?ETP<'-a(_RMs%1n!3`ll]X$ka9k&/DOp +_SjC4^rFL/#SS$l&/H#u$4Rg['`Jmr5_Ztq]?&.Bb/hTEbDZi<"u+3Ac,\)RaMl'7`Q#p>A13CXMA +UAkAqq"FL\qu6cdf&,Qerri8ss8N#rs8W'rr;Q]qrVlcqqt0il9dpc!-5n$R,F/3'q#C3foTgXA ++s%r.q>^Kks8Dooqd(Kg,9J+H]Ct34/gqkb*@2dT^KjrWiK&qYU9i +r;YIL$N0l&s8MrnqZ$Qnrr3<'qZ$TjrVu`os7lQt_(-iQ-hmZ*rX]&.s8)`pqZ$Ths8W&rs7lHi +"8qujrql]trqu]nrVllqrr3#op\t0slf76oleVL9rri2rr;Q\is*t~> +Sc9B&r;?Nkpr&o)ZetNWs8)Qir;?TnrqudnrVlfrr:g5r9ICDl+r)+B+d;g#qZ$QooopI7*#K`q +q>^Kls8N#qqHY3]*?#u3[dr7#.3]ZI(*4>;;N]2ps8DrsqtfbQGe*2=q>^KnrVlcq#QFc&qu$Km +r;-H`rq?BlrW`?#rr;upr;HX+rVZ]pr;Q`rrVQWpp\b'grr2utr;R'%r4XI!(F&SQrr2fprVlfr +rVl`p&H;_+rr;fos7ZKmrVccls8W#rrr3#urVcWorqufrrY>J3rVuonr;Z]o]r%NpU7g'3qtg3c +SH"*~> +SH&WgHMdErRV#iai;0<`5Ta?5nOkQ'G=QCf>(f0((q)c'FbR/A!k*XaiMTCb/5Q/R)[FjaihrRaMl'7`lQEG +_SX:0`llB@rPn]Ts2t;]s2kGb`l5jp_#_Hmrl-P2bg";O`lQ?IbJqB2]Y2D/bg=eba25sCaj/AP +_8=45_8!h(`luo('bq)bb5TTlaMu3:_o9X:b0%g)`WO<)aN4>"(<=M@aNW&ReBYqQcc=)K^rFC8 +aNDZLr6"uZs2P&W*5fb7bJ_9:`4s=:_7Xeh<)R"7WnQb0rVccnqu-Kn!rMimVuM8~> +UAk;mo(;eY"4l/co)AXorVuorqYC$erVuoqs8GglrVuons8,M;,9e6O+rVqupAOm]q>:$cnPUMI +-64J9qu?]ls8Drsq4o&R+!KeGq>UEkm^d$[,UXo]+XSdSbl@_=s8Vrqr;QHjs8;]frr +T)TQ)r;?Edjee`Sb4,H0p\t3mrr<#trql^orqu`ps8)co:*h#()]9\6.@9c-r:g-hrVc7_*>9P4 +,cghBs7u]nrr;bh)]BS6j8/KJs8))k@jE*<*Z#_<.V?m(s7u]pq>^?ipAb0kqYgBlrr;uso)JOb +rr;uspAY?rrVc`ps8W)tq>^Km$i9i$rVlclq#C0erqud*rr)VE*#0K2qtp?jrr2fprr2osrVl`p% +K-5*r;Q``p&G'hrVcZls8;irs7QBkrr`9#rr)ir&,c>!qXUeSUnOZbkk=fJrLNt=~> +TDq=Vq"jj^lH"3\C5&b(^W"UJc-+8N`504'_8XF6aN2KGa2#pC-4(+p'b_;j'mS*Vd`08E`6?If +'G)B%'njQKa2Q9Erl?sK#Sn9neD\m&e&oRq8eD[S*#K:t#sFd*b/;NNajJG`b1=hU_9L6G`P][9 +b0%rK`Poj:a8 +UAk;mp\=[d"8C@%h>R?[qu?Wls8Vohqu6Tp"9/5trr2phqYp3_Kd%BQ+LWprVulq +rr;oLrr +TDoK$q>C0YeYB%BhXgdHq>^9drr2utqZ$Tprr3N+s7Z5A*u,M0)]aP#pAb!gs#0TJQS0F'*YTM> +iW&lSr;ZZos7ke1'c\C%p\+I`r;HHfl\cW%*uogSo)JX^s8Mior;-?koD\dhrr2rtrWi8ts8W)u +s8Dcnq>UBnrVl]orr**$rr)fprr)lsrr +TDrm*n+#bsZ>aMkS@-2I^=(0Fc-XYTaMl!1_8XL>bg";N_8X46@gibY(_m[$d`U"[d*C+Yb]+C3 +*#fG&%^jO!`lulUcHX;G)%RT(T[q3"_SF4:eBDq>(*"$)XNAo3_9:!AbK.]Ge&BVQaiquMrl#2b +b/_WH`l,j:`;.FPaSsaN)HHcG\2W +d)l:.(rjP6`lcNMa"%E"'84cRcd0hV`P]U4`lS/%!m&C$rQ+rW&')l +U&P/krVc`uo%Mjbrr3H*s8W)us8;TerV?9drr2rtrWE2ps8)`p/)hfu,p4EJm/$\WrV#pUPq+-r +*?-"C.'`gPqu$Kor;ZNko2@.X,B3JYrVlg.r;6Bas2;eQp&G!jrVccprr)j!o`+jeq#1KtrVuop +s8UaN!rW#rrr<#t&-)Y+rr)imq"aserV?Elr;$ +U&Pi*r;QWkkJO3t`VKB/qu?]os8W&nr;Zcqrqus!q#C6js!cnL*?Q1B($tkurr;ceo9CdL&f29" +*[S)ks8;forqcWgs7B/9'd%>fr;R?*qYKja_8#j=s8;oqrVuiqrVm#ms82cprr2oss8E0$rVuop +rr;lpr;Z`prVuWkrr)lsrr2os!rW#rrr<#s+92?1-grVHNnrVulss8MuqrqcN,MbNI7 +s8N#qs8W)trVuius8N#prseo+s82ZfJf>bAao;/6q>^Hno)AplrVuorq>C6l&,c>%oA6XFSXugo +q=FL^rh0:A~> +T)Wa#j1gLaB7de4`l6'?_oBg:_TC*Jbf\#HaNDcPbeM35aO&Y['H/29)$A78a2,s@bft2=%ga$g +'bq@BccX/Hdaumn_9pId*"N`ddEfVC_p-BKb/W,+T\%30cI'bSccaPS`lH6;f#u"Nb0%g)`WF6( +cMkun`Q-0Ab08#NaN4;!rQ,#Y!l`1#rQ>,\50D*odET\Nbfn/I_o9^?aMu-;c-ahX_o9L1_8*n4 +`Q,g5beqHA`l?6Ic-42NR;=V%cHOMTaMl-9`Q$'ErlkSgaMu6"auXPM@OeB>l2 +`W*sWaSNpUaSa0saN;`RbK%]JaiDB=_6obD;+jl(Oko6trr<#sU]5i~> +Y5eM#qu?]qqYpu_f\Glos7uQlqZ$Tprr61#r:]p]r;Q`rrqu]oqu?Whqtkpe*??(EJ+rp=p\^L* +.iTER+!N!V`VB<+s8W&srVlimqkkS\*[g^^qZ$Ejs8Dutqtg9jrVlcqrr)lms8;ims8W&ss82Wk +$NKu%s8;lrqu?ZqoDSafoD\Rcs8N&u'E7t0s8N&ts8;ops8;lor;ZZlr;HWnr;Z^!qtU-fqu>p[ +&-)M)s7u"--6sc^B@$POrV- +U&Pc(qtp*;Xe2Q5pAb$hs8)`pr;QZp!<)os./s2Drr2iqr;Zfnr;(j](Ddi2Ie`sBq>?[)-l3XA +)BBtF`;09,s8W)r./j2Ar2(MU)C>(Vr;ZZns8Dutqtg6irr;lpr;?Tjs8;los8W#qs8;]l$NKr" +s8)]oqYgElrr2Tjp\t-is8N&ss8E-#rqucps8W$$rqufps8Dcn!rW#qqYpNp#lXVnqY9jcrr2iq +s8Muqqu6Km&,Q8)q!'(K*ulG5bPLr1rql`qrq??lrr)j6rqucqs8Moks8Vurq>^3(USjZWVs!dO +rr)lsrqHEorr;u(s*t~> +Z2ak&o)BR)p>`8r;H8%K`lZQS`5T[2`Q?9EbK8#QrQ>/a;96qt`5]^AgMI`E)%mTld)j8Lep\#F +$kOBp)&3UXdDj#EbKeS`c-OG[M@UC3%a<,0_S=(6aiMNCaNVlH`QHKJ`llEK`koO4bfn>W_og.# +a:HG;d)jJVa2c<=b08#NaN4%o!m&O,rlkDbs3)_4cdC([`lcHD`P][:b/q]Ca3W2Wai2<:`Pop< +aMu6B_Sa=ka:HG;c-4>Pair)_e'#tXaN"4u!li=*rQP>br5efU(sBqBeBGf1":GVJ:9`EucHO;J +`Poj:aN44ts2b5_rlkDbs2c/&dF$1Z_T9g9aMQ'3G#VI<@ +YQ"V%rqm?,s8W&rrqcQjs8Dutp?CN"rr36#q>:*frr2rrrr)cdrYOrM+!MmK.dHd&ZVDSa,oe$N +,Uf&VqYg6h&,lD)p`gn-,dRIFs8Mlpr;-Ehs82forrE&rr;Zcbs82crqtg3equ5p]%0$2%qt^'b +qu$Hnrr2opr;-Qnrr2iqlM_:ks8N&qK-_Zc,9\B[AC(5Qp\t*g!ri,srqufqrVZ]prr2utr;R2h +j5oRhht-mJr;6Klr;Qourr2rqY5a"~> +WW*P/qu-Qns8Dror:7Y!S?_/Prr<#trr3#us8N#rs7lTmru(h7l6@)_)&FR_q"V-f)]9V/(`FJA +PkXtMq>Lp'qY]t,)BL!)s7u]pqZ$KjrWE3"rVZWmqu6NlqYpNnrqlrsr;?NmrVHNjrrN-!p&=sh +rqufp"TJAurr)iqrVccqq#:HqrVlfks8W'&rVZTlrVlfqs7H;1rr2_E+rq[D)'0i*b52#6 +rVccqrqZTmrqufkrr;p1r;6?,X.l5\WQt8O~> +ZMt")qYL'g!<2lq')_@VRVPi,N3Bn:dF$4X_o9X8rQ+u\rQ,#]rl?,"`Q$!Fb7G@-)A*\.d*S": +&fDc*'bCc]GLY'hbkfBkai`2d$P!jZLWmQV`l-0BrlG)]"3/F)bkoQ^a9Kf2bf\)Qc1fH^b5TK[ +`rF$R`rF*Vao0B\bl,W_`WO3"aN4>"s2k>]qo&QTqoJTUs2YPeaND`Oc-4>QaN4A#s2b,\rlG,^ +!m&C$rl#YjbKA,W=o\U9()@i+;RY?0bK%T>_oD\ss2P&W!li=(rQGSjaMu6@bg"DVb5KBkb082S +_15rm:02;,h=LIArrE&srseu+qtg'arVuorrVQKj\,Us~> +YQ"V%rqufr!<)lr!;ZTn"687rpAY'oqt^'ck5H.lIjlH_+shTB:+.,-*?6RV/!p88q>L'es8NN$ +HmKaYGl7U:s8VopqYpBls8E#rrVlcqqu63epAXser;H`srosFarqu]lqu6NkrW)lpqZ?]prquf] +rW3&qrr3>ADC6SR)BU1N->R`\rr<#eip>a`mf!.lrqcTns8Mljqu!SrJ,~> +WrEY0qu6Tps7u]mp[GM`T=s[grVuosrr;Ecrr3r:pNn)#(`FH=Z:uV_*>]A6+"!6Ms7lNjrVlfo +s8W'-rUY7V(*Ii!s7lWoq>^9ir;HZp!<<#srVcWmp\k'iqu6fus8N&tqu?Tn!WE#rr;cirrUKm\ +s8;llrr`9!r;H$^r;RB+s8N&5CEjc>&f2K/+CtBIrVccqrql`krr<#krr;p.q#BimS"ZLSb4#-* +rr2]ir;Q]ns8Dr's*t~> +ZMt")qYL*h!;ufq'Dqh)lIV#5?#Z@7]#E"Eccj;C`ph(Latq&V`Q6>4(_mo*'P!F`'G)9((a0V, +Fk,$bcHjbXb/VE<`Q$'Gf$n%9#RX5Eb/2EG_oh_p"j4p/aNDU$b5BH`b5KQ^ao]Z,aS3[VaMu<@ +rlFZQr5]5baMu<@aMu<@aN"1trPnfWq8r]YqSrQVs2slQs2tA_)TKb8_oT^=cI8.O#RqU^'c@l% +9@_8,`P]U5rlG,Z"i\Bu`lQ=%bl>fgaMu6@b5TTbb5KBsb082V`5\lp8PiE$Ra:3ms8Vuorr;us +r<)rnqu-Norr';*J,~> +W;ckoqYpZEe^cIQ!rVZZm +s7uZos8N#tquHWmr;HWirr`8uqt^6d!;l?br;QHj!WMuqq>U9jr;6Hsr;6Eirr2oss8E0!qt^'b +qu?Nlrr;6^&c_V*rVl`gcXeOT+=8*YK`1l(rr)j#o&&TskhlC>!ri)rrr3-!p\+C[r;QlsqtpB% +s*t~> +XT/;!%fcD*qtg6dpYMR?VpG)9rr2usrqlfrs7-'gs8DrsrtbV4koUcb)AsY<*YoS5*u>rgm/QqV +s8;lort#,-s7j)F-Wm>Cs8Dlpr;ZKi!WW/qs7uZmr;ci^s7cKnrVlfp!<2`mqu6Qor;QQm"o\As +rVc`os8N#srU'U^rt+r)r;HKab[;Y@)B]hBJc#EEs8Dros7-*`rsS]'o>d\tT:r%No_SRes82lr +qu?ZpZN#F~> +Yl=e'qYL-i(\mt'o^qY7[r65$NjHFfA$$k3OS(E!Yk +KZVff_op3Fbf\)Jrl#GgaNN&\R0+$_HGdnG`lQ0BprEZZaMu<@aN"+u!QrL_`W4!T`r=$Z`q@CU +b/h`Hb4*LMapZ;5aNDTHaNDTHaN4A#rQ+rWqT/]Vrl+oWs2b5_!R/^VaT'E_a8j6Z`=gD2c-4S_ +c)XGM((M#b%q +Z2Xh'qYpQnqu7&pg=t-Ws8;idq>:0gs6BRrs8.Ho*$6@I+!DdP+X2s4pAb!fs7lR(r;Z]lla>aO +s8;fpq#:U9hrql`ns8M]k#QFYuq>'g]q>1!cr;ZcnrVulnrrN,prqQNhs82ios8N#t!<2uq +rr2lrs8N&r!W2fks82cqr9!tiqZ$Qpr;ZWhcX\@J)(ObHrS.;^r;ZHis7GL=nEJs,s8W#orr30$ +qtTs`rr2p#rquTfrjVoX~> +XoJA!&c2P,q#'s_d\`h@nbrFdqZ$QmrUBgds8Dp2s8N#nIiT4>)]9S8*?Q=EMWj^4qY^Blrr)j- +rVuckl*K:Fs8;fpq#:U*fqu6Tp!WN,urp9[brqZNgrqHHmrr2irrr2rorql`prVc`ps8Mut +s6K[^rt,&,rr;lqq"Dm=+s.OHj8AlSs8Drps7?6`rt>;1q#C07VjX!BVU#)=r;?Nkrql`prj)QS~> +YQ+Xt!<)oss8*T-n*fArXDMH]Z,"9&a3i;\bf[rE`Q%Jis2H8*bf\#HaNi7M&J5]l&J>cj'FtUl +aj&DVaN;HE`lcNJaN"4u&&cW6cGGRTf$D@UcbdcJ`qd^M`r*mY`l7knrPe]S!6F`R!6FuVq8hpF +rQ+lU"3&:!`r*gU`rF-[ao]f0b4`Poj>b/h[%`W4*X +b5TK]b5]Q\`rYY@s8W"qs*t~> +Z2Xh'r;Zfrqu6che)0`qrrhoer;?Qnr;HZ\rYPJ0kq",%-QjHP+:3i!<2fo!ri,prVQZpr;QcprqZNis8;ip!rr9!mem%_o`#*or;6Bh +rqucsrqlEe!rMonl2Ch`s8VlnrVuon"k-m7K`1l'rr@@ki;U?rrW2trr2p"r;6Bh +rr3*"qtpB%s*t~> +YQ+V#"oeQ%rqcWnrVS`*+s8;osrr)ir!ri,qo)AXgrVlis(]4'j-QX'G(`=53+F`\KrVZ]n +r;ZZkrVc`mrser,q=t!`s8W#rs8W&rqYp0fs8N&s!<2`ms8E#srqlcqq#C3imf37brVc]ms82io +!WE#qs8W'"rVZWcrrN-!mJd+b#6+Q#s7lTmrr<#m#16a0K)GTHrr;rr!<;clrr;usnGa$tp"lX@ +R\$G*qY:$gr;?NjrrE&'s*t~> +YlFb%!rMikrr3c/p@.A'Rq#lQZFnB%dFQXgb/hTDb0':r-H=$Eb08)P`l?-Ja:o=#*#05)&J&Ho +b0.fG_SF=/_8OL?b/hTBrl+oX%+32>e]>tV`lQ9Ha2e"s!64fQr5JcV_u@LP_uI^U`r*mXb5'9[ +b5B?O`r4!Wb4NpXb5TK^`rVg_84"0aj%o?Tk.;# +Z2Xh'qu6Zqr;QlOiS+80rrVWbq>UBrqt^'brr;9_')hk*JfZ$Z*?lMSl1t)Qrr;lqrql`qq>Lp) +q"Xmequ$Kirr)cps8)ckrqQEkrqucrrr2iqqu?Torql]ur;$0cqu6NnrqZTkr:'dr;HNWrX\l's8W)ss82ims8;c3lhUMY!<:pT"8`&ip\k*qp#Y>r +ip6UD!;l]o"8r&nrr2rtquH`*s*t~> +YQ"h*r;?Qos8N#r%I^s9Vp=o6s7u]ms8;lps8VusrU^$fs8Drsrser,p3mhr)\a8/I-:>$rr3#t +s8Mrrs8;lps8Vm's8;cls7uZmr;Zfns7uZfs8MuurVlWmrr)oqrVHToqZ$NnrVc-`rVlcorVZZl +!rW#rrVuos"9&/qrUKpTrr<#urXer(s8W)ss82ims82Z0lM1AWs8Doss7lT[rtYJ3r;Q`oo^0Dg +T:`$oq"Odfrr)cnqu6ZqZi>O~> +Z2ae$s8)`p(]*acbD^XVMQX_2_:6fZbfIcB`lQBJb4E[n`l5p>bfn5L`lHTU@gEY`'c[p,cI9kS +_ns./rk\]Xa8X6]aT'6k`Q>m1bJqNBdDO&G`lcHBbPB3a`l5s:`l5s:q8NHS_u7RS`r4!Wb5]]_ +b5KE[`qdaPaSj-WaSj6^bf]q/s3:Pes2t>^s2OfPrl4uZ"3Sj3b3d:M`rF*t`5^0Mb/qiIcGn>X +cBc,ab/VE>ai_iOaN"5#r5o5b`l?!8_o;>hs2G#U&&$'%b1+SIYBNuB +WW*1rrr<#Ig"-0&"9%rnqu-O!qtKdYrr<#`rY#8%s6VB_*%)^TbOPH/r;Zfrr;Q]srquTk&,lP. +qu?]qq#C^Ko!;uins8;oro`+mh"TA5rqu-Nk!W;ior;Q]k +rrN,sp&53rr;$-`q>:*frW)lpqs"(^s8)KhrVm-"s7lTnp\t3lrr<#Trr)lsp\t0skMG1Yj5'Y/ +rrW2triuKR~> +YlF_%s8UBlrVcfsrr2Zlrr)lprr)lrq>UNqrVc]ms82cnrVlZl +!<2fos8N&srqlcpo_nmkrqZTmrqufdrr)lqrWE2sp\t-k#lO_trr;`ls8Musrr2rtq#:'gqu6Wq +rVd0(rr<#oqu?]l`Mrj/!hn-:rr*#trr;urrrE&)s*t~> +Z2Xn(qtpBkrr3N-iQc(FE4*%2?RmJm.]o_A:\ +r;Zcp[f:j~> +VZ-_?g#N&2"9&/lrr)j#r:fs\r;Z$\(]==3pmWQO+s/"=rV6EhrV?Kgrr;uqs7uWorVlisqu?[$ +qu$KorVlinqYp^Kls8Vrqrqucprr:gRrVm9&oC`(^s4lbtjkTb.s8W)(s*t~> +ZN'q's8W)ts8NZ,l(t2\nbi@_s8W)tqYgL$[p&"I_pAk'hqu?Qo +qu-HkrVQWprr;rsrqQKrrr)fprVlfps760es8Dp-qZ$Qls8Vrqs8;osqZ$Qmr;Zcqs8Vooq>^Ko +rVccqs8W,u"o\H!rVQKjrsA]#or"RVStk*1rr)lr!<)fp!<0D+J,~> +Z2Xq)qtp?krt>;*kJV=IC9tk&bKeARaMc0=_u@XT`pq+m`l#^8b08)N`l$!Ac$Bm7)Aa&ZfuV7H +]tDV.bg+JUbk]?[`sp/2_nsO9`Q$*Ea2lNDq8iKV"j"j-b/ha(a8jBZbkoQ_a8s<"rPSWNrk86B +!5\9Es1n]MrPAKNrl+iU!6G/^rlFWPqT&ZZn]:aM'ue24aN2NH`m2ZEaMks6`PBR8`6?=$`X'W, +b08#J`lQ=$bQH#/`l@tr,f@UBbg4\accjJM`lQa3W)TaMPHl@9m#@DSY6$o_8@]!<)lr +"9&,orO;fW~> +YQ"t.s8;ZirVu]De+ik@"8qujqu$KoquQ]mkPbtis82I7,p=HR-d)A_s8UpSrr!N0rVcThrVQHj +qu-NlqYL-hqtp3gp]13jr;ZchrtkJ'na#>sio&_Nh;$fAh;7,Lio]Rtq"s[_&cV\'p\4I[rVc`q +rVZTns8;`erri?$s8Dc8rWN9!s8Vrprt#&-p?gi6qu$Els8Dims8Dlq%/fqss8)cqhqmVXhu3QS +!WN#qrri;urr;r*s*t~> +Z2YI9s8;opq>^0s`o'bu4lJg@=_n`jjXf81oU8"?STV8*T +VP^ArZb!rjbgkV6lgF<:q"XU[r:g3orr;rprr2p+r;HKfqY^ +Yl>F9q#13js7+mDCL`jF]ZA=Abf\#HrQ#2caMu3:_oD;h)TKb8`lcNLb/VE;ai_p;()Il$&?*jk +`Pfd?rm(Vib5B9ZaSs?\a8j6OaSs4mb0A2Rb0A&Nbfe2Tb/hZHbfn>UbK7oNbK%W@_ns7,_7mRo +\@/Q5R#d,XIX#mPD/!TnA7T7`B520uEHZb_MjC$;ZaR?T]tV1l]YD;!_o0O4`5]j +Z2Xk(s8N#srrViBg\q-Xq>^Koqt^'brquctrqu]YrX]&)rr;ql*?uUJ+)gcNr;5CNrr3W1s8D]j +rVHKms8;opq"t*krqZ6e'E.@Wg=b*)cHt(mg=tB@[i7lu=f$Mdi +dFd=Br:9jiqt0ISp&>0nrr2rqao2AAqt^6k$MFH!mY*889>16%rVluss82fnrt58.qu?Tos7b.. +l/C_'s8)cprr<#q!<0A*J,~> +Z2Xn)s8N#ts835LU8loUC!rr<#sq"agarqlcprr;Nf +rr;cmqZ$KmrVuiqs8W,urqurus8;`mrsJH!s6P=e)Fh72r;HWtqu?NlrVZ`qrVmE(s7FI(US+9g +o(VnYrVc`"s*t~> +Z2YX>s8N&uqs`O7@qD%@cHNuEb/hZD`lQ=%bQZ/1`l5p:o>h'S`Poj^Hn%/p%tqY'XYqYL-is8Musa8QhPq>^Bjs8W#h@N6L5-miEqq"Xg\s8;cprr)j) +qu?Hjs8V<>l0I?`rr39$qYU0frqu`o[f:j~> +Z2YI8s8DutqW4fR[GLf[qu?]qs8Mrr!WN#rqZHcqs8VZhrr;oqs8NQ,s8D=7)]'82+3jQZs8Dlr +rqcZ^rWN3!rr<#tr\O?Ks8DimrVucos8W)srr2T]naYo"ah".6P)YH_Q^aJ8T:r!QT:M[KT:hgK +TV%gISY,bMr1=n#T:_^FSXuICR?N\kOd_rF][>d+o(DhTqYU0i!r_uorr2rt#6+Z&rqu]mjo55Y +p&=jgs8N&srr*T-s8Dlqs8)=p)&sVA,@L0>qZ$Bgs8W)tr=Ac)qu-Qa_53]nT;^oMrr)cps8KG+ +J,~> +Z2Xh%rr3Z/k.,G=F/$[taihlH_o9U7aND[%bQ,fUaSs0naND`Nb/V?8`m;jE',_Q!$Eh_%d/M?" +c-"#?^Ve%1`lQ6Db5BHbb08$(a>1fWaN;QFaNDNHcHF>Nb/qWDaiVQA`Q,g0^V77ZQ\]Kj?<:32 +?tB*2g_C2*W[-u!=)?];^V[n)`5^!GbK8#N +a3)TKbg4JTaiVQA_o:r_s2t;]!65#WouR*O*lZ.?b0A2Tb/M0?`5^'QeC@+r&/,oq8^bl)a1f[: +rQ5hq`l?!;`6H6=_l@lZ77^R$_s$glr;Qcq[/YX~> +ZMtR:r;?Has8V-6o`+sdqYKsdrr2os"8r&nr;?Qrr;6EWrX\o*r;ZcnR3*On+!>:ls81dS!<<#s +s8<)ss8;]jp&>j,p?()\g"bKMme$8Kp\"(ClKmm)m-X3>mKE"HmHs?@nGi%Xn,i"FmelMTlg4'= +n.G'RmI0W;naPu5kiL^Sf%fESq>Ug"rVZQgqY^9gqu6Kmb5N4Us7u]pqu?A1,97pO(Eb#'fDYLE +rVZWnquH`orsS]'q"asinE'$%iRe)/#Q+8jr;Q]orO2`V~> +Y5\n$rTg_^^?tmns8Vros8W)srrE#sqZHcqrr;Nfrr;rr&,ZD*s8MhZ)&aA2*GG3GqYpEnrqZT] +rW)usrr2lrs!@[Es8Mups8;Wbp@\%GcG$QBIDlO-?6C_<(+ZrV?Nlq#CBns8N0!qu#RTrVuNhr;Zfr +s8Drr'*%h.s8)]cD]]f9*>0>9;qM$Fq>V*,rVZTmr;Z]is7aO0TVA$QiqN?Mrr0A+J,~> +Y5]+&lbdaEFK3I'cHX>P_nj70`r='YaoT`2rm(Sen]:^L*6?:Hb/M?9aNW(Y$Q9ru$r#."cHjh] +bf7K5^;Kris2b/]r6,/`q8`QXrQ,#Z/]Q2]aN2KDa2Q3?_7m[u]""2eG%b8j?F?Yk1MYdV9_rkeoX +`lcKIaSs?fb/_THai;9bKe>_e6Wh%()[Mm&N@&EaNVZC +a2l?A`Poj +Z2Xk(rVlfuf\>9^rs/GtrVcKdqu6Tp"9/5rrr)iur;6EWrX]&,r;Z]npb*m2-5n2Zq#B=P!WW/u +rVuj#qXX1OrVcTm+8"LDg#1iKk3MI+o'c&:o(2MJp%%YBmdBQ6o^;/9nbr"]n*]W2mJcPRnH/4K +mf)SVn*KB*m/HJPnIP3_o^_SAmd')tj58M;fB2u1$3'bus8DWeq"amds2t +Yl>43s8LrRU9*k[s8W&ts8)`prr)fqs8Duq!<)?brr;us&HD_-s8;ff.N]?J(F.E&rr)iqr;Z'] +!WW/urVulprtPD.qXs7DlIWVAQBRPtQ'n5;USFL]Se%B`StD[LT:VUGTqn9OT:c(T"J26XSt>qR +s.TRgTV)4T"/DTaTDkJdStM^GSGf!#USOWUS!fV1P*;B?c/eEgqZ$Khr;Zfns8Dlos8W&rlMge_ +pAP'lq>U?ms8E#srr*`6q>U?es7Vg$*#]k>*uu==+3=KWs8W)ss8W)ur;lrrrr3?(s74U;TVnTV +h>%!L!<2ut!<0G,J,~> +Yl>43p>iSr?"L4Rb0A5XahdtqqSi`\b0A8]dEM1&!QrL_`>cn@bg")B`l?g7a2>a0]XPJ\V20,%?sm;J@q]RlC1h-h@U`hXA7fFd@qB1_ +Bk(X`Abp$=Ac?,\rl4iVs2t>^!li=&rlY5]s2I(<`lQ +Y5]!if&6&ts7uZoq"asgqu$Em$2j\tr;Q`rrqu]YrX\u-r;?Qlrg"`G*$ZeBs8V'W"98B!rVlfs +rqcX!qrQYae_]Qin0%;no'l&4lL"!-nF5o9n*TT6o();=mdfo +Z2ak&'DB>?WP/!&q#CBjqu?Zqs8Muqrqucsrr)3_rVuos%K6>*rVufnOrb;S+=1Xtrqufmrqufh +rWE3"rVcWn0E1hBqX2e;Um$h#Q^XA4SXuLHSt):AStD[ISXc.8^T:l1W!20=`s.CI*S=H1DTq7gHTV/$SU7e'JTV.a:OHuT[iV3C6nrr';* +J,~> +ZMtO8qX`O4CM%q%_8=LHaiV]D`r4!U`WaH+bg"ASo>hrpbf[oB_oBjCbK[l>`QHYR'bLla#t!1f +bfIlF`r;4p_8OF:ai;?AamdCMb5]Q]`r*pXb5]Q]`W4*Xb5]Q^`]D2`bfn5L^rOC;bh/^&*"s"u$qgs; +'b1[ueBc.T`Poa2`Q63S.<*X=BiUQ^Ar3QER~> +YQ"_&f[Tftrr`8qp\k!h!ri,nrqHWlr;Q`rkPc"cs8Muss7cO?+XA=^kPtA:rW`E%rVlisr;?R' +iR$'#gYqA`nac8?mf)VRn,MnTmL&IRo(2D?mdBQ5nG_hYn*oi:nc%qUmKN"Gmd]f9rpTmT$gmBO +naZ,;n+?5AnG_kXnF?#ImK;qGmI1#?#OUgBjP8AGq"t'prqZHfqY\M8)>a@4s81P&+<)@D-(Oh@ +-6XTU-?8bTqY^?krs/K!rV- +ZMt@4r:A@9UUElrW)oprqQNg +rYY\7rVlisr:fgPmanY3Q][Z(SY2^OTV.qVU&UecT`C_`SI2!^TqS*LS=H.AT)P8`StDXJT`1P_ +TDkD`TqJ(XTDtJjSXuLJTqJ$MUnaWVrh0CfUS@a\"/2B[U&L\rU8+KVQ]R?*_r1([o_eagr;HZ^ +rr)lmrquflrr2rqrW3&rrr3`/fI.XR)&jfhq6_Fe*Z?5@pA=jhs8Dros8W!*s8DrsnZSISR\\?t +rr2p!rql](s*t~> +ZMtU7mF\F]B99g?b0J,KaiVK>b08*-ap?56b/hT?_8F:aa=#-Sb/M35`QHHEcc*]@b/iaN;QD_SEgrUO6]a>[q;VBPD*kARt^Ms(M=B#\[pe +BkM!fA7baP$YQQAH--O?t!PUA7K(VB4PO]@prkV@q,FF"(>YIA,^!P +AS,Lc@9d)UNN9\.]uS@ +])Va.qu6c[db4/Vrr<#trquQfq>:3lrVcWj +s8VB +_>jN6q#C?m&H)79TVp'+q>UBnrVuosqu6Tm!<2WjnG`Cds8EN.qZ$Tks8V_Z->;N;rr2lr!<2rq +rr**$rr2rtrqcZlrXAi+rVZQhq=aCE^RUGcSGo2_TV).Rs.B:`!29Cbr1O+`rh0IhUSOc`rM9Ii +s/-.$WN*##X/`.trh^:*VP^2dVPU)`U7n8J@u+!>Frp\E#U*uu7@=m#V\ +rr3f6s8MusrqlZmq>0]rS>)RLc1V)7rr2inrrE&nrrE& +^Ae?7qtp?jr;RH-qX<+$D/>ibbl>fh`l#X,^W,ic*R)[F_o'I9bgOMN +`P9FaNDa+c2Z#fao]Z(`r!aT`r!jV`rF!W`>$/%[uG-1BPD-rD/3fs@preQ +@:<[FA-caYAnYjmC2%J^C'8nGQD%g`#BkV-kAnP[cAn>L_ +A7K+X?srqA)e*5`@:E\S?t +])Mp2qt^-grVluhf%'QprrW/sr;6KmrrE&squcurrVQTo#lO]$s8Muns8)!Z#l!*Gr:^0cs8Mlp +"oJ)iq>C6krri8us82NfrrW/srVHO'lIseCj6>gnlg4$-nF-;E*V&XfmdBE*ki1[_hqm>Mg"tQB +jm2F.pA=aes8DfgrrDurrtk\3q"XOTo'GT#iS`YQiT'(`kihL*o'lYH'_(JVnaQ&:n*ol:n*]`9 +mcN0Oi;3BO!W)]ms8W'$qY^Bkq>UBorquZorql]trqlTjo)9O*s7lNhs8W&tp]'er+X%mO+("m@ +s7cEW>Q,iL-R@.5rr3)ss8Mopr;?O$s8N#ts6JJ%kMu13s8Vusrr2otr;Q]urqlTj`;b>~> +_Z'cUiur;6!&V5Vo:rVlcorr)lsrW)orrr2rtrW)lqrs8T&rr;urqZ$Hhs8N#rs8Drr +s8W'(rV"q;rV6Eis8N!#rVZTjrVZ[Arr)fqqu6Wpq>^KmrVlcnrr<#qqYBU/YaCK9S=lOITV%gK +TV).R.%d*'TV%pOU7e9XUo(/nWjfRR`6-KXf\PKHk2bRenbDkOp%\=PlK/$-(?=?/g!Is`_7R(Y +XfSM!Unsl^T:V[Jrgt:,UnFEVU7n6QTqS3QStD[JS!0,:bNefqqZ$Hhs8W)qrri/tq>C6hs8N#s +r;cirs8W)ts7cL8rVuZjqu?]ps7cQaAKMs3*?&)Up]'s`l;nuG*$??Knc&OjrVuoqrr3r:rVc`p +s8;fpnAM#mT;0C%qYg +_>jN5"T.obq"t$i$i0MbfU-GkQFF]Ma8X:&aMu-6_oBd?b08)Pb0.cC_T9[7_oU!Fb/_HC_YV:O +aSj*\aNDZHrl,;db0A)5bL+AV`m+M(rlPAfb0%fHr5o2d`Pp*@_#DFnb0.iF`Q#m4^qmORIV<%q +A8,akB4kghARt^J!+Z";s().A+Co2&C27g0Ee&h!Ng6*+U8OuoYcb+7]>21p]thFpZ`jA46a!Jg +URRR4N.cY)G&qV?DJa6*BP1skB4PObAn#7[@UEGJ?XdPT@:a+a@U*8[PH_X2^;n(,aN;HAaMl3D +ai2WIb5TK^`r*d]`lQ +]Di$5r;-?frVm5ef\-'"s82ior;Q]q!ri,qq>Lp%rVuokr;Z]prqcHhrr<#_rWiJms8VopqYU9k +r;R?*q>:'err2rrs82]nrqlBeq>V!"gX+U7n+,o9nF,i9o()8KnGhqdnF,W+lKREiiS`e\me6/M +#6+Z&rr)iprr2uqmf*RblfI'_jlbdgrTG$bmHa-*n+-,5m.'Z:nb)PIm,?O?f(Jt@s8E<(rr;W` +s8N&nrr30$r;?HirqucurqlTjo):$8qu$KmrVZTmrqD[&-PmpP,2W(nqZ$TjpP'q6)_!)=oDA=_ +rV?Kirr2rtq>^HuqV^H"g?nn>!r_rmr;R!!s8W)rr;OY7J,~> +_Z'`;r;?NkrtPJ4qu6QioY7<'ebo:>s8W)trVc`ns82d,qu6Wqp\k-is8DcgrVlisrqcZbrW`Dl +s8W#srVc`pr;lipr;Zfr"TA5tqu6Tp1ALeIs8;corUf[-W0<@/R@Kh?T:VXITqS'MT:hdIT:hdL +Unjc]VQ-r5`6R-&o_8%PrqI-&qu$s8VleJfY[@+s3rVqt^9lq>C73rr)iqr;QThs7am=TVe9n +q"=R_rr;usqu6Woqu?Zpa8^Y~> +^]+K9qY'XXqu-O&q!6Y"DeYKL]Y;P2rlcD*`l5j5`Q$!Abfn5N`Pp$E]u8+8e'6"UbKA)PaSNpH +a9U&(ccsMUaN=;$1<@JTaN2NHbfn/O`Q-0C`kTO;bf.N@`4NRHDd6RWBPD'lB4bahBk_0lB4Y[e +B4kaerFmHlDKgSdPFo(i^V7M"_8O=4`l,d6`l5m9b/h]Ga2uKF`5hku*ll:>_T0X6_8*du]Y(PD +P(\4)EH#c2C2.EpASGadra7!]Am8\T@p`PO@qT=Z>?YZtWjK[b_7mh,aNVK;bf\#CccF,IrPeiV +rPeiYrlXcP)Tg%>cc*lA`lucVFpo)o&deZQe'lC_a2H29rX^1j':R=j]u.Y'cc3rC`lu]MaMl3A +a3(oH +_>aN7rr30!q>C9mq>LLp)pA=aerVulis7q5$qXjgPrWiK&s8W#s +rqZEf!;ucp%K?8%q#CBns7uNkrqZ?gru^phdG*[GmdKZ8o^qbJo^VD9lg=0/lg!fugY1WJmJ#fD +"TJB"rr)Qjs82orrq6:7qsih*h;@5Qk3;7#n*o`/nEfE*nb)SDnaGl2k2+\[ +_>jN6r;RK1oDJC[p8P!jm/$2Ns7ZKlrr)fpr;ZZn&-)D%r;Z`qrU^$_Itd^is69Lfs8Dutr;Zfr +q>Up'rVZTlrr<#ss8W&tr;Q^`rV$$_m`MGlQ'n28StDXGR[KY9S=5h6SY)UJStDdRX0T=Te)^&i +p%S1Squ$ +^],#Hqu?]prVuQho^CLXC2L,cZ,Y#%rQGPiaMu3<`l?*@rlPhobeD!4bfS;Zbh1g:16b3I%Q +bfA#N`luTFq8iTUs2b/](!"_Ha3)<=bfIT3]u7IHF'`9cB4kiUB-LntAnYdeA7]FhCMR[#DJXBA +K8c5"]"5Sj^;%V*`Q-$=`W!dg_o]s=`lH*>aN2KGaN)EFb/qd)aT'@Lb/;*6`5T[5_ns4%]=GMa +\=f%>H$""DCM7?oBPD-k@:s(W?XdSV?XmVXBP([Y>AB>*]"l;+b0%uSaMQ*<`Q6-CrQ##ZaSj-H +a=GNP`lYm0`Q-0KNsZI.,npBWdF?F`^;J.>d?GLa%291be]Q1[b0J)J`Q%o"&&u?&bKJ&L[[CJr +:1A4sm/I%bqZ?Qhq>1-krPnkf~> +_Z'l?qtpBjq=smbrrUHmi;NZZr;$3`r;QZp!WN#mrX\l*s8Musq"t*c==Yc.qu#^X$N:#'r;Q`r +qYU*arV-Bir;R!!qt]m`qu$Hn!<2rs%HYj_k3;F/oC))4lhL5RoEt*Um-Eiohr3hlpuh\WrX&W( +rqucos8Mror;QcrrVm3&r;?Eeq>:$`r:U((rUoI>jlPXhlK[U!mHs?1nac5?n,DVVl0%-]bh;dk +!rM]drr3-#r;6EirVlipn,=%$r;Z]js8W&th'a +])Mj2qYU9g&?2J4o(Dn[r;Q`qrr)cmrVlcqqu.-'s8W)ss7c?ho2I+`[JB^arWN)sr;6HdrrE#s +quH]prr`)sqtpBm%0$/&qrPJXPFnA:TUu.R,+G0lR$sS:StMmTUS4Ng]ZK*so(`"Sq>0scrVZWo +rr2flrqlcprr*]2qu-NorVZZps8Mrorr;rpr;6?gqu6Ek!W;opru(e3q"jmdpZ^8M[]l^+USOZ[ +TV8-STV8'Rrh0=arh0^lS<]58f^nn8rVQKirr`5trVlcqrr2iqnbs7&r;Zcns8W&tg*RaV*>B]p +oDeacrr3Q*s8)E0,9%XL'3"_urr;oqrr;rr'`\(/s8DiprRe3ATV%qSqtg +\c3ECqtBC3H"1]s\\Q(t`6$*Ec-4DSaSj-YaSsQm)TFLa2Gm9 +b0.j$aT'9[aSj7A_ofs;bK.`@]thIcL1FV+BP;-n@piYMBk:jfBPD6uE,]]4EI3P-XL5j_`59L5 +`5g!@aN;WH`Phbp!65#Y(<+52`l?'=aN;QF`l?*AaN)?@`lH-@rlI:C`l?!9^VRb(`504$\[fAd +\u_B\I% +a4.R-$4n*k)25uYbf@lIaM?0Je?9Mr',LX.fZ_dkb/jS%rl>nqaM,O4aN)E>P@$p\?ZFumqZ$Tp +p\Odbs8VrrrPeee~> +_Z'i>r;69aqu6Km"6SOro`"jprVlQ`s8;lps7cL's8;fis8DureKuOb+sZ4Lkl(ngrVuorrqu]k +qu6C"q"t*krqlNequ$KmqtpC3qq9EPj6,gumd9B2o^hSFp@RqEkiCaViU-L4rrN#nrqHKmp&>$k +qYpWrrVlKis8N&rrr2uto)B[+p$Cbtkj7^(lfm[!kj@j+lg=95lgO3#gXY?_rr`&ip\Omb!W;rp +s8W)rr:0b6rVuoqs8VunnPL;I,9S7[s7l?gs8Duls8W)uqqYX%+Y+`ah>ILm&qu?]ps8W)u +n`/`lkNVsF#Q4Jus8)Zjrk\Vb~> +]Di]HrVZN_oC0ir;Zfr!<)op!r`,tqu?6d./j2Grr<#rqX7,s)'0_=HN3d7s8W&tp](9m +s7t>P)&jkA,2)ejr;QWos8Ei7r;HWms8Vogb,V2/Wk7s$q>^Kmrr;iorrW2url"he~> +\Gm'7jjeTaCm;cQ^Ve15`mOb/s2t,X'#r,5`l?EQf\*nK$jH_4][G'Kqo\KN#0+R%_SsX9rPnHM +1=!kVai;39]"!c7?=[\]BP1g`?XdVY@qK@gCM[g)E,U/eUTh5B`5g!@`l,g;bKA)SrQG>ba2Rtr +!QW:\ao9H[aTBQ(aSEsWb5fZ^aT'9q`Q#p<`Po[/^;%Iu]tUhNR>uiAD/&u_)eNPe@9d5N@:*GR +B4Pgl@9m/^S[lJO_8F:9b4s0_b/hTBrl"lWo#UjQ-H3a8_SaO<1'nF-(E7)?_SaUAa3;B?c-"Pb +]LN8;(Cgtkf[S-fr5ScX(WXD0_o0^E_RYA);,q=edd$K)s8N#rqYpKsrVH +_>aZ;qtU!dqu6`LiRIf*"o@rjrq63ds7lTnq@*E%r;ZB!.O?5_,]!,;rr +]`/cHrVcQfpo13eoD87\s8W)us8;oqr;?QmqYpNp&Gu>(qtpE_[3Qe4*[#.grVc']#QOf'r;Zfp +r;Q]rrqufrrql^Fr;ZcjoD7gfO-5s'U8"9LT:r'UTq7jKTq\3PV7+P"m.'iIr;Q`rq>:0jrr`2q +rVlEg"T//trr)iq!rr5trr3-#rVZWmrVcfqrql]m"TA?"s8N#rs8N&u"TJAurr;us-2dZ6m,6F, +ZDaUtWMcSjUnOKWUS"'LTpqUKTU(q6bkD&8qu?Eirs8W%qtp +\c3<>nD&7s<2mFC`llEAbg=APb09e+qoJfW&]2K6bKnqfRMHSH$3`rFdEg&6b4D%7>$PTPC2.BkA7fFb?sR&IC2@TuDh+(H[CO&g`Pom=a2uWPdEg"WbKTt1 +s3:_kb/h[%`rsB%aiDF#`sTr/a2uKGaN)<@a8O'\a32[)aoBKbbfS!+`\GNQaN)9<`5T[5_o0F/ +^:(&6K6M0HCi!j!C1^sa@q8tP@q8tWCM6sV?&lG4`Pp-@rQ5,arltJe!m8U(rl"lWo#O;BaMc!; +_p6b8$4n'a$V/ss`m)EBb0A;Ta3VHDdaqb4&ePrra3N,UaMu6=`Q63@aN)68`QcTM['3t7;I"+T +k4noTs7cKl!r_ul`;b>~> +_#OH6qYpWQfBiJ9#Pe,pq"FU\rql`jrX\u-qYp?dOXMCo+Y&*mr;6Hks82ror;H0c#4ps4hWL]urV6-ds8W)uq>UC$ +e`5lPhuE`Pq>:*hrrE&7s*t~> +^&JTArVZQinuAA%q#: +_Z'`:qYL0hrt>5#fQCJM[CsK&`5paihs.ao'ceb/qp-bl>jDa2Z0A +aN)?@`l,^1`5T[2^:(#,JoPL=An>RbA78t[AS>^c?!^lG?rgK\X1QQr`l5s=b09k0s3(YhaMu3< +`q%2~> +\c309db<[[s82ins82Kbqtp6ip\k^'nGi@bAKi?A-R?2#p&>!UrWN9!s8MujrrN,sq>V5fcJ@m[ +o'u/:nau;^Hl"oJ/mq"XgeqZ-Wos8W'%r;HQgq>UrVQWpr:`c,+L]u +q#('gs8W)urr)j(eE?2\iV``Uq>C0grrW2tr5Sbe~> +^AecDr;6EhgoREZqYU!dqu?]orVllsr;HHjrr3Gss7uV&*Zl=E+1(k9rTX=]rs&H%s8;oprVHF? +rVlirrVZWmr:]'OOd)?,S=c=BT:_[DT;/*SUo(6+d-:9%qtpEnr;Q]rrqucrrr)lsrrPFKqkq>C3irr2rsrVl]qrVlfro):!7s8N&srUlWu,8D"=gACjKrVlir +rq0MBQh^OWs8Vug6Q-aX.,=h$rVuosrr3]3s8W#srr;okpS4OYSu10 +_Z(JOqY9pas8Vra^jBX-]>D;'a3VuLb0%fIrl4uZrlY,Zs2GVl]$njS5lh/q&IY&_c-ahZrQ#&_ +bf]Lts2G8\aMu0@a2n5%rQ#l!aN2B?_nWprV06'OBl.Qq?t!VYAS#OlCAquoEe0LW\A#Yi_oBO4 +a2Z0@b0.rKaN;TIa2l6A`l?'>`l?'@anj'saiMKBaiVTGaN2KFa2c?EaiMQDa2l?Eb08$,blc27 +c-6.02U'=caiMTGaN;HA`Q-!:_o':(^UgP=PC%:aBkhV?!h<<^q[Y#_oBd? +r6,8caMu6iaT'?maNDWPe;"Y['bLtgccab[rl@%DcTMPWbfdoDb0AHm(`3eu[EQ_5`l5j7b08&T +bJhQ@_oL!Jb/(Mf9hSH'[-[bks8Munqtp?0s*t~> +_>aN7qu7!"h9b-\s82forquZorql]srqu]irX]&'s8I-l+Whn&i;NWSs8:pVs8N-!rVQTrr;ZEf +rsAM[gth;gnF#c5rp'gWm-Elnki1[rs8N#rrrN,sr;6Hjqu6Qlq>LHqrr;cmnbiC`r;$Bfs7l]m +r;?Qor;ZWor:g4+o^(f)lgX<-mIKoAoCMVDmdBE#gtDW*$N'Pss8MrrrV-?lnc&Rg-2miAs7X&M ++X/"JoDSUcs8W&tr.6.9.;o"^qu?Y>,9A-U-ggd+rX/])qt^-es8W)trVlg'mG[=!gAh3Pq>1'i +"TA5mp&<#3J,~> +^]+lEr:p0dhQ3?ao)J[fqu-NnrVuiq"TA?!s8Vuqrr3K&s8@!f*?-%kh"ppIs8;iqmJd+brr2rr +)#X:1rqu]nqtpEhs8Vidd&NS"SthmFSY5nQ%%s;cXKAtcp%J(Ws8W#r!<;opmJlSQj8]&Vr;Qfr +r;HR"r;60IbdjdJT)PE(U84QWT:_dJT;&!IQC#_Yp@nUerr;uoqZ$Tls8VWgs8OGErr)lg[N-5+ +)fP?Jrqucqrr;kF*Z?;=p](-jrcK;!*??BjqYpEmrr2iq%fZG*rqufrk..(PUSH`Err3-!rVZQk +_uG5~> +_Z'Z8qu6F)q>0WsEDq+db08)Pc-4DSaMu6u`W*sQaT'6jahcBN9aC_!"YRRCeC)X`rPnlYs2slQ +rl4uZrlQV3aNMcKb0%cDb.l*;]"+2KAR/naAn#7Z@:NeZB5)3tG^4h7^;%A\_uI^S`qd^S`WO3" +`Q%bn!6FrXl,WnJaSF!WbkfK\a8s<%rl4uZs2HS._nj(!VicLPD/*cp?t!SV@UWbW@Us(_?Wpa! +Z+7Ea`l$'Eb/r#T`QQEoa>(l[a2lBTd\7Bj)A66$d*0e\a2H0G?3glM:Xme4a37<`%iQ#e_T'[: +`l5j7b07lFrlc/#`l?'Cbg+5'D,!Z/D7]$/s8VunqYA#.J,~> +_Z']:r;6Els4%>As8W&mrVuiqq>UQrr;6Berr;p+s8M[:,Ud@%p\t*es8V3[#Q4Jsqtg?kq"k"* +r;ZfCdc0`fn*BE5nF,T)lg!KfhXL@@oDSdgqu-?iqu-Hjrr2imrql`krpK[equ$?hqu-?jqu-Hj +rr)j"rquZkrqud;rpAe3l0.L'nF?,=oC(r1o^_2(ea`\>q>^?ks8;]lrrDuer[7aFrr;fks7'8> +)^$AUoDegirr;`]/1Dka.UCg.qYH!s+s8"Go)JRcrr3-!q>'perr3#upAY'tiU#ahfDkmMr;?Qs +p%.tVa8^Y~> +_#FrDrquQfnu\V(mf3=br;Z`hrr)lorr +qYL!OcaKaKU7RpIStD^KV5'WRV4j6?Pf*+eq#C9js8)Qis7-(8s8N&uq>1-Y<$)kq+Fj"_rVlis +q!KRV*Y]l%f_tXAL*[fX)f,!?rri?"rVlfqs8W)trsA>js6["bRA-MSrr30$qtU*grP\_d~> +_#GDNqtg'Wep:A9VT$d%d*g1_b/hTB`Poa4`Poj:q8iTU&&lT7ccc@j#"6qde'5tUao00\`lQCMs3)k5cHF/JaiMHAa3;',X(GXHBP:m_@qB4aAS#UmCMn3RY.hZh`PfsfaSX!S`<"!"pr_[= +qT8f^pr`KT!QN4[bUgoXaMu38^V@LgR=K6jA78qXA7]CaB4Y@T@piDD>_fYn\&,r"bK%TNccsW& +a=#0P`lZ-@g!E,%&.o@[c.(.d`l>d-#S7[Y(db^ZaiRlq$k +_Z']:pA+[e#hRbos8Vuqs8DWjs8E)tqtp3gs8W,t#63X5J,B0C-hr;HWos8)`hs7?6hr;Q]f +s8W'!r;6Hg!W2fgs8W'!s8Moq'_Ct`lKRX$mI'3%oC_YCoC(huf]2u2!WDrqrr`#ko)&"W-ia,B +s8Dips5>4J+s/"3kP5)WqZ$9^>U1X;,:1,)qt0Ao-6q(9p\Omgrs&K#qtp%h~> +_>b&Fs8Mrnl(t2poDejirVHNmr;uusrVZZms82fors8T$s5,8Qq>9mbrrN)rqu?*`!r`,tqYpWp +s8Drs#P.W>R#@H/Sc55tTV8$LSt;^RVR"\EpAb0grr2flr;6EirVccqoDegh!<;Tgrr)Eequ?]q +r;?`srVlcorqZR!rr)cmqtp*ZQ8'k4\fSqu?B] +^Af,Ko[.VaH`=p#_T'pPc-"2M`l?!:_o'I4`l@hqs2Geob/qlW\HZ3`c,S5UcG[T=_o9U7`pUqJ +b5BHoc-+AMa2>s<`4*RWAm&kerb!!WAS,I_BPMC&DL%_b^<+L9bK\2Qbfot,rQ+uX!6G)\qoJ`U +!6G&[kK*M>qT&ZZn]1^QrQYAd.E]`R`59F/]tCq2HZEt;B4tpi?N]5fh=!a1og7bs<'a'GM"Lg!\'f:C.*)a2.Yms2tA]!6Y;b(!=V= +bKeDW_nEE_<)-A(a6NBss8W)8s*t~> +_Z']:qt^6k$/jXos8W#gs8W)ls8W)sr;$9%Kr;ZferVulsl2L__$i^)$q"OOTn,<:d +r;HX-c-l7Wq=+"GnF>r1kNh9hl2CS\s8E2uq#('erqZQsrqu]kqu6Eir;HWjs82fis760gr;Zce +rrE&tr;ZWpr;QTns8Mus#lal'rVuloq#:9m'Cb2Fj5fRrnEfQ3o'l)@nEf8ag\h'SrVHEfr:9h7 +r;Z`os8W)ms6UBnrr2rq%Jp&!rVulos8Up: +kNDC5rri8urr;r:s*t~> +_#OH6%f5b,W3,^ls7uZoqYgBlr;HWoqu?Nl!WW,trs8Drh#I9Os7-*`s6oserVulprrDins!.=9 +q"3F1NhVu5Pan88TV%dIVPKulcKk<&q#(0lrVlfmr;6Hjrquirq>^iPQ*9QQr;6NjqYpNcrWE)u +rr2os+ntru=;N&$)C*r7s8;lrp@NPR(Dn2;T_eZ]p +^AeZ7h2Z)TYJS,f_8jaGbl5]_`r3mU`q[Ud`l,pAd)sYAf#u7a]>_h0a8X'W`p_"Kb5BHbb/_9r +a(b>g +oDSUe!;je9J,~> +_#Fc9s8VK@dJs7FrVl`p!<2ips8Mupp\k[$s82imqtpEgqZ$Tps82'[s8N0"qt^6dqYq3'hW4"j +l0Is9nb)SEmHs2qoD8@a!rDior;QcprVQKir;Q3cqYp'm`rVQU.qWd"tkj7g1q"=+@mIKuCmcN9cr;Qiss8VZh-i +_>b2JoDSI$WN,gorr)lsrqucps8N#rr;cios8W&us82d&s8N&qs7uNfs7lHhrrDums7-*gr;cin +ruV14qu?92OGB$hR$sV?Q^aP:T;89Zb2)O^q>1$hqu-NsrVQWop](0j!<)clqu$TorVbaTqu?Ei +s8W,u!<)ops8Dusq>^Em"TSGur;HWprr!Z1nD(m`Vl-AcUSafYS>)mZUn='@Z0M/arrMuonbr[g +s8W#rrr45:s7(=T)]'S?[IjM!nGiFaTbnge,8k^uqYp0ds7ZEjrr<#trr2rtrY>D2rr<#qrr2fp +s7H +_#Fr9qW=&ZBt(sr]tqb1aihd"`r=$_b/hZDaN4A'&BMl1a2QHJbf\2I`6$*?b/=%pn]:OK.*p#T +]"l7nP[[m/Bk(^fCLUmkCMn-3E0R3/_SO+.a25pAc-OVWcHQ1.prEHXqTSWTqoJZSn]:IEqoJfW +o>psSqofSqaN)9>`Q63AaMu$&SqM%]B,"lgC1h!^@;'CiAmnthY.MNfbeqEia=G +_>b#Es8Uisj8]/Xq#CBjqZ$Nnr;QcrrVZBg"TSB!qY:'is8E3%r;-Hhs69Lbs8MrnrqQTmpAP"& +lc.fXqZ$B^o]u#=o'>AplM:Gfrql`oq"amgs8MrlrVQTlrr2:*i'(G)DlgjQ7o_\%BoC2JOo&\?Zqu?6d#64]&rVlfo +rr4#8pRL?ks7ZHE*$u[P.Tl3;s7lNls8)]sr:p*erVm-#qYL6lrV?Birrgj1k2be+ +rrW#mrP\_d~> +_Z(2HoDAI%T!nIkr;6NorVccorr;us!r`,trVuiq!<;rq"TSE"qtU0j#QFc'rVHQis8;lqs7-'h +rVl]o-h[K4qY%glSXGP3U7nEPT:qsNSt`O8k4&?Ls8Dlor;Z`jq>:0jr:pC9mqVe*;S>3L] +qu?]qrquira8^Y~> +_Z(&Cn+5n=@rf,o`59R;rl>)Wr5S`Wq8rf\`lQ7#aq2Y:_ofj5cI:"Y`m)H?c,7^%aSs3LaSO%- +aM#R,_7k_2B5D'qD/!fqB5;=!CN4orZb+6#ai;<=_p6<>_oU$EammIJb5'-Wb599W`pq.E`r!jW +`q.:O`rF-ZaskQZc-4GTaMu3@b/hT>]s=VoGA:o1An>[o?XRGQASYmd?@B;k]ZA%2o>jeOcH=8I +_8!\(c-=YdJe/M)#ngUqd*]eRakFN>)\No%4j;-p^rF^Gb/hTBb/hTDc-=>K_84+1aihrTa2?!@ +ah4'A:/PMTdHg]0s8DfjrQ"qg~> +^]+B#c/\a%"oS5mr;ZZnr;Q[%s8Mrnqu$Bks8Voo%f?;"rVuTcrr)lnq#C0ik5GAYrqc]pr;R;P +f^S(in)rs*md99%g=u5rrrW&mrVlg"r;HZqrr2lrrr)lrrq-6hrpp!XrqZQsrqu]nrqufrrr;ls +qu$6g!;u`nrr<#trqu`k!;uirs8;oo!<)iq')UbGk3_U,naH#>naZAAo\%UarVuHf-ia&Arr<#o +rqZBgs7F8X*[MpT?17glr;ZTdGTS(S*[ae]qZ$Tks8)]tr;$0drr2p(rqHHmp](9ms8)`p#hA,! +k4J]RrqaP4J,~> +_Z(2Gp[Q%o_tE]us8N&urVuoqrVuos"TA?!s8W)ts8;uts82d*qu?Eis7c9err;iis7u]lrr2rd +rr)jL'RUo'WU_rgptqu?Zn`rCP~> +_Z(5Em+[tKMRU.1a2lQQbK@l@`W!jWaSs?YbT4jI`lQbfdoCaiVQA`l.\ms2b,\qT/ENq8iBO +e]@a4$HU3(`Poj:aNDWHrlG,^"3em/aSs3u`koO"R$[8s^Ve(_a@j\' +aN;NEa1fI,_p6r`W"9@S'bj'XcGI9;a3n<)'Gh2h0$;#MaN)TMaMu6BaMl-Bbf[rE_o9U2c,[T? +bJhN<`Or0P<_d(jcM%8;!r2Kfa8^Y~> +c2[hCpAY3`ddI#4"oA&nr;6Klr;HU"r;6Eirr2rtq>Lp)q#CBhs8W)qs82irrqZThrr2imrr)lr +rWE)qqu$Bl&(/kep?VG9kj.Quk2tRPoDJV"rr;lns8W&qr;ZcrrqcTnqsj^`rquZqqu$?ar:Bp` +s8Dros8W)sr!36$rr2imr;6Kks8Mlps8N#q!r`,urr*3%qtpr8rp^3c +n+c/&ch[M=!<2uqq#1Hms8MrrrVlg;qt^9bi(YC@+=8Voqu-Nh[j!.@+!`5`q#C?ks8)Tk&cDP' +r;Q]qrr2ros8W)ts8Moorrh0AkM,V-rrE&js8N#Is*t~> +ao;A?r;R3)r:n4(`UW[$rqu`np](3k!<;rq!<;op"TS;ts7uZo#Q4W!s8W)os82fqrrE&ts7lQn +rVlfps!RgDq=iL2PaeM6TqS9ZUn46VVl7r3qtL!frr;uss8W&pqu?Wps8Drss8E)urr)cor;HZp +irA->rVuiqqZ$Tprr)fpq>UTsr;HQmqu?]o)ZBU%dC63XTr+Z\Uo'o]Un+BSS"/%&q>:*grVufp +s8N?'s82iqqZ$KmruM(8s7P2>)]T\:-JJ@rs7irA*u>b8,LZAes8Dunqu6Nn!ri6!rr3]0rr;rq +s8W)us7Q&`Tq7aln,<:cqZ$Kmd/SU~> +dJs7FpAZ*1qX<=4Bns42_o0O:d*^1^`5fsAaMu6@aNDa+bm)D7aMu6@aSj6naND<>aM5d;`l#p; +bKS2Nd*2L3s2b5[oZ7$SrlG,Zs2G#S.\UN_AoM*l@q0(^@:X(eD3_ZL_SjR=bK7uQc,mrDa2Q*< +`5]m:`Poj9rPnlYr6#&\qo/o\`l5s:`l5s;q8iBOe]@a4!m&?url$V3aN2NIbfe/Na2Z*?> +'bIak_SsN[$5XBh&dk_[bJhEHaiaV(#Kk-*_og3GaSj*qa2,d;`Pfj;aj%r>Z!'q#?'s=;rVlZi +qu$KorQbFn~> +cMmtFr;?Km!;ufq!qXmor;RB,s8Vuor;HQis8W&sr;Zcqr7h2Nr;QTnr;Z`rqY^?sr;ZWmqu$Em +&)5q!lLFH8n+#Z*l/L@OkkP2IrW<-!s6KXcr;QW:rW<#sr;-Eks7?3kr;QWmrW3&sr;RAiiT][o +lL"02oBPl7jm:XJp\t0rq"j[_rr)ir"o\>rqtp +dJs7Gr;RN2qu6Wos8)Zda.f]nq>:0kr;Zfqr;Q^)r;Q`qs8Dutrr2lprr)llrr2rcrqZR"q>^Ej +q>^p\F[_rqufpp\k3nrqucsrr;Ec#64]&rr;utb5MJB +rr;]ks8Vck!rr9"rVe/Dq>UBerpe%2VP^,]U84N\T;JHVVN[:bo(i=]s8;osrVlcprqu`ps8Duq +!<)iq+8u$0AKVd1(alldrS&G;'c.u7\Fon#rr)lsrr<#tqu-O(qu?]ns8Doqs8N&ts8N#t&cM_& +aeGN'T>pX%s82iqs8MooquZcnrmCar~> +df0CIqtp9j!;ufq,PU`^QtL<0^;@q6aN`&Tb0%iIc-X\R_nj@4bg"JYb/M-1`V@OR`qd^Sb503Y +b5]Q^`[/IDaN)?Jaj.uM`l5j2^:8&m@:a(^@:*PN?ta:gH&]ST`Q$!?rlP8_b5'*Z`lS(t!Q`:K +a9KZ*`lQ0@`kTUm`ph&!b/h`Ba2bs6\W:iNCM73d@UL +cMmtFr;Q]q!r;lnrr3)le)L9'rt+u(s8W&qr;6?jrVu`js7u]JrW)lqqu6Knrqucrrr2Zmqu-O* +m`ODRo^V>9nF>]/l.t=lpA]^CqYhB4r;Zfls8W#sp>>$"m-aK9lKn02nEfSsh#@s*t~> +dJs4Frr;us+8>g5s8;T7V6ehJr;Q`ms8W&pqY^?krqufnqu?Nmrr2irrr)lVrqQL;rV?BoSXb5)'0]WlQm\d)BTuKnc/L^s8N#ts7lKks8Duos8Dfn +rqQNnqYpKo)#jL5m`2)mTqf9prqHHlrVZ]prVQHgrVl`p!<1UMJ,~> +df0CIqtp6i+8Gm8rU\pCBlB_b7`lH9HrlG2^`;[jXb5TUjO9',(IfcO_$/*ZPc"^WFI6ccjPR +bJM6FbK7uHcH4)Kb/DKKqT';lbKS/PaK^4k6qgmEh"14=r;?Qo!r_riqu?]pf)L7~> +a8Z/R9O!;uin!;qEIp\k?rr;ZfqrVZ[!jrr2iqrr)jBrqu]npTu1i)^ZdU)]g1H**i7:3lqu6Tsr;ZTgrW2ro +r;R2phrN\Tq#CBis82fpr;Qlur;6E@s*t~> +ciqYpNos8DiorVm,us8W#ss7QEirr)lVrr<#urr)j#q#C'fq#(*j(A.+9 +OHHB9S=-%CS"6IXU9E/=q"Xmgr;V?Ip\l66r;ZfqrVuoorpRk+W2H)YUn=KUT;/0UW/dnFq>LOO%rV?Hjrr3'!r;HQn!ri/tf)L7~> +`r@%WoB3&jBp-$E^r=O@c,n&JaiDE@^raU6c,RNErQ5,]rl"lWg;kGUc,J#I`m;rO`4WaI>[)#Z +? +df9=GqYpQprr3)le_9csrrrAuqY^3erVm3'r;Z]nrV$9hrRCfRr;Q`rqu?]is7?*drsn)AoCMD= +o^hD7mH<*\rqufrqZclkq"aa_r.4kBrr;rsrri;sr;QWnru(:jmd]c9p@IkElgO3#hTblrs8M`l +s8N&irVm#uqt^0hr;RZ4qYL-epY7,@)'9kA+<;UOp&"L_s8W)ss8Mp'pAb0g@^Q(.s7uBfs8)`p +rr`6"s7uZo"m"\%kiDC8rr`6!rqZQmrVY+DJ,~> +cMnLUrVlfrs8N&trRIgFe*[&0r;Q]trVQNirs&H$rq?BirVc`qs53hSrrN-!rr*u3s8Vopqu-Ef +kGjT9SX>nAUS"3VVP(ETq"OCWs8;]mJc>HDs8Dut-2m]=s8;llhncFnU7IjLU8+HUUS=%s7uBf +s7uX.s82iqpAFpX]WI`tVo\W2rqufrqtg?mrW2usf)L7~> +cN!nBrr4)>rUo?tcO@qTb"C4N.r^:V;(_oDYrrl0N1prNKU-HaNP`5KX/^qQY"FDY`%@:WeT?sR>T +@V'#?\\l>"dETeX`6lTDrPnlY0?;2Wb/hZGaiDTP^Cq7e&.T?g&.],ocGn#LaMu3BaN2B8bgFg, +PL%1k`PTsGbPK9k`l5a7bJVEAZZfhO<+qc\q#:9orVHNns82cp!rMimfDg@~> +df9@H!W;rnrri?$s4tlsrs&H%qt9d_rVlg(rVZWopAY*lr;ZZJrr2rtr;?Top&Y*frr3K"akZ=F +nG)\HnF5StlMLJY!;uik"o%ffq>:)@rqZQnrsAW"rr2lrr;HWorr3N!jQZO0lh'Z8mdB]4jOWMn +rseo&qtg?is8W)rqt^0hrVn2CqtU!]s7Q6c=sG49*Z?OLh>@6Orr;oms8VrqrVuT`>:Ll-r;HWq +q"apgrr`6"s8N#t"Q&8%l.Z.:"TAB"r;?Qk!W;rEs*t~> +cMmnDrr)ir(]OBfXI\2=qtg6js8N&urVccqr;HWpp\t0or;ZZnrW)uRrr2p"rquZkr;RQ-s7u&m +N16c7Tr"EPTV\EY\C:3GpAFR_Jc>NFs8N6"qYpKmrVmr:rr(rrVkU/]R\cgVUSFfaS^En +rqufrrVl]o"TJ>srVlfr+oVH8qY=27*Zl:9-lh(As8N#trVQWpqZ$Nop$u00*k)"FrrDihrXo2, +qZ$ToqtfupW1BTOjneiNp\t?prVlfIs*t~> +c2[hBrr4>Dp?8Z"@@t*H_o0mFaiqoJ`l?!8`5K^.`Q6HLdDsDNaMu@V&L?XRPZ@U+/`^rOC6ai`#T`r*gU`W4'YaSs=1ahQ$=eDNe0'c%Ah(BCqgc-4DQ_njI? +`m;cS`QUF]$]W6QbfnDOb500n`PK4)bL"MW`k-7m8Q0!4kkP/Squ-NrrVHKls8VrrrR_("~> +dJs7Gqu6lus8VoNg$Sb<"oA/mp%nX`rs/;us8)KdrVP=Jrr<#trVmW(r9O7Xs8W)OipGpno'Gi9 +nE8corVZ[(rVZQhpA+LZq>:'er.4kCrW<#ts8Ms#rr;rprr)cp&*`$3nabi2lLjT5rpK'trVlg! +rq?Bcrr2lo!<2rs.fB2>q>L-ds7Gl@-6F6S+1qOMp&FshrVZ]os7u]dhbbg>.O3^Ar;Zcoq>UBn +"TA2srV6Bl"Qe_2le_X@#QFc$q"agbrm:[q~> +ci4%Fs8Dp&rVulPWh[Q/q#(-lrr2p!rVQNlrs/;us8)KdrVP4Gs8O>Cr;6Egs8)clqXW+$Pbb%M +XIl)UTV&!eeElr*rVZQjs8Muls+11GrW<#ts8Mrurr;rrruD% +aSuM?kd5,2LVCM%aV<(GaihfH`l#d9b0%]NccF)HaiDR%aT'EBa=YEL_o9X:b08)LdD*c;_7Q:J +@r5jhB3SVI@q9>'T"2h\aN;NEbfe,Nb/hZDrl"lWJ]R]/!liF,rl6;*bK%]Ha2Q*:\@?s&@qoXY +?s-rE?#X=cr5J]Vs2b5_.EolO^rOpNf6.i=$P!0%dEKMVaN2?=a2cBDd)NOM +&IB3QVo$O"b/ha%a:uV-_8OLDd*'\Q\nFr;?#[RDp@S4Zs8Vs!rVuorqptdt~> +c2S:Orr<#ts8Ud6mJm4cs8;lj!r2cnqu6rr\K]r/r;Z`qf`(jL"TJ5lr:Bme%Go_"oBYW'kORs, +j5p.9rsAZ(s8D`is8W#prr2osJc>KE$2a`%s8MrorVuikrsn#=jRVd7mIg#9qX3q.jo,2_nc/L^ +r;QWo!<)oos848Ds7cQkrr<#AF>7d`q#1-jrqcZprr)ZmqZ#P*,9%pQ@.jR"qYC0err)lsq$R#t +s8N&uhrieTjSo/^rql +ci=%E!<<&t$ig.gVQ7E(q>:3lrr)j-rVuinrr;uk[NF?&qu?Wpqu6Wqir8uX"TJAtrVlfr,Q%N@ +q=gqSR\HILU7e +b5Wmgs6R@1@<8us`6-9GbKJ#Pa2c3bL";Rb0%rPb/hTBilD;C`Poj +c2[hArVlrOiog:?(Amn'r;-9grVuoss8V`O-7L:Ks7u]nf`(mM"oe>jp&4[brs[*/mIKZ2m- +dJs1ErVm6(oVSOpp%eUerqu]o&,cG*qu6Wei?9EhZ2aV!rVQTos5Eq[s8Mrorr)j7p\saOQ_'nG +T:hpRUnX`c_Vk+crVlisrVc`qs8)foqu)*Fq#1g(qtpEnrr<#sr;QZkrVuiqrtP+WXJVkcV4XKY +StD[MR?kter;ZWmrrE&tr;ciorseu+rVuirs8;Wkq"FO_rr3c3s8N&rrr;lqrRW84(E4Jsp&Fmg +rr;]k)Z9R5rr<#qs82cWUo'iUWq6/es8;onqtp +b5Wmdo$qGYBs,4ocH42QccaGP`5fm;bKe#;%M]A5f#Z(Ub0%rPb/hTBiQ2&=!Q`F]bTad>[^07M +B4t^_@q0+]C2J^%]#)D)aNMZGc-OYWb08#LaSj,0aSEmkbJhHBaiMTHaiV`LaihiF`kK0kQ2YP7 +?"7/J@pE>LAm\i'[_BeuaNDZHaMu="`[o0PaiMWHahu*>bg>.sd*9eXbL+PV`QQ?Dbf.lW\.B2^ +&J."Fbf7rNbKA!(a90H$`QA,'%EcT2_o&B[>>\48b38TrrrW)oqUGOq~> +d/OUSs8Dutq>UEAip$LCqu?Qk)#jR8qu?]ms7@`d*$Q_fq#C]CPrr<#o'(l%qrVuod +f]qtik3MI&mHWcgr;Quuq"Xaarr2QiJc>9?rqu]o"8r#iq>UC(oAAg#nF5o6nFQ#4kLA5ars\f$ +qu?]ps82Weq>:0js76."s8Duns8DrqbpOe_+ +dJj^Ur;Z`ps8W&mW2?lsq"jjerr*].s8D`modCjp)^1%2s8Dipq>UEis8V!U)ZTd9rVuosrT^\I +S#N0VV4sQSUT(Kip\Ojfr;ZfrrVc`nrV_rqu-Hm)ZK3LX/2VhV4a?OUnaf^QD3I> +qu?]mq>^9``TV.jWp%\Rd +p\t'dr;P.EJ,~> +dJj7FqY^@#pt9ap@^j"ecH=6+ar&4>_9:!AeB&%7'GCsjcdL"WcGdrN_oTjVaT'F&c-"8OccF&C +[!YtcCLCCTA7K:gEHf%/^r+13r5e`WJ]R&rrQ68(a2Ga.^TWc?=RMKB;`MYaiqiG +c,RfHrQ+uX+j/'R`P]U5bg4\]aMl0Acd0eRbeqZHc-iX'()[\o6.!sSb5'-WaqDe8`P][;`l>N# +91r0&f'EA9r;HWsrVQKAs*t~> +d/X.Crr3?'s7,"4s8W)qpAFphqu7E-s82Ol+=A$T*%Z:.qu?Nls7ZKmqr.MYrVZ9_qu6Ejrs[fC +rV,dHmHWhWFD%o'l/7lL3ia +q>UNps8;ln!W;rrs76."q>^9js8D`'-n-5Y-%l$no)8ahqtpBirrDimrs@NHmH4?Hs8)`lr;Qcr +eGk%~> +d/OXQrVulss8L6B[IV5U,]Uo(-6p%eOarqZQorr2lrr;Z_HrpKdbs8W)ur;ciqru:t;s7u,tXJ)AdVPBod +Vkg8`Qd>9orr<#rrqlQlrquirrVuos!WE#srt58/rV?Khs8W#j[Nct4'd%nunc&Ofqu.9/q>^Kd +j.`3?UX8o;rr;lnr;HWCs*t~> +dJjaUq#(0gqYfcQBPXNYaNV^(aSs?]a;2n7cH=?a" +dJj4Err30$s8VNEg&D!Rq>C'dq>V6.s8N&sgasQm+s\6Q=7,b]s8N&trVtmV#5n/nq#('crr3A[ +kk+67o'Pl9k2#S+rrE&trrN,srqccprr2iqs8N&r!W2eGrp]mfr;6Kg!r;`mrr<#srr3K$jlZ!u +m-j?*mHO*'imR]'!;QNl!<;Kd%fcP.r;Q?"+sJ*K,)H$inbrUfq>:0k"oeGtrVu]mrrhrTm-`R* +rrE&trrE&Hs*t~> +dJj4Err3<$rqP'HV"4?]rVlirrr)csr;$BlrX7U9(E453(`H?)]'5/K_>-1rr2rtrXo,.s8W#sr;Z]pbGq&( +U#,VDqu?]q!r`,tfDg@~> +dJjaTs8Dibn`I,r?F%>gb0J0/`r*mpa2H6HcI0'1&Ki&3&e$hAaN2QDa2lHKb2^P\b/r,UbK7K7 +]pCLCda?=.AXBT0LE`6/)'"3S^+`r*gU`r*pVb(7Y(a=#!H`l?*@b0%iNbJ_33`59-QH#@G/ +?=7#=?sdMS>?SMQ`P]U:c,BV(,fn!F`Q$-E`PKC.b0A2R`PKC0bg4_]ai)?BQjOK@%hNO)bfon* +qT&i_c-=8I`r=$k_oKgCaM`d;8lTE0jnJ`RrVcZo!;k@IJ,~> +dJs7GrVm5ieC+9ps8)KeqY^-g(&S%.q"a)X-6XET+>"o$p%eXeq#C6NrY,5/pA=mfs8V9;p@%A; +kN_d,kO/6Ir;Hm"rquZkr;?Qnr;ZWqq>:)@rpfsmr;$-^p\=Xar;RH,q#: +d/OORr;Q`qcE*_hrVZTmrr3'!rVc`n)#aL7qu?WkqWCm$)&=,.-R%OBq>^KirqbXRs8Ni4s8Vfm +n#hbISt2aLR\laT\(LBMq>L9jrqQKnrqZSFrpp'arrDrqr>Yb9s8;fpn]Ir2WMZVmX/;PiS>i"1 +q=4L^qZ$Nm!<2uts8Mus"9/8trr)j.rr)ihs7cGt-Q3aE,(T:]s7-*frtPJ2rVlisqZ$Bis7uK5 +Tq%pUlMCMXs8W#ur;P:IJ,~> +d/OaXq"41AVg)O/`Q?9Fc,mrD`l@tu(sL.AccjSX`C:^]'H7])#ee0oa2Z'Cb0&bc(Wt1Kb/q<9 +[WbtpAn#I^?Ya7iI\+6k`r4-dbf\#J`l5jo`W*sVaSj84aRR>!`l5s;aN2NHaNMfLaiDB:^W![D +E+s&tA78tR=(PQO@\^iPaMYsArQ5,]rl-#%aMl'7`QHHMb/M99`QHHP`QZ6FN=cX6*#<;9bgQ:/ +qT'Stc-4/E`lcNJ_9B^8bJCcD<`!(>`8gLequ-Kn!r`#pfDg@~> +df0CJr;QZp$fBRss8W&ms76!`rql^.rr)fqs7u]iphqB#,9RmN+2msRrrMuriVilXpAP"!f]r%t +l079kn)*+%q>UHorql`qpAFjc!;qEIoD\dfrql`ns8)osqtg6grt581s8M*7l0S$&o(2A?l0$.? +rVllqr;Zfr!;uBd&,lP.qi!!D,UOS*pAY*hq==Oar;Q^$rqlTmqZ$Ekrr3,fkMkIQrr3)uq>:0> +s*t~> +d/ORSqY's_Y+`T(qu?QnqYpNprqu`prr!s/!W)iTrYkh1s8Di\PF%c2 +U8Xl`VOXd=oD&(Vqtg0erVlisrp]r=rq$-`rrW2ur;Q^:rVuWis677nV4j`PW2ZYkUSj3Rp@nUa +q>L6hrVlfrs8MusrVlcqs8EQ,rr;hO-5@OC(nC3rs82W_rr2p"rr)fprr3K(s7lNhs7!FjTVn^Y +r;$ +d/PcunE02lFBa7/a3DWM_8aI5`l?*Bb/hWB`QQWMd*:-@%h]`q%hf#9bK\DZaOA;la;<"F_T0R2 +Z +df0CJqYgEn%I2O=s8N&ks8Vilr;Q]mrtbP3qXOUbrr)DA,p=QY,9\A(oDedeqr7VQrsSYOlh:2= +n*&ZfjRi3H"oS2lr;ZfprWE)tr;QQjJc>6>"8r&nr;HX#rr)]gp\=Xarr3T,qu?]kk32F4nac)= +m-=9&`;9N#rXAi(pU)Cq)C-T +d/X.E$iKS?Tu6Eks7u]pr;HWqrr2irrr2p3rUg-hrqu8:+;u7<)]Bifnc&Lbqr.MlrVZZac]sof +TpM^VUSb!+i:R'Lrr2flr;HToqYgTsrr;uos+11?rr)j"rr)fprr)j!rVQHer;RE%d]fXKX.lA^ +X/;SpUls^.q>^Hmq>UEoqu6Tps8EH,qXq->*toY9[.XIprr;Nfs8N)urr)lr&-)>&s82iprnj`M +S=7"arVccqrmh%!~> +d/OaWn`\W%?[B,odDjAP_T0X7`lS/%(s'h=aMbd>b08/QA.9.j)Aj"oHcj^YaMm,Z)9U%?[Zt*+ +@V&YTBOu"#N2aA+`Q#s=b08*/b(7Xba90T,a2\(u(sgFH`5/gFEbB'"?XR&I?sRVZ;kph>b0'_- +!6Y;^s2kGb`l?+!b5]Q^`Y-A8d*0dt$P*OW%\(np`Q-*EaSO'TaSs0oaNDZLbg!oC_SF:7a.Zs? +92g>klMLJY!<(LKJ,~> +df0ROr;$BmrQWpmrrr<"rUg-grqucp)?9^4s7lEis7H?krVrr;onrs\i&qtg0drVufor;6EkrseMQmdKi= +kjn?7kj$Ikrr3-!qt^-grr;Qg&,ZA).3'KU-b'$Rs82inqXORbs8W)srrW,qq#: +d/OUOrqtB>Y4;PkpAb0es8N#trr*i7rr)lrrVuohs8W&lZR-G-'Gq`/K_#$?qu>XS&,,IdS"QLO +S>`!SVlA,j@2rr<#poYdDuT;83PW2$)ZX..Z4 +q#13krrE&srVc`ms8Dp-qu-@:'cJ&8Zhj\$qu?Tko)8XhrVlco&H;_,s8N#trVkNJU8"@-qu4qA +J,~> +d/Pcpp>i5cEOoe4%r'82be_\7kmS%ut_`l?*=`;RgVa9'N.bl#W_aSWu.aRR@Nb5TK^`\#-KaN2KEair#Tc,[`:\Zp]l +ARK"T=^bK9>%h00^;J+;c-4DSaND`Obf\#Jqo0>kb08)Mc.NX9'G;"FeBlCXb/2!9qTAZU)90S5 +aNVfLaND`N_TBX5c,dhi<_Q.eT&BSHrrMukeGk%~> +i;`fUr;Zfr!;ufp#5SU?hr;Z]ps7uQ?-Qs`X+sA=7rVHQarr;oq +rq-3tbhqgXlL"''l/qR9rsSf&qY0map\"7TqY]^[Jc>6>!W;rnrs\l&q"aa_qu-HjrVlcprseVW +md9H3m.C#CmHiBdrr3)rq"t'gs8W&ur:g1$s7lCB,U"JAqu$Ems8;iqo)AXgs8;iq&,lG'rVuop +q"t*XlL+,snGE7cqZ6Worr<#t!W;rps8W)\s*t~> +h#@HTrVlWm&,Q>$c)@Msp@nC_rVulqr;HZorr)j1qu-Qls8Vojc6=/H(E")6;Z$=jnc&Rg!ri6" +p\ka%p?S':U8+?TVP']_^Z>(arr2urq>^Kos8<#srVZTlrr2iof_u$Rrr;uto)8Xhir8rWdJa4I +rr)ios8D`ms8Mus')hFFUogMjVkU#`V4soW^%M:%rr3#urVl]rrVlfms8Dp-s7l::*>os'q"t$i +s8Drsq#:6jr;I''rr)fnrVuosrr)ir$N'h:VOF*Lanu&9dJn^~> +huE`Tp&>^'qWOnoA#6uX_T9[8`PfR0`Q$("bS\LD_o9R1`6?-;a2cQ3%h]d#*#92HeB#c%aT'B_ +aSa'UaT'6s`PT)oBPD$a?=I5PD2,1-`6$6D`5]m<`r=!]`lQ6Db5TTcb09k-s2b)WhoGf?qoAo[ +aMu<@rlG&\q8`QUilM,=dE):/s2b5["3AO%`W!ptb0J8VbK.W:]sO`lA"ss2Gbob08#M`mkh@()*.ec,n&J`5Ka +iVs&[r;6Bjrr36$qt9aXqY^?lrrV!*rql^Gqu$Korr;oqs8Vcls8N&srr<#srVliqqrCd',Tn6T +,q7[Iqu?Qis8;`nqYgWqqtpU +h#@WXqYL$frVulrrtbV.n>iA;o`"ddrVlisrVc`qrVlisp&4mjrr2rtrVum3rVGMO*ZZ%6)]p?Z +q>1-gqZ$Kis8Drlru1n8rVQWls8;fnptq.,T:hpTSu89beb/t?r:^0grW<&trRCiHrrE&ns7QBe +s7-*es82fls2b37s8N#tr>bb7n%#EuW2-2cUS4KWS>u8Xs8;osqYL*dr;Q]ls8W)srs\[g*Z7lK +qtg?mrVuZlrt##+qZ$Knr:G*$q"jshrr;io"98B#rr)j+q"2.kS!fu$qu$Hns8Mrr!<2orrr1sX +J,~> +iW&rVs82irs8D`m$gc#U>)g&7bfn)G`W!k)_T'O:c-jMRa2c-:`lcTMa2Z9Dbe#eD(Dn#-'+he( +b0@uHbf7TDrQ+uX"N\g2aMn.r'ue5=aj%fC_7cdNCh@6hA6ieVEgjZ?`UV%KaoTT+f#[m6"3ep/ +aSs0a`Q#m:`Q#p?rQG>caN4;!"NAC#aNF+qrPnlYs2tA_rl4rV!62[m+i_L9^ohooARShJ?=$uN +AmoPb\AQ,(bKA#Nb/hZC`Q#ss`rF++b/V?Ad_rl,YnbgXJ=E_]/$A[CaurVcWlrVQKjqu6frq"X^ajSs`~> +iVs&[r;6BhrVmB*p\+L`r;Q`rf[]Zps8Drs!ri,srVm9$s7lNls8;ior;?QnrtbS0rVQBhB-/QA ++<;USiW&cSs7uZls7uZoquH`nrrE&rrt3N'mIp2Ap#b_tkPYA[r;$Bj_Z'H1qu6QoP5YR]qtpBj +rrDrqrs\/JhWsOqlh:/EmGQ%arrE&qrri?!qYU0brX/]"s'$co=Sr-qpAY'nq>UIHo&0NI"oeK#rqu]nrri8qq"apfrrW)nroa<3~> +h#@KUqYL*ers\l(r;!@fb4Yc4r;HWos8W!+rr;ipq#(0lrVlfpr;Q]q(B441qtL"u)'T\0'cJ@R +s8)cqq>UE$PU7dt:o`+jgrVZQirVlisrr)lrrr2p(pA\/9*`Vt7 +qtL*i!rDoorr3?&qYp4i*@\,dr;H3crr3H$oWFpXU8R;Ps8W)urr2rtrquctrr)fUs*t~> +iVs/]qtp?ls8Domrtt_0j*4mXW56U%c,R`A`lQ6?aMH3@b0\9/`#H_=cd'kU_SsL7eOBib',V>n +&\uu;c-O;K`l\2#rl-)%bfn/I_o'I5a2ZBD`kJutZkQH?"%,BUU.kUaND`LaSs3Y +`VmgUa<8L?aNEC*%15&Cd)WlCaNMEBaNMcN`Q$9S3slaff$)2*a:-59d)!)#;G157aQ`U$rr`2r +qu-Hm"o\;mpA4aKs*t~> +hu<]Urqu`p%/]\ss8DutkKW2Ys8W#r(&\(2s8;orrr;ipr;Q]qs8Muqs8Dfnrri&oqLg?s,7,5< +ir&fUq#CBlqYpEm#6+Ptqu?Bfrr3Aag$%\lkk+B,iUHaDrrDurs2+d4r;$?ls-!?`r;6BjrVuos +r;R/iiU,t.o]P]1l/9__rri?!s8W)tr!*,ts8N#qr;HU8rVuTd?7-t"p](9is8;cks8W&sq#C"l +-70`[[f>mss83H$s8W&Om-Wa!s8W#js82fprVlutq>:0irrW)nroa<3~> +gA_icr;HZos8VuopV`rGdJin.s7G0]WiD_pVk:#iWLo:)oD8Idrr2os!WDrqrr<#trZD1=s7Q%g+s'_Us8VurrVQKl +s8N&ns7056+;Z6/s8VZhs8NT(s7k0:T:VO`qYL6hs8)`ps8Mrr"9/8uroF*0~> +iW&rVqu7<+rVufhpAXsLSo&q5\\?21bfKb&.*0BOaN2N@a2H!;aiV]IbKIrCc-4SSd*cJ<'GqZ& +#S=Jdb0@fGbJsM%rPnlYrPnlY(X^.>`knEl@Ui_W>%hP\DN1O.`l>p:b.l!tb4NdO`W!mRaSa'V +aSO$ZaS +hZ!l\r;6uqu$Hls8Vs#s8Dutp](6lru1k6rVucns7?9ir:m!9*ucLT ++H-6prVufqr;-EjrrrE"q=sIXrr3&Kg["k+"RkdGlMCM[!VuWks2+d4r;$?ls,m<\r;?TprW)`k +rs\AUlfRTtjRVU0p!<:0rs&>qq"jmfs7cNmrVum-rU_Nb-[5F[rr;`ms8D]krsA1!+rqsW-KG.' +nbrRhr;Q^)o\\s$g%,1=qtpEmrr)j!rqu]nrVuop!<20]J,~> +i;`iVr;R?-qYgHoqY^3;UT<5DrqlZnrVulr$30o"s8Dutq#C?mru1k6rVufos7?9iqt?U,(_[o5 +)N"IhrVufqr;HWorrE&trVulrrtkM2s7PcLQDCFVU7n3TXN^;3rr)irrqXD0qu?Kk!WE#ns7lTk +s/>qrs8W)tr>,D4qu?]kp%6/$X.lSiU9LGlWK"giq>C6orr<#mrr;rsrsntt,p4CQp[nIbq#CBl +p\t1"mT1AD+!Mrhs8;ogrr +iVroVqu7?.q"Xmgp@%eDX_r$F[DKo*bfn0,`[JpOaiV`HaiqN?aN;]NaiMTG_o^0>e'-(_JJ8Y, +)]f>qb/MKG`luI$aSj-Y`>ut>b0%rN_T9d/[:% +]Y);(aSs6[anNk!aiDNHcHHV(%U.B'b/h?DeBGbMa3Vgj&.T?`!k-=i`q.7\`l5d;_7,8":/Y5h +kk+lSrqlWlrVHTnj8XW~> +huUEorVlio%KHA+n]'hdnEfE-l/:Cqs8W#rs2"^3r:g3\s69OYs1eR2rri8prVHBh +rtG%bhr*trkOeN9p"8%!s82Tcqu-No!r`&rrVn&?r;Zcms7lNQ,Te:ap\k!hr:g6kr;52",U4NZ +>4MC_s7-'qqu?Hjs8V +irB&X!WE#prseo'rr;ijg81n*r;ZNir;Zfrs8NN+s8Vlir;HZpr;?Qorr*N-s8Dutq=ajep;A:3jrqcZprW)orrt5,$abc^hU7\'VW26Nfm/$YYr;XY5qZ$Nn!<)Qhq>C?ns7cNk +rV-^KdrYGP4s8Dusppm&iTqC3=s8Muss8MrorVZ]qro=$/~> +iVroVrVmN/qtp6cq"ss[kdPABRD-bZaj'h-!6G/Z%EQ`BdE9STbKn>N`lQ:$aqqt;bL+eZ^s(!J +WuDfj'GrsNbfe8K`QS5$rl,tt_o'L:bg"DVbeLcK:hFEL=]f0IBlh/&a2lEEaSs5sa8j6Z`<4-$ +aNFM+!6tMe!m8U(rl+oWs2b5_qof)^`r4!Xb5TW`blGueb599[aoor5c-4>Ja=,'I`Q#p=aNDZG +b/_B?_8j-ECLLFW@8L38>%q/\\%TZ!aN2ECaihm+b5TI*b0S5Ia26*:#mh)Re'Q.W`5^0G`lYIs +&/H#m435FW`l@Vk%E5uu_og-9EDB&$B"RO2s8;inqr%L)~> +hu\5/r;Qru +s7Q'bq#(..lJLjnp@e+Ip$pV\s8W#mrVuosr;6BhrVm?+r;ZNkrr;ifi%H0"K`:l\rr;rprn/bH +*?cn6nc&Res7-'jr;HKcrr<#Z!qPF*rr3&rr;QTn!ri,srr<#tk5Tr~> +ir9,[rVc`ns8W'-r;6KlkcU]+p&Fsir:Ksf$i^,)s8;Wdqd"aAqu6Wprr2p5rVu]ls8;osrU6[, +'d"Hsq#C6is7lWmrqZR3rVlcoqs_=eW1]cPW2$&e]CtRjq>1'is8Drs_uBN4s8N#rpAXjcs8E#u +mf*4crr2io_>aK7*WH$8rVlisr;?Tirr;]_m)6$+USji[SYW0NV>:#frrW2trVca$rVZTlrr<#m +rr3o5o@tf5,(TLdr;Q`oqu,+n*YoY=;XXM`rVuHf'EA(3s8Duml`lnTTrRYXrVHQorr2inrrE&X +s*t~> +h#A/dq"X[^qYL6a`cYO:b.u*>b0'_'.*0HLbg\[Zb0nV[a2Gs$P33=(5iIBXtdoa2c9Aa8=$Ya<8XM_p$$CaNVC7#nL\"cc4&RcHFP<#nRR\)*H*gbf@ir +a:Q>(^r=@A_5Um09j2_mn,<1`s8W$#qtg0drS[^+~> +huE`R!<2ut!W;opru1Lah>dNMqXX[drqlTjs8W#orr2q2+=(pup\t13r;Q`qr;ZZoqYL3jpAaqo ++Y"H\p](9gr;ZWnqYg]sq>'sdqtpBm%ccF5mIKT;lK[:,s8N&nr;Qlqo_/0Urpp!_rqZT1rW2rr +r;R$$pAY!dpAOgcrsS&IlgjiDqX!S=hoGZp"oeApqtp +ir9)ZrVcQl%fH2$oYI)nr:g6kq"t*jrr<#srr<#t"_8PoC[h#rru:q:s8Musqu?Kgs8VinoiE%U +'I3;Ns7lNlqZ$NnqYqH4s8W&nn#VqPU849VS>NCHo_SU_s82WhrVcc6rquf\rpBabrqufprr)f3 +rW<-!rr2lrrW)iprrW0!q#::,o#7N'T;&*^T;/K_Rdp.Ms8MuqrVuip"oeQ$s8)KhrttR^+WM\M +q>^Hnq"rek*>9\C8c8Ves82ierr2p,rVucls4sTFT:E^kq#:9m"9&/qrqucrro=$/~> +h#A/do_/1[rVli\N)3BR\]`LFaj%oJb0'_'s2ZP2f1-&S=4>I>b/2!7c-4,Maj8)K_o'4>g.;u$ +&JNI5bJ_EH`6%u!rl-)"_o9^@c-=DM]5q:h@:WVV>\.ZAZb+K#d*9eYb/M?>_T2r$`P][6aN4;' +s2t8\qTAfYm`#j[b0%fF`l5j5`Poj +huE`SrVlotq#13nf\ui01\UMArVuoqr;Q`rr;6EgI3^$b+(++$s8W#ss8;lps8Durs8DulBd"fE +-H>o^rVlirqt^6kr"/c'qt^6ks5Dl.n*fE/lfd=,rr`8mr;HTo"8hcaqkF)[r;$?fs2"^7quH`p +rs/Q'oD/%VqYL-i$hi]Lo()>Cn*9-%ao25BrqcNir;-C7r;ZZos7cQjr4+%!.CJsLs7aD^)^H[M +CARi,qu6Tpnbs-sq>UEkr;HZpi9BR_r;ZfrqtpBnrr2fqro=$/~> +iVrrXrVHO.qYgu.p\Omes8N&grr;rrrt58,q:)13US-$1qu?]qrqu`oqu?]qjo9i~> +h#A&bp@eCZs8Vr@@8Vl\a3E#a_o9^rastEMaN2BB<='9V&4saZb/D-:bK.]BbKJ8Qa2H9K9FVdK +'WK@$ai_lL`VdgU`Yc\7b08)P`P-4nB3f"OB4bsuS[uMS_Tg:'blc,0`l>m:!65#W$-('-b0%rN +b/h[&`q[XS`r?P$5?`V;.6Bcp&4ggs8)fpr;QiqqYfUWJ,~> +iVroWq#:a$s8UR(mf3=dq>'sas8W!0s10X*+!MdR-f=drrr<#sr;ZQkrsAZ$qh$1=,9iiXq>LNq +s8W)srVmE-qYBgas469%o'br0n)'pbr;?R&n_iR&p@7P6m.BVn +rr3E'rVZQmrr2Wes8N&uq>Lm#\1&gQ\G,g.,pX]U-&)?slMgh`%fZD(s8Ud6lKd1(s8Mrnrr33$ +s8Mrnr;PaVJ,~> +i;WfVrr3W1s8Musrqc&YXi1&4rr<#trVZZnrr2os$G%Q,(E!u0,2E"irr<#s!WVrorsJ`%q1'S- +*?C^GrVHKrrVuosrr2p7rql`qqZ$,\S=uXLU8"NZ`9mHqs8W&ts8Mrsrr)l6rr2rqrW`?#rr<#t +rr)iurr)ilrsJc(s8Dors8;fprqZQprr)ir!<)fprr'\4s8W,us8DusqYq9/r;->+Zm6k>[.O'u+<;F;++a:drVliarr;rrrX\r'YG7\b +TB?%Js8;lorqlTmrr2'[J,~> +huE`Ts8P.YqtU-iqq6.$DQLFle'ZF[`Q63EaMl->b/fAB%29d")\9X*_oBa=a2H3=bKJ&Kaj=H* +&.o8$d`qd6rQ$)$b0%fEa2le`Q$6LcHsq]b0J,Oao03\aNVd*`X9Z#_o'F2`l?!VaMc*0O_\<3>Zb06?=RMSU;+=Ta2c0Aaj%lKd)Z..s3)8"`l5sAc^,+P$^]DoQ44KC&.rU- +cH6(*n&Q'_aMu6Bbg"5@AP>QoA&IO&r;QirrVcZo!rD]jkPp&~> +irB#WqYps%s8UC$ec5[Kr;?Qhs8W'?q"f+Z+X\QW,TOJsr;-UEo +!ri,qrr)lss82ir$-qo+na>]+le:t0rseo+rqcTkr;Q]nr;6Bh])Ma.s8N&ss8O)9r;HWnqt^!a +qtg0dqY0XKlKdj)lgON?q"jpdrqc`rrVl^"qu6Wpr;6Bjrr9k7s82rqr;QWo!<2uq!rMonrr3u4 +q>^KSl0S3Lp'r;!TE,:L.2.j,o[,(oUgrTsO]rtGD/r;Zfa +jQPdXs8Vfmr;HWorVlfo!<2$YJ,~> +huE]Ts8W,u$2=DlT;]!irr;usr;ciqrVZX3q"\qQ)BKk5*>c9cr;-?krVQWfs8Dus[/M!H+aj%Y +q>UEo*<,m7rr2ikrVulsn=$5JTV8!PWLh#orquNjs8Dor!<2or])Ma.s8W,uqYqB2qt^6iqtps8N&urr2lqrql^6rr;utq"asWZ_j@o +X/DecUnWsiqu6Kls8VuqrrW2ps8;lqrtPG/pT>\],IOa&)AjM8J+NU:rVulrs7-*drVld*p[knc +S>2tXrqHEirVlZnrr2'[J,~> +huE`RrVnkSp]'Q%BO\$R`R!#Zb/M9>b0%fE`lcNDa\`H\+X%U9".7NZ`5BX9`Qu3:a3N5##7V1R +@F3-?rlY2\s2lA'`l,j8`5p*B`QG]!CLLOR>\%bdP.JiW^rOO7aSs2daT'<\`rF!]`lQ*;a2n8% +&'3)Bcd0tabg";QbK\5Nao0O"dEg+^bg=YZb/hQA`l5d0_SjI8ahl+'b08)Pb/hU#`BhMbaND`T +dETG<_5M9]>@_2J>?GEN>)9i*]#)S3aNr)PaMQ'Abfn5N`l#d9bKu4R#S3Vt&e5Hi&8';5a2c3@ +rl4KLs2tA_%Eco@c+pYr7S$^*g?nJ/rrN&rr;Zfl!<)*\J,~> +irB&XrVuoq!<2rs#2Rc.s7l9bp\u!-s8N&uq26[G)C$LT+ccKus8;oprVlg'pV%pt-mhshs8Voo +('"72rr<#trr<#ts8UR5lgXE2lKdF-s8W&trrW/urqu]'rr +hu<`VrVcaGrV-0FVP1%iq>LKrrVc`mrtP;+m[Bc;U8=Z^WiFkArr;lms8W#ns1A:-rrrB#s8W)prr<#t,5qE7lf?jP +da64[`l5s<`l6-Le^N!sg?%l$q#(0lrr;rqqu6]rr5/F8s8N&ur;QTns8E&rrVca3q=CkqT:r$R +WhH)]SAP"!rr<#or;?Hks8Drprt##*s/-SW)B9V6)'OP'rr)corr;Qgqu-Qo$iU+cU8XTNc2@G; +qY:'lrr)iYs*t~> +iVs#Yqtg3frt"ei\n+mI]ZA@FdEK\N`Q%nus2H5)bgFkdC_IHn)]9J!D:?\S`6ZNDaiMiQS.-#G +&ie7aao0B\a<&IDa2lM]\\Q%q]=br&e_8EueC)[fbK.iH`rUb/hTA`Q$'>bL;+E'FkT^ +&J#IdcHO>Jb08#L_o'Laa:ZS=bf\/UccaD";,^.jT&8r6rVlrsqu-Kn!rD]jkPp&~> +iVroWrVuoqrVm,Kj8]/Yo()_Xs8W)ss8W)uru^t7rqO#O+s7pH/0]'3p](3hs8;fii%$/r+scm\ +s8Dcm!r`&qrr3'!rVQTo#h&)-p@.>1k1K\5!r;Ncrr<#t!WW/uquZlts1/+`qu?]pq"XderquN\ +kM".sb/_H=`lQ9DaN) +hu<`VrVca#rV>iPY2AmNrs&H!qu$Korr2lrrr;pU?m(]472qu?Wls7aO6 +TV\QVW2-AhQ+$2[rVlg"qY:*jqu6Tnrt,20r;ZZlL`@BU)]p)tp](0jrW<&trr2rgrr2j0rr)fp +s8VfecD.24T@Wr9rVuiqqu?Zpk5Tr~> +ir9/[qtg3gr;R?!c":FG[Em1HdEg%X`Poj:rQ$2*bf/5`d[CXW',2$!$rPQpcHOGS`5p'5&etog +$e!>B`5qr#rQ$)$`lH6Da2Z0@a2P8iC1(R[>[hJbV8CEubKS;Va2Gpp`<4-$aND??s2tA_rPg\3 +aj//Qaj8Jff$r'g^p^GW\[]5`^;7\(_SO(%]tVCq]Xthe\$`EC]#`:Of[nHoc-",Irl;ms$HgT: +b0%fH`l5p7rP_+Cb08D`b0Ir?`O1A6?=7)9>[M)Ra3\N, +&/#Kc$B*Ab0%uRd)*8G;H$e/XlT!MrrW/qqu-Nrqtg9Ss*t~> +h>[NTr;HX$gtCTbs7GsVrr2p#r;6Eks8N#sruUn6rVYLt-mfrW*\1I[r;HZqrqc@f*@2aSj8]/S +s7ZEms8N#q(&7h/g$S>#mITW-h"h!Mq#1*grVQQls8N#ss2"^7s82d5r;HKgr;QWhkhOM&^r"(2 +_8F73`PfX0aN"+r!65#W'?8,9bJqWD`m3N6rV6`5naYJss8VuorrW)kr;-C(r;Z`qp%O4f+sA>Aq"aserTaC^s8W)urrW2sr;Q^!jPf7Y +h>R?Tq#(-js8W)Ys*t~> +huE`Urr3K-rV3Fg[e0Uts8W&pqu-Knqu.];p&FsdgE7[^'c7`5=6KGWrr;unohH;E(F%f:s7u]g +rr<#ur=K#.s6,Z:S>;mUSu8RCq#C6irs/H!s8W)ts8Te3s8DusrVmc5rVZQkrquNShU0 +ir9,Zqtp?js#KuOicA78Xi\c4c-=JVb/hTA`Q$!Ab/hZN`QuiW\d&ia',D/p5/bpud*'SRc8Z1K% +hHM)bJ;6@rlY)Y*6H4=`5][4Zs[NVA79%YD/c5^ai)KLcHXDLaN+8!]u^5rs2YJc`Poj:aii#\r +n%h.`k8X_[_9Mi^r4:5`P]R3_u%:N_>_4`_8F7.\$WKG\Am.We^i6pb/_NB_8uZ!r5eoX!6+lU6 +-.ErcHaJRcG@N,K5>(&?s-W:>[^o[Yedulb/q]EaN)NI`l5s;b0%`H`5omBBFG4S$BrfWbJM3=b +g"AQ`P]^ba8jB^a:6;9cHO84CfXD7GgP[Nrr3&sq"Xgf!r`#pkPp&~> +hZ*TSrr3&mdc:?,s7cTirVlusq>:'gquH`qrs8N"s8;`P>9G?n+;#tLhuEHMs8#eD,9J,Bq=jge +q#:^Enrr2rtrVulrr;XY5s8Vuqs8N?&r;?6Ke&]T/`Wa?! +_84%-rkf,`aMu3UEo +r=Aqurr;ENmcO'-mechNjl6:>!;l]oq>Lp)r;ZZoqu+EODUSRXs82iprW)uerr2fqrr2p"r;6Eh +rr3>\iTf+bs8W#mr;?Nns8M*ZJ,~> +gA_Z]qph@FoD&:`s8W≺HNmrVdlbAp&4pd<$3.s)n>Y:rVuZls8N0" +rVlcq%dfIPTqJ$NSZ/^Mq#CBnrr3-#r;Zfpqu-To_#FB4!<2ip-Nrr;oiqu?Kio#Io6U9:Mn +WMZJd[-IViq>L?ks8W)ts8W&s&HD\.qu?Ni^hdc3s7H?hs8W)t"98B#s760fs8N#srs\l+pZ&-A +US#I#r;-Ejrr`9#s8M-[J,~> +iW&rVrVnnVo]NA]A#d,_cHOANb08)PaMYp8b08#J`QuTEbf\2B4q&AG((h2j^u!;[ccn6W().@Q +d)ErJaSj9[aT'@&aN)9?aN)<-?"mt]?X[G_G-/Nta2H9HbK@rNbJq*6s2kA``r).q^V7Fs^VIRr\@B$I\-fj^^!5!We^Dac`3IB/a2?!Fc,e&Q +^;%"/Ch-US=&`O9A7p.i_T'C2c,[rNaN2B@aSsY8"'Pi)*2s8VlfpAOsi!<)'[J,~> +huE]RrVlrIgA1aJ!<2or!WMooq#C.7r;Q`rq>L?fs8VrgBI+ZH,on0XYl=OuKI@`c-)1,5s7H?c +q#1?prqu]o#h\4sn+#]4jkg":"o\8rqY^?ks8W'#r;6EkrVsb6rVuiq#Pe>un__d:`;[S'_Sa:0 +`5]d7_SX41ajntof\,!3f\"ZtaMu9Aa2Pp4_o0I3a2ZK`l1P&U_#FB6#6+Puqtp('irr<#erW;ulq#14# +qt^*aq>^Kmiof@Trr3&srr2lrrr2$ZJ,~> +iW&rWrVm0%r9:5cfD>@Drr2rr!W;ons8N!?s7lTnq#CBinm`aW*uu(7,-:_=r.61-*Z^dBs8Vcl +p\=^hs8MrqrugppQD:+OURJ'XXlB$Or;HZprVc`ps8Mrqrr<#trr)f3rr;rsrr2pTrqQKmnD2F1 +_SO++_o0L4`Q-!<`5K[;bLP4sg"G*4f\"Wra2Q'=aMl'6`5T[6a2cWdlh:>X^]+*0rVlfr,5V?; +pAb0ir;Zfnr8Y&TXf&4rVk]f[VsOH_rr;rkqYpNpr;I<*s8Vrls7lTgs7l9eq>('irr2rdrr)ls +rr2p+rqlHip +g]';4mD*RNNP!*Sbf.E9`m)iSaMGX2b0S;N_8!q1^<4[Ed7k2_*#fP-(6R1lcb +`4WRf\\,Sh^V@S!_7mOj[^3EM\[T)[]tV7s^qI4`Za@-O]tV:s^qRFm[CX2obgFe]_8uSps2auX +6--g]_7n4Dc-4JW]sjME +kl1Y^qYpWrqu$Em!p%qrrr2urqu6]pq>U-kq>:*hrVmo4s82]nrqh6p*$cLL+XiHCX!&W;++a:g +pAb0hs8MrprXo))rVcZos8DoCnEK6!kO[d#qu6]prr)lsrW3&srqc]nrVsb6rr<#trqm!!nCbn$ +aN4:t&&Z9$_oL!Qj6lU8q"ad`qu-@%q"jpeq!R1rdEBPL`50:.rkel[fBW,,_>aK7s8E&squ-Hm +rr;lsqu$Em"Shfms8Mus%K5JOmI'N5oChY4h>dNSqYpL3rr<#trquZkq#: +j8],XrVmH.r;4(*aSGc6qZ$Qnrr)fpqu?]qrVnnRs8;corV1dc(*!u+(a=q-W#cp*(jkrUpAb0h +s8N&urr2rss8Dutr;Zfeg5;MtWLoi^Tt9UgrVlg'rVulrrVZHfs8Mus!ri/sc2R_Bq#:obU8"6mq#(-gqZ$Tpk5Tr~> +h#CRTp>^U/IC-r;ai_H7_8aaGc-"#C`lcQM_nX(/]#qn/dF$KR&.09j+rM/;d"_T*)@g]CfYY\Q +`m;lOb/hZDaN2BCai;?>\"m& +bL"AO^TV*F +lMpn`!rW&srVm!!p\XshrrVW@iVid.rVlirr;?TlrqcQmp&=jgs7lWmr;Q`prVuii\0NLE+rW)llrW2rrrr2utrqufqr;R/siTfjplLOT>o%r:'s8W#orri8p +p\4L]s!dsIqu6Wpqu?ZprVZWorqufos7ZHlpAapcrqu`or;Zfpr;Q`rrqu]kqu7/Nm,mL-s8W)m +s8Muoqu5[VJ,~> +nc/Re!r`,tqZ$Tp49,9Zs7lHccDn;Ds8;oqs8W)qqu?Qnr;Q`ks82irqZ$Tos8W&rs8DW!+U9EOTTuG#pAFpes8W)t +s8Muok5Tr~> +k5YGZs8QF(qu?]lp?JP]?EM5kccO2J_SX:;aj8/ScGRlHaiM*0^;7e1a2cBHacHZB(*OtC*E3^] +#7EQFeBZ4Wa2uKKbf\#G`Q$!Da2H0@Z+X.L?Wq/TAp8a@`Q$!AaN4>"rQ+r[c,^%1bfn5Lrl$>+ +bK7iJcHXGLaN_fHbKJ;bZamca_SX70_S!=cZE_6sf[eU%ccjJOb4EjWbobKc_R-_\]Y;(o^U:2U +bL=_^d*KkS`l5s>c-42GaMua+ +WEW7i@%HH[s8Mlhr;QfnrTF32~> +lMgk`rqlcqrVlutqtL'grt3i3oDejhrVuipq>^Kls7ZKirVlllrr<#ts8W,trr4)8e0Q1a*@E$T +,9S1,qu$Bkrr<#tr;6UBn_Z'B2!<)os*<,m8s8W&t +rVZ34`PTI,_oKa3]Z%k8i:?dBqY^3hs8;]lr;7T7s8Mliq=NO``Orat`l,m?i:d*ErVQHks8Dil +rVli5rr<#urr)`prVZ[(rV#L7kj[p/r:BI4iVicWqYpHn"TJ2jp\Fae,6%Q@q>L?hs8;lrr:^!_ +r:BX-f>jDTNGWM's8Dutq>UEorVlip!<)fp$KpRBkLon8qtpBkrqc]pjSs`~> +nc/XgrVZ`qqYpZrrr<#tq\]+iXf9\CrqlWnrr2`ns82iis8)cps8Vljs8W&srtP4U+;c.5*>o_7 +)BDNHqYU9ks8W)rs8N!1s8N#trV#T?US+?YRAR!bo(i7`rrW2tr5&C4rVQR#r;HZqrqucqs8Drs +(\?+q`59C0a2Pm.`Q$9bpAY*irr2lqrquflrqcX8rVHBiqW,rG_S=%0_o'OPpAFderql`qrquZl +s8Tn6s8DusrVmo:s8Mrrs7>0ZVlHnoUT:2hS?hSZqu?Zqrr)cmrr)isrr2g?rVlfrqYgHhrqlZn +qt9d[qXO1#e]!rJMJ?nrs8;lrqZ$QnrVlis%fQG*rqlWfaJ#8uU#>bBrVm$"s8W&qk5Tr~> +kPtMZ>Q=[!qu$HlnA]ZUI(7;Abf[oD_p-BDbJM]MdE^"a^W+C4_8XR?aiDHDb/^Rj&0i/@*Z>e& +#qsiVbK@oJaiMQIbf[rE`Q6-DaO.u=Y?,:??"I>\Fa1[?aSj8sa99Z0bfIg'`W*q+b0SAWaN2QJ +aj/2]cFC6l]tq_/_nhe1_oBd?b08)PaN"4us2ZD,aMPg0 +a3ViRc-Ob]a3DiUb0[)iSp#Nt8OUYNd*TtM_8c>o'$&,5`kfL(LJplBAAn' +lMgk`rqlcqrVlotp\k*njOiSns%34ds8Duss7u]ks7uQls7QBkqY^?ls8W)srVu`hs8CYX,U=NL +,TnE`h#%'Krr2rsrr)cms8Mrorr2fprVuoCiofe"m-*NdrVlusq>:0js2+d2s8;j2s8N#ms8)KM +a2Gp4`5TX2_T'^Mli$_\"oeGsr;?Q`rrE&tru:t9rV5R0c,[c=_o9dIl1k)Ps8Dusrr)cps89_4 +s8W,urVZ]qs8Mus')L\No'l/Ao'bo!dJs7As8;cnrr3)uqt^-es!%I?s8VWbjO)5PPalW*@j`cS +.Np8i.OH>^HiO!Arr)irs8DrsquZltrVZ[#iooO\o`+pgrr)utr;>XUJ,~> +n,NFe!WE#nruqCAs8;orqY.:j`;'?3rVHQns8N&ps82ilqYpNerr;lorVmZ4rVccnq#C9D=rn\%% +ilQ3-JA4mrVlcq"9/?#rr2rtrXo,/qXrp8U8"?TU84ckq=sjdrr3'!rVaS3!<)Tirr3r5s8;WO` +l,d2`5T[3_oKsSlh^SVqYgBlrr<#trp]jdrqud+r;#O0bf.E6^;7kO9 +kl:\]7/Qr]s8Digs8VuYIp[W1_TC'DaMc9Gb/)'3aMZ0If>c%V_84+1aihoOaiVE:d*T3e%MKTd +'b1Z\\C/[GaMu9?`l7qu$c^9/bfn8T_nE-UAH#p@@qT_'_o'L8rQ;mr!m8U*rPeiYrlY8^&')Z. +^sC9Qa0N4h^V[n&\GilC`6m>kbfRoE`lQ=%bk9-UbTP!Kb0/#RcI'VF\@B2c_SX+(bL4PSbf@fE +`Pf[9ccXDIaSj9[aT'@!a2Z-@aM4$GBHA8Ap!]$&19b5TU7b/hTB`Pop>b0%`Jd`0bQ +ZET=6JQ#<7%MKQo%2KWn&e#"OgsF6a_SaCmaT'E]a:Z>1_nX-l@8fp)RH*o=s8Dflrr2usk5Tr~> +l2Lb_rqlcqrr3W-s8N&umbR")s82irqu6Wms8;lr"T7Y*gA(XH!<2lo!<2uts8EE&pMD)u/K5ra +e+s%?rW)orrr`8tqu6Nn#h\;,n+5i.kMQ7a60)#jI2rr;uos8Vf8^:0irrN,squ.c=p](7o-RU5k-RU)[-mKfV*?l[G+XA0Z*2`T1r;Z`q +s8DrrquQiqqu6cQl07.&s8W#Xs*t~> +mf37bqZ$Tp')_e-rV4X4Z0hSds8Vuqs82iorr30"lttDJrr)lor;cirs8W'*q=SJD*%)IC-.)Dc +rquforr<#ur<<5up"c+/rhp*qVQmQ'q#10jrr9G+"98AurVlg4rVuokb.Pp7_8O=3_o0mZlhLJZ +qY0jcr;lotrr2rrs8E&trqlZlr;QZorYkb7s8Vl\gWd^U_nO++ch%25rVlfrs8Dlor5&C4rVulp +rr`9!rVZZl&,G.oVPpDfVl$MqSA+OgrVlg#rr)cnrVlfrrVdl(8CU%rVu`ns8NT,s82irqu?TKUS=HYf_tXG!<2-\J,~> +lMpn_rql`os8O;=rVcT\Y%2.Ibeq]Oa25g9d)O)H`Q#PYZI7CUc,[`BaiXP*'?\GAbfJ#QcUe3I +)[-TVYL(_Ab5B9Y`>?P8b08,Tccp&bK.`F`l6!Ccd0eO]Y)"r]tUVPbL$70 +#K=a'_oU3Nb/26ub5KC"aiMNC`Pfa7^7TRT?!165?=RGDHaUf6aN2O'bl>fcaSs3ZaT'C*c,J/f +2%1!.)&aMGj0PZUmfBs$b!s8Vrlir=N~> +nc/Xgqu?]qrqufqqu6uPht@$Jq#C9jrr2p+rVlNcs)B>6M"(<8lfId2*rVlNM`5p-8 +`5TU2`Qd3+q>UEoqZ-Wis8N!&s8N&ts8N&tqYpWpqYpKo)#aL7rqZ$De&08F`l#mWqu6Kgr;Zco +rr9h6s8W,urVQ`oqYg9j')UbRo(2MGn*B2aiW&rWq"ajer;S)Br;6Bhr;HTopVn^),p"$G-QaZS ++=8Qb,V;c!,p4]jq>UEmrVccrrr;ltqZ$Kjrrh'>kN(Irrr`2rr;PjYJ,~> +mJm1bp\u95rVQ,^Wm1PCpAb-lrr;utrr;`fs)&u*KC&C/qu6U2rVZWorr)ijp](9gq3rNG-RdL> +rr;uprrE#prr;p5rqZ#CVPL,aU8b??p\b'js8W)srr2rt_YsQ8rVliqs8E#qrr2utrr3Z-k/>Ba +]>;D$_oBsWmeZq_r;-d@rW3)`)p\b!i!<2uq!WE#pru_7=s7O8W +)'9Y0'd+;6',_o=+ +nc/OcqYL9kr;SbUo$VPhKuVEWbfn2E`5p3B`P'.>9*c)Ie()XX`6-9FaN;WJa2l*5bgOMQEWcLR +$*hn)cH=/Hrl#eq`5BL4b0A8Yc-4/==D_\]?>+7uO1E!A_oVi"rl>)Z_T2l"`PojaN"+u'ZS85`Q6$:^Un>c9N#ZN +n,<:[pAO%OJ,~> +o)Agkr;?QkrrE&sr;ZcprsntZh>dNKs8Vims8Vurq>UBubU4JR+=j)`r;Q`rrWE3"r;$?l$hjVq +s6cquo_AIdpA4dfrrN,srr2os&(L";p%.G.j8/fUr;-3ar;Q]urqlTj_Z'H4s8El5rr<#ts7u3) +`ll*4bJD*5ce[m[s8W)trr3)tp%eU_s7QBhs8W$;rr)fqs8N&urr)TTh8mgY_9'dMq>L6m*Y>&s82Zir;QZp,l[]:q>:*gs8VopO^ +)^g?/[*S72q9(!%-:mt4s8)Zns8N#q!ri/qr;QrpjQH$hl2:Par;-9eroa<3~> +mJm4cpAYO!r:.q)[IF+hs8Vrprt#&-qZ$TpaWhZ>)'tdKr;?Qos8N&u!WDinrsSQ#q>]f[[ds.l +s7Z +Qa+d%mIeF<'-BX;s8Vrlrr)j!rqufrrqm&YW1'NS_#!s,i;\<~> +nGiLds82fls8)fpr;SbUqV@QHDm7C+cHO8N`5BL8_9'sIV@!\A&Jp&Kb/V`PbK@oJ`l,d@b/qHE +^s0lSPKV5%d)*fBrl+oW"NA3p`le;,&^82+@VK@_@UNhiQG:5Ma2c:#b5]Q_`koeG`l5p:aND`N +b/D5^_nj7.rl+oWqoBo&e'ugjb/VKE +_oBjCcHX5?[D9A_]!Jrsda#qTccsPS_8uZ!qoBc!a2Z6CaMu9>`Oqdb>Zt?.;cch/?&Z80`Q--D +rlch6aMu6@aNDN>b0.l[D?paf(DR\t&05k?NiUINgpIb+'dq/D`lQHH`qmahb/M9=a2Pp0RpSc_ +;Ng&4rV$$Gs*t~> +o)Agkr;?QkrrE&qquHNkru^@Wo`+sjs8VomrVHKmqZ$Ne:+7V9+=A:%a8Pf2s8W)t!r`/lrr3,u +s7lWfrr3&tq>^?krVlrur;QZp#h8)0p$VA>iq36M"SME]q>C6l"9/2nr5/I3s8;j.r;$6iqW?8F +a1oL2^rFCIl1k8X%fZD+s8N#trVcWhq>:*hqZ$Hlqu6cop\OgerrE#srtG;(p@e@FbeD*=`5]sc +rVlWmrqaG0s8W,us8;oo!;uinrr!W3s82irqr[)6oCDPDl07$es8W)qq>:-j,l[]:q>:*hqtg +mJd1cpAZ<3rU\[=UYl(KqZ$Tns8W&rs8)cno1C8P)AjS86cAlrqu$Hqrr;ZjrsAT&q#C!cs8Mrl +qu6Zqrr;usrr!?'nXZ/AV4aT`WQFr0s8Dors8Dusrr9n8!<;los8Nu7rr;lZe&BDD_Ss:-`7aDN +s8MupqY^6grql]qrr2lnrr2rqrr2lqrr)itrV6Bl!WE#srt+PC^W+@4`6%BGs82irqu?]1rql`q +rr)d.s8)coh6`uVWMlhsUSt'(p\b!hrr`5tqu$ +n,NFd!;lcls8)fpr;SbUrT:LrKa2c-=cc",SbV'PH(`""u.\kG8bKe5L`Pod>^WamG +`QZ9LbL4ncb/;KDrl#eqaN;K?`Q69Jc-"#;ARf4^?=@YjPearO_8Z>ps2b5[_T3tA`Pojd*]b?\&,bm_n3Lf^W4aNcHOMSa2>am^^J-&bg4\ac-"#C`PqbpqT&ZVrl?/$aN;QC^rOO< +bKJ8^fZ(5#^;Rap[*7.Nb0S5H`Q#L1!QiF\`u;q<_8aBdF^A6L:K(A"A7^;)_SjI:b08)PaSj+& +aMkp+^W+CCFEVnlLCFq3$Id/;c,%cT:^\'FR`WCebg=N1`rF-Zao9 +o)Agkr;?QjrrE&sr;Q[!s4Z2is7ZHl/H#PCq>L3es82inXX##2,9J*X-"lZ?s8W)ss8Vurq>:3h +s8W&trVcWnrqlWmrr;oqrr38lhXpL4m-<^#rr3/ur:]g\rr2p"rqZBf_Z'N6rVdN.rVl`ilI!/[ +_SjF4_p%$4r;Z`oqu?]q#lX\qq"ORZqu6Tprr2fms8N#t"oeGurr)fortbV4qt^$\p@nLMcGRT? +`m"&Sp\k'es2"^7s8W)ur;ZX1q>1'grr2rtqu?Nks8D*AnF>o9p?;%krr3*!q"Xdcs!I[@q>C0i +rVufqqu?Khq#,=X+YHdop\b'hb9ePX;Y^7fs8Mrrs8Drrr!rT's8Drqs7u]pk3_Bnhu +i;Y_,pSOIkqu6Tkqt^9ks8W)ps8)`jW?3#q*#KV=+(OX0rr;uss8W#sq>:3hs8W#sr;HNms8;iq +!<2orrr*H,qtI^iTV/-XUoD?%qZ$Hkrri?"rVlfqs1A7Js8N&urqPa*]#2A&`l5m?i:m3MrVQQm +rVlKiqYpBlr;QZp"TJ>us8N#r!<2lq%dhTZ_o'F9oDeUcs82ir^Ae*2!<2uts8N&u'Dhb-qYfVN7s82ikq=aZ))]L/$rV6?kr5g*)(es`'qu?WlrVlZn&cMb0 +rVlcqrr)MpVOX +mf*Liqtg0dqu-Bk2XfID;//]s2Mmrs2P)[s2lk5`l6!B +cdL"IXMMop_7d4_^sUQUbK.iNb/VK>^V@Y(`6?QTdETbO_oBdnb5]Q_`rF!c_o9U3_o9[:aN)EE +rQGhuf[@OJ_7mFgfA+Zra1oX6]#Z)8a2uHEaN;K=b/hQAUN'F::ek#!=CtF<]thS+b0%rNb/jS% +,07d?^;n.7ajA8Yd+_Y.%37UZ^Ve4:UC@nI3RGp`a3DlOr5S`W&]W#;a2l +o)Agkr;?Qjrseu+rr)cprVuoVhZ*WHr;Ru=q"Xdds8W)sqtZ0s,UXZT.Nfstr:g0ir;Z`qqZ$Qn +s82Bds83B,q>^Kos7k7Ap[[e3kN2[B"TJ>rr;QZp"8r&nr;-Bts7cKlqYpNndJj+E(&Rt/qu?Wg +rp@_@a2Ym1`Pp$qrVlfors&K"pA=jdqYpBpqYC!brr;rnrV-`rq>0sdrr<#ts8W)t)?'F0qtp9a9`P]g7fB2i-s7QBjrVum!rr)fCrWiK&qZ$Tms8W&srVZ]mr;70+s8N#qrr<#Ukk"?5 +oC25 +m/R+b('":5s8N&trVufmfqbhCs82cmr;Q]ps8W'=s8Mupq=f^h+!DI:+W)8Zq=X[crVuirqZ$Qn +s82]mr;Z`prr3W*s8Vofb*&ZtUSslf\G,murVlfr!<2ur!<2rsqYga"p\k-grr;rCrrOnA)'5LMs8Durs8N#hrsJ](pu80ETV0-trVlfsqrRj.~> +mf*:crVQBi2".PXBs#V!b/hZC_8FODc-4GXcd:%dC'G%Z&/6B)$ZFV?bf[lEa3;WNbfS,Mbfn5P +aN4;!)T^(Dbf\&O`6HQA]T-$+@ps%\Ee2'G`l?*@qo\r[s2OlU#LC6,bf%TGa3VgBc,mrDaMl9E +b1Y7M[)C/,^V$hTda657aq2S1`lZ9@aN;QE`l5p;a3)L#b:q&]b/_K@`PfX,^;%P(b0.rObK7iH +a2>d +o)Agkr;?Qjrri<"rr;usrrhErp\Omgru1k6qtf_r,p4?V+CcbIH: +`PCL/q"t'ir;T%]r;$0`qu?Zgq"jmaqt^$Zp[dtGnF,l9mI'H5nac5Brr;uoq>0p^rVHEjrVQWp +qYC*fn`/$4b/hU&oCW(\rr3-#rVc`qdJa.Brr3)ps8;iorr)cpqu$BlrYGJ4s8)cqqr$].o^;;G +mHW:&s8DrbrVcchrYb\7rqjee,9eVbs8((e,pY)6r;-Bls8)cqrVlTi!<2rs$M3$FkiMjErqQBh +jo9i~> +m/R+bs8W,urr3H+rr),_U"9)8s8Dlorr)lsrr2p9rVZQglt@O\)&a;2,93r\q#C6kqZ$Kis8Dup +rr2rorXer+rr)NiXJM_bTr4`m^[h:$rqZKmrr)lnrW)forr`,qs8ULG(B+12s8N&urr2rsl,^cc +]u7h/_rgmnrVlco!<2ip!r2Zlm/I(brr3#ur;?R.rr;olr;Z`os8V]Ncb7B<_uB<&r;Qp!rVc`q +dJa.Brr3)ps8;iorq69lrVQQn&c(@fV5L8dVl65bU@\3\s7cQlrVZWprVlfqru(h9rV=DY*?6BM +rV4SW*?6HqqtpBms8)`oo`#I$s8;oga-i`lWnR@LrVuiXs*t~> +mJd:eqtp?hs8VsNp=+1iQG(#WaN2B=^Ve=DbKA#Oc-+8ObJQgU%Mg6.'G(A/cH=PTbJqoM`m2WK +b0'\(rl4uZ!6Y8a&BE&=`k''*An>:T@V9k?]Z'lkq9&`Ys2OlU#K4m0ai)3@b0e9Hb/qiL`Q-'= +ajAYa[_0i$b/:lp`R2cIrlG)]s2b5^4j(smdE]YH_o9[?d+-b+iS`VMhV[/Bf[eU&dE]nR_Sa:1 +aiqrRaiDE=^;%b/b0JG\ahkR)]Wg;7_Tg+"aTT]*a2lBMa9TZ,ccO#I`lIYk)TK_4b0A&M]:EYB +>[16/=&`gEZamrs^aSNpRaT'Bi +c,IK$J59*>E45E +o)Agkr;?QkrrW/srqud!p".ptr;-Eur;$-brVlfrrr*`6p]&E7.iBWY)^le#de`qBp\t3mrqZTa +rrL?mrqud#rqQ?fq=OL]ruV+4nE]2qeBlFY_8!^t^r=XNh;RG\o(i1Zq>1!erVHO1rqu`nrqu$, +^WaX;l21>Trr<#trVccrdJaCNp\amfrr)lorqQC!rr;rss7Z +lMpk_!<)os%K?>$c_IZCr;ZcprVlfgrt5#)a\F,o*#oY:)Fi'Jr;ZNjrr3#qs7?6irVca-qq%4. +USOW\YeKo1rr<#sr;QQor;HWorr;cm#64Gts8Mojd/F(Crr)j+rr;cWbeD!4_SO.5Ys8N#r +s8E'!qu6TrqY^*f%.`lQkMtOZhrEqip\k*k!<2cn#QF]$s8;Wirr2p1rTMM +mJd7dqu-El1B%%=ii6^2aMu09`l?!8_8X[D`5':0a2Z*;ahc8t2&?`C)@dMf/[!OKd`'ARai;0; +prNKXrlkDe&C8VC`kIgC?X[;I@W[@CaN4/!rQ,#YprEZ__9C'C`PU'E!Qr[db5]Hpa2u]Ubdt0t +aMkj%]Z&:Dbf7`FrlRjS`l5g8cd0h]da#tTbKnetkjn66mciimiSNJOl0e-6o]t)Td*L"_bfn2N +`l?0Fai;TLaiViWb.+Xg[Bn$+bf\#KaiMNCai`#P$-C$'`6ZNFai;?na;<"?`l#p@_o9a8STeOL +<`<%%@U!fW]>F?a+Ni$XccjJL_nj+*`7WX]";;D6fA0%9*t`qY`6--=_92YrrPnlYrQ5Jj`5]Hh +BMV<)M!G!>!rM`jkPp&~> +nc/Xgqu6fur;HWorVm#Hd/X.?qu6fur;6BhrVlfr'Dhb)rV;d6+s\!R-RU3\mIUAXoD\aiqXXXc +rt5)*s8LX?p[Ie9ioU4?rV?6cqYpQprquNi"TSDrs7lTndJa7JrVccqr=]#*iQ8jTaLoO3b4GQ- +s7cQmrqudGrr;onrr)ZgpA=IGg;gIhP*1iaMM[4KN/ijRLQ.UpZ-qLjlL"-7q"F^`rt,//qYgHj +ldi/3!<2lq +q#2'-s8Vlipfnsc,E0Yl)Bp["qu?Wis8VTf$30u&qZ$NJnE8m3rrE#Ys*t~> +li.[us8W&srr)fpqrsfQq>1!gqu-Nno`#U%s8)ZfP:IRl&0W/>+E$K7s8V]irrN#soDT="s7ZBd +hk[0IT;ncl^@M3rrr3#tr;QQpr;HWms7uWss8;`nq#:<@rYtn7rVulrs8DrUb.ks:]#D\6p\t0l +q#C?mrVc`ps8O8?rVuosrqufrr:K7.cG[T8^:h1m^VI_%]XthrdG"*_qu-L4s7uWns8;ckrVlis +rVliold*Se_SZ-Iqu-KmrW<-!rR(TJrVuoqs7cKlrVlTl(&n72rr<#ts8;iNZD4FrTrFreTtL!q +rrE&trVlfqrVulort5&#oiN1P*/DBU',i+]q>^Hirr;lprVuco&,cD+qtSd4V4adCqYpHnroX62~> +mJd4cqtpCMqt]mI_Ja;*`lQ<=`l?!8`Q6 +lMpn`!WE#qrt3uAkPtP]q>UEns8Mrorr2rtrVlfr(An.3r;?8<-m]fO*%2sYTD&<[r;HWpo)AUf +#lW3>n+#u2io^4>s7lZmqu6Zqrqliss8Vln#6"T$qu?HfdJas^rVlfmr;HKUaMYs2`l,[>s7lKk +rVufos8Drrrs\l(qXrpj[%3)+J:N4LHS^%rI!p6fJqJ]+It<<5T"3G;o(E%\qYg?ep\jses8W)u +s82W=a2lNDoDAIcrr)irrqk+B$2ac!s8)]ks8;fdrY5D3r;6Els82irr9!>:mI'K"-5n:-,psRbq#CBjs82ipp\t3mqYg`ts8V0=jlPM$rrDlVs*t~> +nc/XgqYq**s8Murrq<:lc2.>;q#13is8W)nrtPD2s82]cHm0UG'GVc1,Fe?*q#13hs7ZF&rVHNk +je&HBTW+ii]_;3urVllrrVQ]prr2rjrW`?$rql`jqU,9\s8Drsrr<#pkf:TY^rOF1e,T=Ds8Dup +rr)fprr2p-rquZjs8VrXgs*dR_T0^r^GNaB_o0O0_oBd8_7mXr^<>F*s8W#sqtg +mf*@eqtp9j0)5)-f5ken_oC!=a2Z*9_oU!EaM>L(_o9X;a3)<@dEBk\>nRn_%M'Ed%t;gPrkniT +b5K?YaSEsh`lGfi?tinRARoS,]u.b4rQ=KJ#04m-`Qc9Pc.Ct:gt^N0e(EL8ioK7bkiqI"k3)!nk25"LgYC`Nmd/iXdDEW9`lH9M +aN)NIa2c?Fd^-$"^3 +?WrDr^;It1rQ+oZ*6?.=_84%#_p-6M\c`QM&iL76")H?1b.l!7bK9_*s2b/YrQ+r[%EQH,^2[1H +=anoiqtL'Os*t~> +li.(dr;?HirrW2Ohu1-k +rr2rtrr2g&q"jp^nE%NQOG82_GS>4?GB\4QEcH&;FE;SGFa&4_GCfaL%%4iemIpDOq#(!^s8N#t +')qq1rVb:eGT=G +rr2rtp\l!'s763iq>-U*-QFEO*%pOTr;-Hnq"Xm`rr<#srX/T#s8W&ts4$?!i;W`XrVZZXs*t~> +kl:\^$NL)(qVn3KnG`I]rr2lr!ri,rr;Zcq')V_,pAb0fro8YR)As5))]^91rVllnrVuQi%KH4r +X.c)_T;AHfnc/F`s8W)trVulqs7ZEsrVufls8Vinci3nC&c1n;^r=@0_TUO's8Vums8Doqr[n0L +s8W)prVlfpqVf3.`5KR5`P]L0`5T[4_SO(-`59=(^r+1*_o9C"^te\`qu76)s8Dutrr)lrs8CNd +`koalrr<#rs8N)tdJa@Mq#CBns8;fpp&4sgrr3`3s8W)rs8:TSVQ?\oV5U>`de!M +n,ECdrqc]orVmZ3p$^tT@:ZdecG7]KaMu-8aND`L`l.krs2Z&"`QQ9:bfdrQ`cMq0)ANnt#mZ:3 +b/VN;rPnfWr5oYnb/h]<[qfGZBO,.^F1B9+aisb,m)TFPai;9Fahcn_VC,eD]9< +eBQ4_beqK?aj%oIai_u^]Wo#U[c#WXai;?BaiDfM#L('2aiM`Hao06TaSj7!a32QB`5g$C`lGi] +CL(7J_r5ScU*PBP*b0S/RFpSlk'F=sWaO85IaN;6;d`fqXb/h[&`r4!Y +apuV9`Q5s5UKUJf?)Qi[qrRj.~> +li.(dr;?Hjrsee[f)PdLqZ$Tpqt^-er;ZTl(B=:3qu?]fqsL"&+!i$Q+"11brr)cpo)AXg!o)`" +rq-E`Q,sKnc/I\s7lBf +s8N#tr"]#!o&dfWKn+VrF)c;FH$Xd[FEDYIrcJrsFE_tVH?sj`F`_hWK8,8]`oHIVq>:3lrr +nc/Xgq#C?m$1#I%g]%-Js8;iorrN,trqufpqu.?1qu?Qns7?'PQlm.a(E")8;tp:ir;-HirW)ut +rX\V(U7@pNVl$WXp\Y!jrVuiqq>^0f#Q=]&r;H?hpWrj;rt=eN^;Rk*aN3-0s8D`mp@eOcrr2rs +rr)j$j2ALC`59Cj_`>QK^qmn*`5Ta9`P]X3`5BI._Sa.%^r441_Ue/Ts8;iqrr;usrVHHlrr)ls +d(mc=fD#7Drr)lsd/F:Mq"Xmas8Vuns8Drpr;cirrtYM1rVccqq#0g/Y,A4sYFhkkQ07i=rr;rr +rr;oq(]XF5r;Zcir*^W\(EFZ-oDeagrqcZlr:BmfrVlg)qu?WmrPkb)T +n,ECbrqZWnrVmc4oBakk?]r(<_UHiWbfIc@`lQ[_&RCNZbq`5]sV&Wa3Mi@[Dg/(^pCYt +e&]PX`l?BH`P]U4aNVoXf]DDRbKeu.l0I['naZ5@lfmd)o(2PJo`"M5oCDA=oBto.lfHsMgudt_ +d`TSG`5BR8b088[cICIMWkH-icc=2La2uHMa9Ti(_TB[:cc,e$rQ+lY)9L+Ha2Q!6`5TI/\;k9/ +<`<1!AS,5P]Yhbfa9Kf.`l5s=b5TU*ccFMTcHaMa;&C#b(D0-]f#l+O^ +l2Lh_qYpKo%JIlrs8Vrqrql`mq>:*grrE#orYth9qu?WjoZ;2'.30WU*$lgVci*nArVQWorquZm% +fH>+s8N&urm:'*p[.8*rVm3'pA+^fs8N#qr;HKqr;Q]qs7lQtrVlirs7^T^rYPP4rqlZjiP!@Q_ +SsIFq#16js8Vrqq#Cm@Hn`RNON.l\&EGf`;F`qtSH[C$`G'.nLFa&"OG'\I^H?aRUGB\.NG +_(:/b3ns#s8W)rrt##'qtg<4aiDKfp&>!jrr:@E!rTiZrr3)qs8MrnrqcWsrqu]mrr2utqu6utk +j%O#q"47AdJj1Gr;Zd!r;6Eiqu.K5q#CsrVuorr +r38\n*]T;s8Vrnrr3#ur;?Tprq$/?~> +nc/Xgq#:a$r;>9EY42Jfs8MlmrrW2urVl`prVulsrtt_7qu?Tho>bhr,8M47'-/);bl%Jtt +n,EOhqtg0drVmB%mGbNmC8nkYaNVlVbfKe&,K@mNd*p=cb/hTBa3D`Qbf[nr:^[pH)&a8/%gf5^ +cH+,HaSX!SaT'6k_n2F?A7K+VBQ:Q$^rjg?b5KEW`r3s\`lQ6Dr5],``lQ6Gb!sYSb4s$Xb500\ +b/ha&aSs0cb0A2TaMuBFaN"2"4ibX_`QQ]KYf+Z&^pUl*ccO)JcHXq\bf7Q:_oBsPiU,:,](&gcd`p(X_ofs@dETq[b599[ +`rF-[apu\?`PKL5`OAuE7U;bPqu6WqrVlfurVQK]s*t~> +n,NFcrVlrrq>C6l#hmu/s8W)qrr;rr0DthLr;Zfop\jsgqYgHgqtT<4:a@81,U"0N,TnLIs7l?g +s8N&ur;6Eirr2rtnGN:kmbI$so^:o(rVm$"r;$-cqu6QlqYpBl&,lP)rqcJ$>i#/Sq>UBirVQTo +s7$!ks8;`mq>($fs8Mrr%J]turS#0.`5BL6d/*V;s#pAUpAFmeqYgElq=3@=ML^>1I=$9]F)l8C +H$ajaH[9s^G^OsfH$4=VI"$KfGC+RXGBIkDFa]"Xiq)pArVmN0q#1$]s8W#H`6-3erVZQjrVc]p +rVd&us7p]Hs8Dlorr)l]rX&O'G1??as8;orrVuNhs8W)tqu6ulea)Pqn+5l8eGfJ!rr;lop%\F^ +r;Q]nr;HQiqZ$Tprq9&**@Fk\rqZTms8W#qs8;lps8Vrqs7lTmrt>5)r;Z?Hn*Jp2s8Mrns8Voi +qYgHoquH`gs*t~> +nc&UgrVm!!r;?QnrsShrV5)u@s8Vuqs8N#t-N3uDqu?]qqZ$Norr<#prV>T6:*CSr*#TJ/*ZHA7 +rqH0es8Doqqu7B0s7cQlqu?N0T;/<\TVJ[No)8RcrVucorVuiqrr;in%fcG+rVL#,h>.*Js8W&t +qu6Woo`"dg$2aMsqY^Bmrr<#srY#8+s82iQ`5TR/_TC:)rr;omrVZ[>rr)lsrr<#od)3N4^V%># +`Q#m6_o0L2_n`t#_o0C-^qdec_E#BF^;.V&^r+.._Sj=,k5PAVr;Z`ns8;cos8;ospWgLla6N^) +rr;rqs8W)ts8W)tr;ZM0[f61%rr3#urVlfbs8Dp)s8MmmER4@Rs8)]nrVccqrVuZlrr;uss839! +f<;?PXei%nWgWGWquQflqu?Wos8W,ts8Nu8r:4NTq7jNmJR"arVulss82forUBi;~> +mJfEJqYL0js8DQPb\q!^[)0f$b0J5RaN)3;`l63Jai2B?b/)$<_9:*?D`8:E)&a;(&J>?U5h3om +c-=;MaMu7!`>HV9b/)BFaN)#I?=7;R?tOP_^!+aEb/qZoa99N%`lcO(aq)V<_8XLE9LZG3d)sYX +aNOJ'!R/^baT'Btbf\/Pb/hZD`l5d7c-kCq_o'^B`l7nrJ'5^S`m_W._oTj3\[9l1aiDBCcHt"f +d*0GH`Pp0[mbm$[lLOK3mHa31mHNs(m-3g-o&/aN;THa2>g7`l5s?aM#gC:n?Rpb/M9km^q#eF>u>'7>>e^+\]E":aMH +nG`Rhqu$Em#QFPms8V-;oDS\=r;Q`qqt^6krVuooqZ$TgrV?3bq=M(0,p=?N+!qpI.j?B?VtU/h +s7uEgrrW,qr;HWprrDimrs.';p$:i0jSSrV"o\H!qY:!Xrql`qr;Zd(qRh>Wm/?qYrVl]jrr<#g +rX8c$rr1hjiVWQNrr2lr')qq+s5_A@_Sj@2`r#B-r;ZfrrqQs!q#16fpA=7!W/#a!HN/@VG'%\F +H$a[UFD>,^;+X5K:KCb3CN"9=I"-KhH$=:KG&h\NIu:,;nF?PYr;Zfnp@eC^s8V`@_SO=gr;QQi +s8Dp$s7uCn?'YHfrr)l]rW`46[U0"6qYL6drr<#srr;lrrqud'q!6o3kjn98p#O[%rrrDpoD&4\ +rVlfo!r`&orr322*%2gJ])2L,!rVomr;Qs!q"t*eq>($hrrE&tqu?Zuf'Mkhrr33#rVuljp\Xph +s8;orp&BO~> +m/IIlqu$Hnr;ZZ/UqZO$s"40Js8Mrms8W)us8)]os7lWlqYpBb[:pj,)]0>2&euZ3,>Y--rr)lm +q#Cr;ZfrrVuKgrr;los8;os$iIVa:[J,]qZ$Tps82fqr;uuu +s7lTkrs\`$qqc)Ks8N#ts8N#sr=/f(s5qSE_Sa:1aSkl5rqd!"rr<#tqYgHkrr4\4cbI64_S*\" +^r+%,`l5^)]X=]-R@K\3S"H[g]tD"k_8O4+_83n&aMl0<_8=XXrVlolqY^@6r;ZNWcbRB=qZ$Tn +s8W)urr2rtrr2rooi>.Us8N#t"9/8trr2p+rr;utrr;utrr;utrr2rrrX&W(ph$a4rVuZmqu-Qo +rVuiq!<)iprr<#t'Dqb-maA)=UT:AnWhQE]p](6krVZ]orr)j(rr<#tZPb#*)S,_Drseo&qZ$To +s8W&nr;ZTgq>UBl!<2ut#lO4hTqA"'p\t0l!<)os"9/5rrpg#=~> +mJf9Hqtg9ks8)9AO(EXE`6--?b0.lD_8XF4ai_QAbg4,I`P]g?acT4+%h]Tp+qkS-$k4tCbL"G[ +_nXCoaT'6u`Q#p=c-F2Dai(Sj=CkcCC2]NO`QlcQbK7`oa99N%`lcO(aq)S5aN2PpEC@mnccF;R +a3+D&rlPDgb/hTBaSO$jaMu6=_ns1;gr2gd_T^9E`Vmeja3M]R_7.(qai1fpf#?"R`l?'?cI(%_ +bK7]Fh;[#:i9p+,o(),0lK[g,lK7?thq-Q1^:2)-eD'*OnFQ;IqtTdQmd'H?q=sF;dFHh;f$_UO +]Y__3bg+/Mai_6&\[U&4cH=PV`P_\o1WISY_naS`7ss.:_nj@8bf[oB`l?*>aMu<@aMu<@aMu<@ +_o'RH +rqcHirrW&go^Mm2~> +nc/Ufr;R!#pA=migXuK)s8;rsrVn2Bqu?Zos7uZgs8Vlmc[.2c+!_aS)^6FT+Xqlrp\Opds7QLol&frl(@Wm'@G'\CTH?OL[I"$m'T@3!"rr3,ur:g*err3,_a2Z'QrqZZpqYgHmrWi?# +op2RckkP,UrVu-]#QM5pKsgmWrUKmes8Drsr;Zcnrt=VXmHO<;l0?jls82ioq>^Blrr)lsrY,2. +rVHQooOo3d,Yn13rr;cns8N#q#QOW!s8W#ss7uZos8;lrrrhuRlK[F/rrrB$s7Q0bqu?ZppA]X~> +mJd1crr!<+qu?]SWi"JIs8W&rrr)jCrqufrrVucpq>^Kkrm,j=*#]n6,o.F<,TA"Tg@Y1?qZ$6d +s8;]l(B=.,s8VocSsZRIV4ss:q#16lr;HZqnG`Ifqu.*(s8Vll^qRRu^;n%>p\=aoqY^0hkfh,eh>[ +m/IIlqu-Qos7*R;B:?X8a",9SaiVWF`59R7a3MfPa3W2Weu`ahPj=aM>N(>A75PA87=@_T9pDbfn2Kn]:aMs2b5_&Br;;bfe#U3iQE2bfRcCa2@kq +rl>,_aSs0X`r*pX`rF#+_nOCAQ?#%)aiquK`lcHD`P^'Ge$??q_SO*mcILFbaMc3?_8+(8d)X2Q +i9]1@gZ7_jnEoc:nb)PBhS#SUXehr$Z`0n/\$E0=X0KFgiU,sokj7^%oC;DCm,[<\hsf^S`l6$A +d)aAKbKJP]\@T#\e]l7Z`5os<`PjXQb/hHFd=%3L_T0X5`lcQMaMu/]'["M:`lQ-;Lf$fDE7FFXs82Zk +s8W)prVuorpA]X~> +o)Adjr;QTn/H5MGs68Cts8Vuorr<#trr)fps8VrmrVl`ipu\JA,Tn6S*?QF@+snUE^\%O)$30Vs +s7cQnqY^9hrso&*rr<#rs5)W'oBti1g\q-Trqucqs60I^r=&T'R?a57qYg +mf37bs8NZ.s80F+\*a(ir;Q`rrr;usrr4;@qu$Kmq>'0M5p70e*#BJ4&fr2=?+]Y)r;HZes8Vin +s8Dutrr)d4rr<#ss7l;kSZ&BUS?T3lr;ZfpqY^?lrU9acs8Drsr5Xrr2ips"XNOqu-Hhs7!t?`k072aiD63^qQq0D.dWu +Ao)@/F`;>GH['XSG'7nHFEMDa[(sc&rkB\o`P'7,_8FCZs8Vrqs8DuprV?Hkr9;/@_)BP^Ls8)clrr*6(rVucprV?KnnbrLarr +n,NFdr;Rr;rTK)MH*P3)`lQ?HaMu0:`QZQGair5_cdBO`.M*=4*r[#h$ka$W6CuQTe^;j^bKI`G +bJsJ$rlPnubJqT5]6e1+@pWPcEk9!'a2uC$b5fTQa99Z,`l?+!b5]Koajk]dL5*sm`5Tg:^r+.1 +`l@tuqo/?MI`9@Ca[1K3bg+GT`l?9I`l5p>f>=o._o'-jaP"eabf[uI`4s%%`m)fWjPnJ:l/V4# +o'bf*lgNfLT:)=KYdU^D]XYA]_8*k$]XP>RXJh`/iTp""kiqL*p[7Y=lf6^]hpKNjcH")Eai;TP +f?CG,Xj5M?b/qlM`l5s=s2kkmakW5!>,ouh_oU'Hc,dusao]Z(`r=$d`l6$?Zq&+mc-FQ4aSs?^ +aSj-YaT'EYa;`+:a2Z$7`kS-Y@T$9/=^biBS&ik!`5KX7aNFM+s2lY*`lH$?`OQd*)AS1Sb.5I/ +cHseZb0IcCa2Q +o)Adjr;QZp!ri,nrr3/Yg%bUIrr2pKrr)ipqY^BhrVu`brPr_&,pjc_*$-1I*[agTkPt8Ps8)Zl +s8MusqYg +mf3:cs8NE(s8)/\T\TMFrr2pLrr)lrqu-Qjr;ZQ_r5NIt+X%dJ()Rr4)C/+Hjo=uKs8)Zls8Mus +qYgEns8N#qs8EN.rr;ok_jmKpUS+WgmeZq_!<2urs82?cs8Drsrr*H.qu#_\ggK:Brr2rtrr)lo +rW2usp](0js8N<%hdis=rVQTo'`S%.rr2rroZ+kf_nsOBs8W#ls8Dip +n,NFdrVo"ZrUJBQ=e3LOb0/&Rb/_N?_8OO;bh1=gh5XBf(*+>=(`4#)%MLgH^>-oTccF8NcH+&N +`6$'>`lQ6Drlc)$`Q#U&LhCL3@:F.q[(O;p`lQ3BrlXfQ+NhpN`P]^:b/hiId)\Ps9?52l_SsL2 +_8O=3`l?*@b/j=sr5a<+a2tthVf:D7b/VHBcd0VMa32u?^W4@-\[1#:b/ViPc,dZ7_o':1g$./3 +i9g"0n*]T-n++boSYE6q]"5Pj_8O@4_8!LgaN;B9^V%(`\$i6>fCnt2mdBB,n`KH5h:p]Hg!@^\ +ai)<>`lQ`^[BR?[fu;4Xc-42HaSs?k30?s?f6?Fdqr`5Ta;aND`Nb/h`C`lH$?`OQQj(D5 +o)J^grVm-"q>:3ln(dO3ruM+;rr;cnrqQNio^%Y_+se6S*#fk;+"/F_aRT0+rr2rt$NBr&q#C9j +s82WfrVm]5r:U*ds8U@6me#i3iVrlWqY^BmrVQQms760hs82d*o_S<8['k;Vs8N&urVZZis7-'r +qu?@mfOEh7r;?Qnrr3Z2r;50c`Pfd3d.[56qYpNnq"=U`rtak$K7&&hEcQ5@I"#H_,pF?I&I]$P +!WU0]n]/nmh>RBKq>LWn +qq#Q-@fQ<#rp0UarWrJsV3Th(Zhs[krr)lsrr;m!rql]prqud&mH![)mI'E4iSOM4!<2ip*rc3> +r;HQ`_(-[urr2fjqu?Wps8DutrV,H7+tA?LKoqt^0drsIcKl.ke.s8Monqu-Nns8N#ks*t~> +n,NCdrVm'#n\_$9q>C75rr)lsq>^HhrqQ$JNuoGe(`aA,()Io9,C@K$qYgEmrr3?'s7u]mrVuls +s8N#q%fQD"s8;ceP+n>=TVedHq>UQqqYL0]rr;rqs8W''p\ac;Z*S]NrVm!!rVuoprr;rprW<-! +s7uX!r;Z@fdpCr-rr3Z2r;?Qos8L]f_nj:,deN\=qYpKlrtbJ/s8W#I^rFC2_o0O0_nr0qAnPgr +FT-G9GBS+MG^k0bFEqkKH@1*dH$F@VI^Korr)iqmJ[%`$2sepV3BS"[/L"'s6osds8W'#s8Dlprqm;EX/`1u +WiN7lWqu\ms8Dror?)%;s8W#ss7F)U*h!$-r;-?krVuoss8W#ij,+j(KCSg;r;RT2qu$HnqXLe` +T:Ndiqu6Wqrr<#trqu`cs*t~> +kl1n_hO\2&]>r%mb=g!u`lPs?b/VlWcH:26%Mfa!)Aa/$&/5O[WQi`3bf\#Mai;6@^raU:cH!rD +`lcNMc-4#G]=PIPC0b:UC2]leaN)<@aN;NDaia1q(s:(F`P][7aN20?dqb\R?dm9=`l>s7`Q8%u +rlG,^r5\rZaN"5#!6Y;bIEKFScp5iU`mMrJ`Q6HQaMl6J_77M+`OT`5'++ceRd8 +dc9Z_qu66Pn+##6StMj[\A6,'^U^qd`m;i:UT;)NaiD94]tM"q`4NIgeFi;'lL=02oB5`%eEZ#H +bKe2O`PoX9c-XqG_7%&*cG[uO`l6$uaqVqH]Ml=1g<%@RaNDcOaN2NHbf\0'b5TTob/VHDc-FUe +HHIt-e]e$4mDoXWa2l?@`Q,s;^jH&c10Rg[>ZYg[`l,d7aNDZLb0%fF`Q-$=bK\/'&ds*%S9rr;ifq>:0orV60dp]#a~> +li/I1q"t*kfA?Q-q>C3ir;HWps8;ogs89?6+s\*R*#p%H+Wr=@WVZGcrVZZo%fQ8$s8;oprr<#m +s7uQkrr3f6r;Z]ps5E/9qtTO6nGiIaqu?]oq=smes760hs8Drsr;Zd#[mAZ#^A@g.s8D-\#Q=\s +J\B!!krVlip"TA5qs8Dip#jh$OlgO<0r6,*>!r2cmrVl`p!<2ur# +Q4:^KgioBRdrr3?!r;HZqr;?Tprr2QiJ +,~> +mJd1crVn8?hk$n;rqu]mrVZWns8W&to)JQn>TFt)+Vtn3+<2CO>H.Mrq>^Blrs&K"r;Z`qrVlfs +p\k+)rVQKjrr;orrhc'WUSO`abkh8:rquctqYC*\rr;rmrscA"e:PC2s8Drrr;?Tprr2Zj!ri/t +qu$j!rVliiI(6q*rr)lsrB1)Zs6[kF^q[k1lMph[s8Duss8N&toD8LYdEBYN^r+.-_RlIeA8?C6 +H?XLTG^Y!eI!Bj[GB%J=EHQR+HS'VoJpMTcHZjLVUUS@a\&H5%^W!n(dJs7?qYpEmrVuoss5(N+ +_uB]9rr2rtrVd$$nlXUq]Cu4'rr2rtnbs*ur;HTnrr)fqr9dSA^gd3?r;ZWmrr;lprr3i7s8W&t +s8Drmm_>j'V6$Sk[#X8DqtC$hrVclsrr)ir!<)os#5E6hj7N9Krr3Q*s8W)unE>6F)BBS?l2:MZ +rr2utr;Zco$NBu'qpD:1V4HB;rr<#trr3#trpp)>~> +nc&Ufrr<#srr4A7]P22d_8F:7aiMNDa2c-A_UI#"5mIl+((h<"*>T+u4G2&,c-FE0`Xg#(_T9X; +`5]pF_op$<`le;*&^8D5_n0YhB4POaBop*JaiVX'`rj?&b0':r"3Sj3`rVccjAQcXsJn +guI)JlKd^'p@@b]?&.obl>g1 +aMu6?`lH9Kd)eld_Tgb/VELaj"N:&/>Nbbgjt\rl+rZrl,Ynb08)PaMZ!*MHs"]@(G\) +s8D`hqZ$Tj!<)ZlJ,~> +n,NFes8W,p3rAsPhWOq=rr2rtrr<#tqZ$3fqLKk=+s7sL+s&!M-]HmSs7QBdrVucns8Dutqu?Tl +s8DfoqYU0frVmZ4r;Zf`j8/E?nE0KFs8Dfos8Mffq#(-ko)A[hrr2op&,l'F]AI)Srr;rss8N&u +rTX@]r<2Xmi`PLEr;Q[/r;Y0``P]X9f`(XFrr)lqs8Vfkrr3MaR=93qE-HJMJ9+OG.i\ro!"K#0 +!!O/Z&-N(M-l+'A"nhp."UkkdAo`-CFaeO\FFT+[jno&W%/p5#q>^9iccjPip\ame#5n+rO3qD1 +kPkAYs8E<%rqV5NfQYL8rquHgs8W&ss83T.qYg?irr;lprVuo]lgjQ9p$(qmrr3#ur;HNmrr<#u +rVuotq\T,.TDeffs8;corVu]jjb>',,U"3U++sCg!;uin!<2ut!;lcq#ik"3n,!(ap&+gh"oeH! +rqlWcs*t~> +nG`Lfqu?]n$,E.Hq>:3ks8W)ts![aDo`+]8+!M[E)]B_8+;uPah=gmDs82irr;Q`qs8W#srVccp +qZ$NnrtbS1qu-Nms7"!sTq%aJVU,#:s8;oqrVc`tqtg9^rr;rsrr)j-rpk9BgL&h9rVQQmr;HZm +rr)cooDK$orVlcmm8VnAl2L\^s8?a4s8UTf_nj40f`(^Hrqufos8VrorVQWb`50F0_oBO+^p/]- +Ao`*@H['jcIsZHfHZO1FE.!1L:2k9!I"$NjG^+O`J:)WgH$"YN_8O7%`PTR-`P_!@s8;cmrVuip +s8;o@aiNB7rr2rrrX&W(ora8!?hXHtrVuosrr;rrp\tj)rVZWmrr2loqu-ACXO_U>rqufmrr2rp +rr0*%*uu== +(*4 +nc&Ufrr<#qs8Q*tlAo8%]tVP.bKJ&Mb/VBD_V!:D"qM7W&el,o'akRi\]W=;bf%WF_SsR6a2u3< +`5Kj>`6ZHB`Q63HccsVN`it,(AR/VNAs'XDcHFPVa2Q!:aSs?Ra;E(Dbf\#H`PopHct9B+9YAct +b08,Pb07s+b5B?\aoKZ^b5]]cbrsCs`Q-KX9@lYUbeqE@cd0\Tft"Z3`l5X,fZ;C[`lQ'S +bi.R(h!==qna#B)i4GMPYIMHj`Pfa8rkq[GU7J-b_mFDZPc_R8_T'X;`Pom>_nWt![_hk:q"*_> +m.'K-e)f`Mccbe_*:dGVRFZ,"Z/aMu9k8Cu83_nj=3aN2?>`r4!S`Yuh9aN`&U +b/MHJe'HFiBrBR"[+!1=aMu6@aSj6`aMu3 +n,EIbqu6Tjs8N8]huE`Ss7uZnr=mO*,9J!N+<_pSG/3eErr)HgrV6E_rZ1n7qZ$Norr2lrrn$c/ +r9`e0s8Vojr;Q`rqt^'crr;Qg'*%n0r;$Bfrq`:MhSO71rr<#trr30"s8W)srVc`qr?_ICqu?]m +s8)cnqu?]prVulnZ\cY7q"k$bqu6WmnAWMb_o9mqrVlfr)?0R3s8W#ZU4IN*FaA1TJS6GV)@coO +!sAl.!"o>5!#-t!*tehf.Olhn#lju+"8`)u!>5J8&J6R-EHHJQDfg/LLofk7rVlg8rr<#sq"r4S +aSYi3qu-QpqZ$9^<8)-=r;6Nnr=&]*p&Fsiqu6QorVQWlrr;rsrtYM/s8W&trr;]1J(`:RqY^9d +rqcWjq>UEorVlNgrr3;ujR;R1mHs2slMgegrqH*`qY'pfrVn,Ar;?Qkr;?Tpq#CBlrqaii-mKu\ +,U=EL.3eWcqu?Zqqu?TmqZ-Qns8W!'s7b@>o]H2Rqtp?l"TJ>rq>9gaJ,~> +o)B=$rVlcqs8)coqs'rOq#(!gs8)^1rVZQ9,TR^=*#fn?,'C]pp](9ds8VuqrrE&qs7uX+s8Dun +s8;lrr7%(0RAldSZ1@no"8hrmrr)lrrUTpts8)cnr;Z]pr1K,*^M`rJr<`E$s8Dlqs8MurrVuor +-N=&Bs8VrqqZ$Kjs8W&ss8D\rKu7U%s8Vros8W&haND<8`Q\)Tr;Q]q0E1kHs813^`kf[8^V@U^ +B52@-H@'m\I!L$eI!9jaDGG%kIsZ3E@<6[ +nc'0us8N&mo`+sac=U:n`P9@2qT'SucIAj%%1X!g(_dGf4`l5s=rlXfQGg+4Sb/;B:c.@c(d]ca%ccs\Wc-=DPbfRrH +`Q#p:`lH-Bb/MKF`6?0DaN;`Qai)BMfV*]$:?_AcYJJ,lbJqT?qnYM.`P'Ilp[[hEk3VEq +f@0$6`l5p:_o'OCeD%12XjtV6_T0sI`m2]Z6ciB@g<@[U^r477^W=C8_oB[?`P]gq`>ZY4`QZ]N +_SjI;d`p(\Uik>78Cu24`lc$=sH[(j]*aMuNL_oTj: +`l?*Dc-=>KbKJ/N]u\= +n,Ejoq>UBks8VH@q#C9ls7uX1qZ$P!+XS0V*?6#hnFukWs82TkqYU^-arSM8Fj)W9Aq#CBlr;Zfqr;ciqrsJc*s8N&ur;6Nm +rVlg8r:^0gr;-Ha<8=::o_n^frV?BF`PK=3_<:[nrr2p2rr)ZmqrOf8E,9lLG'/4#,p3s.r;[Z: +!!<3%!"KPZ,UP)Q!!a\pAj@4:#Qau.rW!]7"9\f-#64f:*,9D3Fa\:FH[gsho^MbX8,`J^il'%' +qu?Qnqu-Efs*(s!AFfZuq#C^Bfr;-HnrV-3hr;Zcpr;HWps8)Wls8W)ss82ioohaZ!UlGEN +r;6NirV?Hms8Drkr##D0r;Zfdk4\H>lLrVuorp&G'grqXiQ/0H)] +*[2jK:b!>4S,WB`s7u]orqllqq>:0jrsS]'s5!>AlL4cQrr)fq"8quiqt0o=~> +o)Adjr;QWo#5e86Tu?^'rr;io(]+10=<&Fu*u>\/Rd^"Mr;Zcns8Drsq=4G%rVZ]ns8;Nhq>-PV +S"cUTZJ#-TrVuinr;Q]q!ri6"nbs:!s8Vloq#CBOA_c3_aT)#6rqcKks8Dolrs/Q's8;corVc`q% +/fi!rVcQjn57o7S,*$_$MjYUa25X7^ukOmrr)rsrr2rtr%S*Dd*08?c,[N.^kQQ>E-?SWI!KmVH +$Y!^Jp2!+:NgYqG^*e`G'J7XGBS@[H[:#;Gp%EaEHm"daMl$5b/V0:^qSn0q>^Ejs8Drps7kTja +liX%rs\f'qt^88\'ALRr;ZTlrt5,-s82ims8W&lqu?Wps8Murrr3#qqu6U.rql`ms8MafiRaods +82fps8;oprr2rprq?@*qtg +nc'0us8MojqZ$EURU:ES`lH$;qoBYtaO8X""qh@c)&Nj?dF$:_ccO2S`l6$5aSj9XaSs=#aMc!@ +bL+SNb.>/mCg:@SA9+*N_o0gHcHOB/`WF6(b43Og_p6]ObeM-C_cG=>>H-;rc-+/Jbf\',a"GBP +`l>s9c-FDPbJqTIa2cHIb/V6>_o0[KfgSufQA`5Tg;^;%=u_9K6F=(^Adirme#i* +p?9u`lH7Jl_8X@9cI^@WXgH'm`k]d>bK7`T@B7.=e^i6c`k]U1cH*lB`6ZWI_o9g;ai;<nt +b/VKAa2Z!;dDsJTcp-=5QZ(O&c,n/Ha2n8&!6G/ZpW36S(uXdY]YqM-cG[iJ +r5LY;bg"APcdBkJb/VBIdATMF$l^E0(D6m4(^q@;gr[:F][+pHaMu +n,Ejpp\t0hs8U[+s8W&ts7uX1s7ZK-.3]fW*?lmJIHpJ)qZ$Tjr;QWonbrOdrVllrrr35ehX^:( +jkfV.rrr8ss8W)trVlrop\XO\s8N]-q>^Kgoh=3'dp)YFs8)cqrVl]nrsJc(r;6BhqtKpdoDS[h +r;Q]mrtG>1BZ8V%rUp$drV6-+`5'41c2%59'E7q,qYBNbJTbsQG'SFj6kUIK#5\B9%0m" +o)J^g%KHD,s82`ZUTEPOs8W)ss8Dp1s7Q?',TIU=(E=Y5Hg:;(rVuoprVuEes8W&r(]X=0\sAqa +Uo(3>rqufprr;rrrr)fnrr2rgrrq>UDd!1 +FanX\H@gThH?aL_G'eR]GBS.RH$aghI<^"Y_8X=.dC['1_t=!+s7lHjr;-HjpWLS(qu?Wprr2p4 +j&k35M>I/Ks7QEgr;HZqrr;NgrVc`q!WE#rrrN&prr3Z1qu?]orr)llViT`kEm"FYs8W&prW)ud +rY>D3rVuAsU84T_WiN.qjS/]OrVuorrr2utrr)lr!rqulrr3Z.rQA[o*#9e5(E";e\ak.h)BJVO +s8VrsrVlcq"9/5nrr2p*d\3,#VVV4MrVcZnr;Qitr;QHjJ,~> +mf*alqYU0]`b]@^]ue@7`W!mWaT'BudDsk0%L`d_)^$7.AB;s@aNMfG`lQ0sbkT?\a;W(=aNMfL +bItZ>>[h8R@qh%>^;S%8ccs\VaSs3Ia;3(CaNi&NcT0P3`B_nrc,@`H`P]U4a8X.2a2Q!8`P]O2 +dD+#QbKRuI`l6'=a2Z6HceqU-`A,obbKS5Qf"/c3\@8`ldf%CpcW[@%c-FJM`Q8&$!6G/Z!Q`FXaSa0sbKe/GX)hKJ +nGiOfrqufr!qk17rr2urq#2*,s7uB_Te.<0*ZHIT,`DEppAY!arqZ?ZrW)orrrDrprt*TKo^1u" +mJm4_rr;rrr;6KlrrVlcqss^es7lTn%eBErgu-qYRed!]s8W#prVm$"qtg0err)ir%0$;#qY0jd +q>L6frVlfr'D_:0MVi`uqt^*drTV28`l5p^qYU]4fq>_63$ipD0 +(e/mW3#r52!sKf+^IgBq$N'l,":,>>"8i-`&Ih]VBRjoGH#eJ%f^8_=rVuforVulZ`Q7QFqtpEh +qu2;TflPC4s7lWor;HZqoD\I0YH"qup%\@Zq=sd^rql`ks8N)urVm?)qZ$7ffA>Ogli6bVqtg +nc&XhrVlg(rVHE1Tulm*s82cns8Dp8qu6?]o:.0E*#KG9,pJ6@rV6EmqZ$Kjrr2lgrrL3j +s7tH4USOZdWQ4Q/q#CBmrr2rsr;6?hrr;Qg!WVlmrsnc#=5;]cS=06RrVuorrVc`urr)cmqYps% +s7Q3]q>^9hr;6KlrtP8$[YWac`Vo`5s8M9#_T0[8lM:JYr;cirs*XhGs67A=^W4:/]!6itF*`1] +I=-BhI!]s^FaJ=XDN&XMF`r.WGBmQHDfTTVkirVuol +s8Muqs8D#qa70'-rr;fjr.M<-EQn4Xq#CBkrVlihs7jpDWjD3fq"jmaq>1!fquQ]mqYq'(rr;ao +f\YUgm/Qq\rr;6^)ZBU6rVliRXer5%Vlm:qZ2=7pq>^Kks8W)trr)lr+o;6BI=<5)Aa20 +*$DHSq>&;`)Bt4:0k$h^NjT:ijlqu6Nlrqufrr;ciks*t~> +nG`Lcrr"GDo]DBHL9\bC`Poj9`l?*@b0%iOcHt-s(C_?&',hVoCXLPLbfIWA`Q$-Gc2Gl`b5THu +aiqfKa2uB9Ti>]QbKnA8X3/H#a3hrOdq5E'@^=M7`6?KFbL"kbf[-USKT4.#daHUmdEp:h +da6.WbfdoEbK%cA_9^Ljb0nX#bhC7]`W!p`b08#L`l?*BpW39T(Wk"B^Vc^]?=-f3>@CKI]=Q"t +`QuHGb5KCFb08)Ld)sJJbFqQJ$lfrk&KVo/REE^rW"TRRFj.b8_:6l[bf.K=aNVlMb/qZ:Y$k't +<0$#6s8W&mpA4aYs*t~> +nG`dnrVlZjs8Um3rr3'!r;ZTl%/p5&s7Z3OGU=UQ*uY&&#H.@mrVlcms7-'us8N&rs8W#sipZ:( +l/(S%rs/N&s82]hq>C3k!r)Bbo);,Yr;ZcirQB<=guD3&rV-'sfrs8Dqq>^*Hlg!4#rr`8ur;Q?gJ,~> +nGa(!s8W)pr91#YkP5)WqYgHoqu.6,s7u]eo]:-<*YfS-+!;`(q>:0k!WN,krr^Hb +o:V_NX/2W,o)J^err;oprr`/pqu-Noo):6@qZ$Qis3,ZAgYbj!s7cQnrqucqr;$9jr;6BhqXj7G +rUfl^Ch%70eFrqBr;Q^4r;$Bmk$cc^AbQ-'rVb3`^<4F?rV?Ejrr2fp1ASu)_o9R1_6.ZjE-cPP +IXQTgGBe@VF*`7XEd#8sBR=iCG^=FQT[Zh]F*N1^H[L0erd%dsGBeCZF+AMAc+q!.`l5g-ciYnKus6ZtPKQr*CaQN6rp\OpgqY^ +nc'4"r;?Tpp?fh4@]$i\b/_ZE`r!gqajA/^cI9pC()7Ds&/GidLXF8fb/q]Kb0%s*b5KE]`Yce= +b0J#N`O3-dA7B4aA:1]&`l,m?_oTptaoKN^`pq,@bf@rOa478"f@eh>da5hLa2l9CcHFAWe^W's +e^Mdab1Y4oLJC94?aA&4dE9_TbK.TEc+[*#]hm>0bK&5L`P]QsY1(M8b5TK[`Y$hke_]Zao'Yo" +SYWU3]YD5#rl?,%aMl'DaJjGJK1CX+_oB[/NC?E^(5T-&_8F70_u7Db`k]F.`Os@(lMp\Mm-X6* +gtV>Ed)O&KahQ0Kg9dlocHF5ObL"$me(%%8d*ftOa32oceCB+07mo=$39AVRG\C>oL9/JHgW[pi +c-"5P`5][/dZo"-cbN6)e]c(P`lQ +nc/Re"o&&sma^J!rrVoos7uX1s7uZfs8VolbY9N-+sA!P,#?Y]p](9enbs!trquTlrr;?DqX00JI'!<<9=/jJ:(2('":!Vl^k"p=r2!s&Q=AScdAGB%hTJ\:@As8;iqq>^Ko +i5O(;r;HBis0@^=d9lG?q#CBns8)PZ>='f&;-m*e8l^qHZG*Z/F'r*jS^d6,s8W)rrr3<"oM"-+ +f4*\3rVcZmrr<#mrr<#trXo20s7Q6gq>^KegZn1mmdoYbrVm#os7lKhrr4/@r;?Bjr;Z]e_&t!N +*$\t:7j]WSpAb'ja!r)VR/d$]qu-NsrqlTjrr3#mqu6Tuio]Xcp&=skrVQWprq?AB~> +n,Epts8)`l`h9j8s8Voprr2rprYPV1rq?Blp\;p)-5mmE)'9qpdeNYrr;Q]qo)8Xbrr39%r`%M)hit#2r;Rl:qu?Zmrp.h&G?n-F<)lkh +=bsGZKni`=anYo8qu?Tnrt>'.e`:kaqu?Zf`P]F0`9$mrq>L^Kks8T6\g!aC&qtpEnr;ZNaQWcX&4&9*W5tFq6TWl)6O`Ff4FJ.1UqY^?iqYpL# +r:Wk@jj[_tq>C9crr)lorY>>1qZ$Tkqu"F+XK/:tX/VQWo_JO\rVllrrr2rt,5V?;s8;K&)]^"; ++CWA[,9fMIs8;o8,o7_irqcTjs8;forr3T0rVQWjs8Vo`Z(Re\_tX*/rpKf:~> +o)B@$qtpBms81c?>&1Ufb/DWLaN"(t(;n53f%/7#Y;R++(Dml&%Q(E8b0\>KouR-P+iqjKbfIuA +_gkm&@U3\dUVY*jcc*f>a2Q-Bbfn5L`PojfaC3*7`luWZ50_g;MilK<`Pp!Eb0ADde_JBRLL!>1 +4[2+mMFMfdQ?d^C9<6Rpd`Tt[aiVWF7ag70U=@?,e[WW:]WArodDj8I`Poj:b0AGogYM8eoBYK& +N2*_m`lPp0+iVUJb/D0?aj$lE7!:e;U;"jsaMai`*Fqi4OKGq)^VRhd`Flp+`59R7`koFhq>9O@ +mITbpi9A\8`5K[2c.(OdX0gC)ahuQ]PC(Ws9@D/+bf\/]eC]:@0cMc5:d[*%7Y$\rXeLDT9gr<> +[F*X[e]u=`c-FGW5M>&>9u>3$`l5s=b08#NaN4>&s2b)Ws2YVkaMc<>bL=MM^O]?er`'8->ZeAD +_99XAbPoZ`a?Ibf_8jR?b/^+[(E~> +o)B:#r;?Toq>^KMhuM31r;Zfor;ZZlnZ3rV,p"!J+WkF_nc/X[rr2p5nb)eZs3^H- +o'"p/s8W&ms8Vrmq=smerrW2tr9sVSs8)Qkdr!t)fhCl3o`+marr;i]`f"20Eg4)\h;@&Ies/m" +;'5r#>Fa8%UIuW(EqZ$No +rqufmoZYGFrVlTjqJX:0a^Y&Eq#:-il_dd!5%&;PEH-)&Dr]L.h:p`Ah:^>jS8M;EXQ9E]p]'s^ +s&.P'l&hk +s8Vji+=C[Gq"M?B+Y0nos8Vq,-m0i3s8W&ns8W)tr;?Qnrsel(s8N&fjQl*ms8W#kqu?]n!<2`m +J,~> +mf*^oqu#TWY1`[Ns7u]nqYpNp'`J(3qu$Hirpm-7)^-(6)B9]/l14?D(&n.2rr<#kjH-41V5LKP +p%\L`s8;cmrr`5trVlcqo);5Ys82cpe8=%'fM1i3pAb0hs8Vu_`J\).E0@WUgtpiCdum3i;-@C5 +;--S#`Urs"rVuimr+*t(_K#8KroV)7^r=OmqZ$Klrr)jPrr<#^]YqY'^VIX.EH5fAH@'j[H[L0e +H$OL]GAp9n]23?gH$ORWH$+OF]@NKH$adbFa8"L`59@'`kf^4^A.O(s8;orqu?Na +bfT;Qs82`gC;8]MAGlK)rqQHTUJYYc?#Fe(E-#)uin*)Cg=tH>g"!^*D,Y8Khu3TIs7lQm;V0Oa +O/DdRs8Dutli%q%s8VrqrVQQe^p0]3Xe;_gW:BcQs8Duss8Doqrqud;q#CBe9,f'ad/!FG*?$6- +pAb-c?m6L4p](9mq>C0hrt#,-rVucns7cK:URdsTir/fT!<2QhJ,~> +o)AdiqYL3k%/[t^@Z.e;cH">TaN4A's2b5[(;n5;`Q6?KccCnH#Sn0j)Aiu_a2ZBHo#;iob0S/P +`k&3OB3o"VC7;WGbKA/M^Ve"/aND`Nb/V?:`q.8bb0/)F_UPu%gZI!Mf@S!^^r=^Qe&\D)7nR9G +XN9#If@A$'6pspYB3\2,5tS'(da$+^_77WcbML!@e&feU\];RtYgCP>`l?!6_8XL?f&,6*lM:JN +lK"J4Z*VKd_8jX)Gq"BSaNqoHafpd`4A&(EPJ4WB_8j^Dbf\/F^V[q6aN_T6 +qs`q6o^D8+bjjK9`jrh!c.U[t['A'3behTRU_:mVdd`9D^8D;c7jFW?=c,RW?aND`Nb/hZHbf\)LaMu6=`VmdpaMu*@aiMiS`kem% +@o?0+9O2%C\&5c&aN_s.b5B=;a3;TPeC#rS$q%&@e"I6_'p?npf@`h3$3no*bf.QMdDN]5`QZTJ +b/MEI_o.I3853j5mJd.cr;ulmqt0o=~> +o)Adjr;$'=`7<(iqiBDs6kgNs>Q=s2$kj[sH?+7QFEiY=lLaoP +rVlcpq#Ag`i;3?NqY*kHhqV)sp\40qHX)kD7q?[[FDPo9JC`]:i8NYQhqm;Ohq[#D`/SJFVW[UK +abAO/jOq.lp\jgbr;?Nms8N-!r;HWirtGD3p&G'gs8)Njs6K4Ime5r4dd[58!<2uqs8N#q*WH$; +s7lWk]5H!kqY^<^:*CoupAY*edNKh]T)/<^s8W)ur;Za-s8;osrVuiriTTagnGiObrquctqt^6c +s*t~> +nGa$us8MukbbMoLr:pUC(p&Bgk*uc+7*?QAso(r@`rrE&rs8W'"s8Mlo +rso&'QCsqGW2R`/s8;osq#(-trr)fps8W)ts7$$fs$QOWOk\XHI^f1Qq#10RX(bUYWm08QhV[,C +gtpfDfA9;=BlJ$,D,s_T<0Q;;p\Op[H*Q2V^Hnr;RE*ahPO._S*aMBQ&'B +G^Od[G'X(7ApS`PHuW\o_4t+UGBA4WH$sp\FaMWeDKp5IH$OX[H?spaH[gEeF*i+SGC@BN]YqY& +^r"Ius8Duss8;lls3o$Fq>L?iohOE$hGWn>q!Q"D=HQfU@rZC,C2Rs=eDK*=hr!;I);X30hqQr@ +_22]6U>tkBbD=p1in1egq>^9is8Dp!rVlfro`"mj)u9O4q#CBnrq!4dVkgMpY+*<%rqZNlrr<#t +rr)j>r;HZjrqX*?c27J>s6rZ"*bFs@rUnGT*?^gGqZ$Tnr;HZprXA](rVQWgkGb#DSB(U,s8N#h +s*t~> +o)AdiqY:'i%-sQpG.#'"d`fkS`lS,$*6,t:ahu*\%2KBP<(*_8X^G`Q,s;_oBd?bfn5L`PojgaC*07b/_THL5"=9gN9:KfZ)+b`f*VU +@[OpXfA#-;hV[;Ghq6pgEcZPNDer3B?9ieQeB>q[a_eNIeO.j6bJCs8_R[5(d)XGQ`PKC1b08N$ +bh)%WrUK3bOJK8$^r41jaT':u_7-nn\WL-1F`0pG^VnC:`m)5nAICh3.XReo]Y;A2e'H1R_T'X9 +_og'E`O51,l1OW7k24k_g;:P7_p6o^fse0,g<7a^cTTq6dm2F4cGc8S4ap0i?ZL./D/jTIfAbZE +i8EMJgYLfDf@8:h\pdLMM9+2uUM8/]q8O5l`lPm;bfJ2T_Rcj\ +=]\C#<+T6f]uS+kbQ,faa>h>``l-$I`mE(r8ug+Vb0\Qr&en3me^i6X$P*YoccF;OdEfeH_8XR@ +aMZ3Db0@`$?:mmkRH+&?rrW/rqt0o=~> +o)Adjr;?Nns4@MOs8W&nr;Q`erYkh8s8N&SFrVbL*$uaS.(T6RrVulrr;6EirqcX$p](9Yi:6C' +o]6#Orqucprqu$[8,rAaq"p,JhVI;M;2DWrSRP`ZbMV(-jl5+Rgu7)Lh;%#Mhl:1+E,T`>DJ)qq +V.+2*][EN_i8r^.g@tK_aM5[2nc/UerVca,p%7hBZ%IFHF*<=3+;PCirW!Z6!!NQ-!!*-*!u+7X +gu)L,(]aU:"9&9.#pqjmo]&"a%Km(=q>`na#RCM7#o*UnDf9fDFFAXrb4,<.rqlTjq>0HtgA:jE +s5RJ>h;$ja>$+^V[F\A^CNFT8EH?"p`oPk3i8!&Ii8*PNrnnRKhrNM3P?q*^FJddOiS;"qp&"^b +rr2fpo)8^gq>:-j%/BSms8V'Jmd]f8kgTJ0"oeDpq>C6ks8)^2s82fprVcE!,p+D`qY^BhBd+iK +qYgEno`"ji"RY.8lJ2C=!W;ios8W'!r;QHjJ,~> +mf*dqp\1AVh!k@DrVuors8W&srr2utrquirrr3N-s8DoMEYf`4'HS)5,.7CHrr)lprY,81qYp?. +R\6LLWN5gns8Dutrr2lrqu6Wqnbu/\qu?NjOJCR`i7q.b[`"hK=c1D[gY(`JhVR&Hh;d>Fi8NLj +@;g%#Dffi.;pV:JBpQE[UL2j;gM5O?rPQ[n`kq*Aqu?]qrqcQm9\dG(a2,F)Lh^pTI=-?cG^"U` +H@($aI!g'VLWIYEBleQGH?jg`H[Ks6bgOj?F*W4]G'nR]H@C?=^RZIMfSC3"?4EH?"p`oPk3i8!&Ii8*PNhVR/GhVm)*O^([Y +Ff=$Phq>MjpAP!j!<2opnc&Of(B+1/rr<#sr7RmHVQ?i"WLE&MrVZNkrVm$"r;?QorVlfp)#F73 +rr)Gq*Z,sHq"t*cA/l[5p\b$jrr2lrq#1Np_kE`pU$MXUrr`9!rr2QiJ,~> +nc/Xf;?-LWfP+cc[E$D3b/hH;`QHEKb/VE?aN`&Wbf7WDbKnG_[oW\G'G;Au(DN=^`5Tg?bfn5N +aMu6=`Q$!AcGdi>KNqhj>]4_t_8O74b/h[&`rF-[b5]Q_`pq,hc,RrJdVt>ug>q-dJ>T<";b__W +e^Mq(h:^T;hqd>KgYq/KX(66jE,]r8@SY(8<*+I?\s6[ak0m5;ce,M?`5Ah'd)aS_cH3uFb0IoV +e^N[Jn*TYSR@pRc\A651_oKj9]t_;"_k1F4DgFWnXT$7d_o9^;^P3(l9kPr$Vn]dSe'u[^^VS"8 +b/_93`lt^-r:0FOp%[b"kiU.+_8X^Jc-XJ._;!GSbIC6sh;-dT9gqu'AoMg1EH?"p`oPk1 +hV-WAhV7,Fhr!8Cg>'uaKhk$%B:[]6i7+`;cHF8H`lQ#hTAaj/,S +aMl- +nc/Xg"oJ?"qV1$/rrW/pr;Q`erYk\5qtpEnn'Y#W-QXE[)cHS1rVulrr;6Bhrr)iurqcTmrsR!@ +o'u//q>^Enq#'+M7eQf]oX'G6k1o(MX+GGYa55q0hrN_LhV[,Kh;$fFj4iMIh,%j0G'.JAF)4V$ +g;]V3BSPI$gtlVqq!Z>>^Ve1fs8VrlrVmE%o(UUlH?O.OK4ubQ%gW48!<3*"!"fA>$NLMA2dG>+ +b;M!d!<<*#r;[6H7@a#:`%iJF!=&T,quC%($4$V3"pYM?;J^Q*Fa&7_TBYqCrquZkr;H34b5_28 +s5RG@io]+Ig"#-:hJZ))FEMAAEc,hqfA5HCio&hSgtUWBh;@2Jgu75Gi8!)Kf&>ZEjPu8ps7cQi +rr2fpo)8^gq"amg&,Gtss8V]SmIBZ7k31FsrVlg"r;$-brr2rorYPJ0s8W)us0E^q,[pTMrr(1b +,:f2[s8;Hes8NA\l0R[/s8W#nrr3'!rVlNjJ,~> +n,Epsqt\O4Z17ems8N&urVuorrr2otrr2j3rr<#qrqlTms6\-**?Z.:+r!cMs8Doqrr)j3rr)fp +rqcTnp?7a@U84]acM.>\"BQcS%FfAGZIg>:`Bj5&MGio/SIg=oA9 +FEDS9FE)%hdb2U0J7sYngY:Usg\UE[^r4@*\HuF4?bfmasD0'rJH[:$dHuaCGQd*Xj>'"q/IWg9gH[gKkGBA"UI!9aaH+:f._nO%,\]+UJ +rr<#srr5@WcGf\Ps8V7hgu@MReC`:/f@t+7CiaZ5F`MA=<7fschW!JQi8!)Bi7m)FgtUcFg#(Q< +jOW;MgYh"LU]:2jrr;uqrUKmdrr`5sr;Q]q&,Pk9XJi5"WM?Dhp[S7ZrVca!rqu]ns82crqYU3j +&?cin*F/F?rqstX*@7!Is8;He#Q3PYSt2[rqYpKsrr)iqo`'F~> +n,G?Eo%g!ODn*p6aiMQC_Sa@8bf\#G_oBjFdF$1X`Pp!=a3`>UYW!(0&JQ2n3R5R`b08)Sbf\$) +`Xp50a3DE&QBASmjN`QH*WaEPkGd*^%"E7<_#l.`4cBo9d_j42uFhUpW>g>Uf?g>LuEhqQp^ +Bm"Q?B6A61:"._0Nc8bmZd6n;cI1"\`Q6.Go^hhMp[["_ +ma/kl`lufSccNAqj2fNc[8#B9l/:(Bd+cpuB1?QSFDZ/@Ci3/$gYglFhVd8Eg"P<aND`Nb4!FOas5!I`Poj:aMYQFAm/&(9h\l(^:hn;bfIcB`lQBJaMuBH +b0'_-(!F\9^WP#i$m-3Te'uOcY6GRRGgk"/a9f\S>tn.*Phkm,r;Qirqu-9hJ,~> +nG`^krr<#Of_kaK!WN#rs82fqrV?F)rVucnr:p9boq3]X*ZZrPqtp?krVZ]ps8W,u!;lcq#2eA$ +n+,r,rVlonr8R]Hr;ZWjs4(oAiT8qXfA,h&AjP/JKh;[AFhrWnSh<=1-=1R +k^5E.$Np8+%L*P346l_/Sj4A+"9Jc7r;Zm"#6b#+r(gYUrJgYLrDgumSThVdJUhr*MQhr(!h!ri)prr38^lL=?5o'4Wkr;Qlrq"ajes7uZortPJ-rVD*n,TnS_q"OgcC*4cI +p&Fp[rr +mf*Ihm\co2qYU9orr<#trZ(t;rr<#trVc`qs8N&rrr)Wjo^\Ls*Z5eF_#=<2rqufprXeu,p@t\[ +Tr4Zd\b#Ups8DuWr^ZndrVccHC>7V(guHr>i8EAMhUgiCgu7)Ki7QrKj5&JPh;6q"CN+$2FD>i5 +DEuP*hs8tOf\kZJCYAaK_o0O;g\q*NrVulr;Z6Ojs3SKt`50?uH!tT5GBnL[G^4O[H['XUJoc$6 +b/_JDDfU#GH@($eH[:'[I88/DFu_niqOrVo@arVuol +fu*(Sq>^@kf@Sd5io/YHb;_q6E,fi0G&_S>@,'Pti7m)Kg"bcJj4rMNj5/\Pi83GIh;6oJhV-^b +p%eLbrVulrs8DHdrr3*"rVZWmrt"qpYH4b%WNW7fa85Z4rVlcqs8;]l!<)os'Dh\&INTUJ--Z,Y +s7p7F*[(%Rqss^kr:RgtR[pGLrr;ur!<2WjJ,~> +nGa[2s7=BRAsUBXb/VNEa2Z*h;dJKgumPO +h.UVECNai9F)Gn^d+d%*ce7(5gXJp(g;pb<^::f'bK@uPb/hTEd+n0@jRhp)mGkOJ\AQG1a2Z?J +aiV]B[CFPdJ1bbEB0UU9]Z8(3^V@\(`hIer@Sg<-=c0i9]tr(3^:qY1aMkp/?c1$t]=IY+p@e"H +jN$''`Pfd=bf\8V]!'K1_o9b^cdLM$i8*#;`\g)'DK9f4G&_M:?J4,lhV$ZCf@o?BiSEDOiS<8H +gtLT9g"P':g=+A4e&oVP_oBd?bf\)L`pq.Maoof*`Pojra>L]9C0sq89itS!N5W`\b/hZDaN2TL +aN2NJb/h`LccsSL]>r<1(` +nG`gjrr;lJgAh3OrVlicrW<#tr;6L"p%a+j-XcfJqu$Hir;Zcqrs/#CpuCc-lM1AY!WN#Sr^Zhb +pA]lOg#_8Ll.b.Rhqm5LiSEVQh;@5Qin`8Hj5/_Sf5GYMEcQ/DDKg&D8"%o]inNMQhU#O&psmI+ +_T(m=r;6Bhr;HWp,l-AdKQ_3VLhJOf%g<+Pq=!!30(!W2p-"q*M?FF/1RH\\9VpAFsiqa15XkfDlbqYg;eQJp]]i7m2B7q[$`EH?,? +Ec?2=>M@uiiT&bRio/kNhW3PLhr!ANhrEYUgudPMiT+J4p](9gr;HWns76-kr;6Bjr;Qcorr3T! +ipl[3q<77qs8W&ts82Wequ6WlrW3&trr*N0qG&.T)C0.ss7uYt-6OhLq!n@_rs7]MkMlL@rqQKl +rrW,qrqHGC~> +nG`mls8C9:XRc2arr<#prqcZnrWE3!s8Dip#4hJ9.jSEqqYp?ks8EQ/qYnO#Y+DSfZJbc[rVccR +r]pG^q#?)OfAb]Akh=tPi83>MiSEVQh;@5Qin`8HiS<;KeST5EE,]`dnSI=6HeGBnUeIscTkEg=\V_na1/`6J;]8,iDarr;o[`o6mms8JdXiSWbU +g>CCI@rlI)EGolC]Ej5&_SiSiPJj5&MIhV[8JinreNjknhLi*Y^Bs8Vons8MutrU9aa +rrE&tr=],,s7u]>Z*(+-Z_ajpmeHh\rVc`srVZKj!WW,srt4us)&Er-C&@r+r2UqY-FW[Ko)9-t +q;.s +nG`gjs6ZOjE43a,`W!mSa&qoB>kaNDE> +N*("$@qKkV^rFI7hT/sAc,\FHQce.@gZI)>fA#*9i8<;Lh:pcDio&SBi8WSMimr!dEcH)s8`mEf=g%>44nEnDnXM;m%ccX;Lrl]?(`jiabR6#(c +<,OUO[_^#)_nWt*a2Y3A9ik(U?q>%1]#)"oaM,C-aN)9=_oBX0^U2JimIT`:m+KPJi8`l5s=aMl?9^kQB*r!cca>G`R**l +&JYZ`9$bo(duFtL&spYYo#M-V]q&8!9k1Hlqu6WqrUg,?~> +nG`dis8V?Dn,NFcrr3'!r;6Kmr;ZNj%fcG$rVQWpqZ$?:E8^cprr2os!ri,qrVHWnrr2p%cL(2m +m,@U8rrN-!iVlsXqu?Nmq"@kJi8*;Uin`\PhV[;Bg>CWDi7d&>jkStVhVI8F@rlX2FDYu?DJa?2 +%RSsB0KdE9ALdf'4Gq>^Hmq=sa^n?RdrGB8"P80nlOqu@9/!WW3%"9T/\3MGj:n>Akn +pAbX.$8Z,PqXWBG.MNO+"T/6#"9\N$rsB*>Ci=H?E.sD)o)&Fd6i?o]ouu"as7H?k:T3+(hqZ.E +4`YabFDQ2FCiF968_r=]hW3JGiS3)IhUpN +nG`dirpm@ma7oQ3rr3&ur;HWop](6lrr3H*q>UrVHNjs8N!-p$n?CUSauecMRY? +qtpEOr^Qhcqu?Eb=PVcegZIAHj58_NiRQZ>g>_&EhUglEjknkJimqOXDfKf5DKK]1DJ`@`g>:uJ +g#&?dr;4j]^;%_]rVufqrql^(f"/H/a2Y0+C2\KKI/\I\H$XjbG'%qRG\*;acH2;\FaAC\H[C-f +H?sgcF*MKCdEBRFD0'fDGBnRaI!U!]H[L9eI<^3]]>DD)ah>U*n,<4brVca[p<;(bs7ZKl9W?n( +hqZ.A3GrnTEb]c@CiF968_r=]hW3JGiS3)IhUpNGmp]('gs82cqs7-*e +rtGD1r;?Nns82T.XK&=tYGn/#p\k*jrr3#urVc`pru(h9r;Q`rro57E)'U.Cb4u#/^*FI1=7H7i +o)9*ro +nGa*rr7u[-MSREZ`l?!9aNM`IaSa'Y`r_o0F1_na.7jl5:os7>U/X.uc._T)PnFND\Lai;9,Q<6sa +7oWZ!T>&Ch`PK:)bg+2FXbSQ46U50BG,)Rea2G^+_oKg=bfn2FaMl?5o^V,2q!74nl.sP'b0A#N +e't\6d*9&Eh)UhIcI:4S7Q>=.E-6#CG\h>8E_"m@gt^oCf\bB7inW,:gtWXqe(<@'gtCH2fZqs( +ACSfO^rFI8aSs?^a8j6KaSj*paNDWGaL\U"@9ZZ)>[C`\\A6,,c-=JTrQ,#]rl,hscH=,FaO%p@ +(D[u&#H?&#cD)3d$Tu5J`q.7X`l5?!:/+c@hY@*Ms82HgJ,~> +nc&dks8VuSj8T&YqYpKrrqu`nrr)lmrX\r(s8DorrVlios82]hr;6HlrVlotr;QKmqu6Tp#NFh7 +o'b`"s8N#rs5O#DrVuirs8)`f=ch%ef%f'3`j)8/U8bE;h:1Q@inrVGgZdMSgfAN8DKU#@D09iC +E`?FJb2h15EKKkjqS^7pc.r;'`IUC]rqcTjda@shs7ZHjN.%Xj +AnFnQE-6&>G].D>EGokpaQML=g#_5Pi8N.\K5".Q92JAPBOQ41It(p0CA@i/r;6Nil2ChcrqlTj +rVm<$qXsmghs^+*p$;>%rr33"r;?Hhrr;`l('"71pTP_^)BpCNAbGbl*?udV.fT86rX&N&s5W\: +h#IERrVlrsq>U-gJ,~> +nc/Xg"o[;RVsF3Yrs&K$r;Q`qrql`qrr)lsrr;m*s8N#trr2rprqcKer;6Hl!<<#sr;Qcrrr!;1 +T;&'RVRQ*ps8;]lr8@QDs8N&uqZ$4iNR$8+f\"`nZ(de`WNjJ!eE,HEhqm&>kM"qL<,uhlF)c/7 +F)Q5@;InLrr2ipo`'F~> +n,G$4^h-rX`m)cJ_8*t4ccX>K`lZEKc-",F_8F:8bf7QD`5Km=aN_iPb0%cIa3+D&!Qi@[aT'E^ +bRhq:_fK-k?"%Dn[(s]!`QZH_aBQX*`5fs=e'U@,`RW;be^;:"cETaO"16dam9[]>V4jf#c+O`PBI3_T1-beaNB4,q& +F`UMM:"7eEaMZ-8`l?*Bb/hTBn&YLJ435Rec,[r<]qAkCF+KEW,.((JO(`pq+T_i7Pg;.mQ5rr3#qqt0o=~> +o)B=$q#CBZht$gIq"Oads8W)ss8N#rrV?F&s8Mrnrr<#sqtpmkHCij\Uj'Z)->H.GorThVL`6@6@rV?6gs8Mrlp$ftTI!TsbHr)=*!r2g+!>Dg$>FF)PoCDK0i;C7<`.iTAhHhQ^K`Dh*S*QBI>lO%)`/;Hul9m.^JRrVlin +s8Drqr:9jdrttY/qu6WqrV?']rr;HOnbMYGoBP]Bs7cEirr;olrW2oqrr3W0Cb$kYQmEq)8Lbc4 +)Bp7OpAOR]#lOJts68h=jQudD!ri,sp]#a~> +nc&gjrqaO,]);R-"9/5rrr2p'rr;urr;6Birr)j#rr)lrr;6Kn#Q=Psr;Zfms8N#srr`9!rVl`p +&cMY+h3"P0V4apCs8;opr;ZZPr^$G]rqcZpp%G4e5".b%78d*.CijT)=\2Vh`o5S0jPnqMgrrH1 +FE2)5G]%G@C3"8NjBl,,>H.Jrs6IbK^r+t)rVulrrr7E0^Vdn#_RFl#DKpPXI=6HgH[C'aF*`"O +?d6[8aKnicFFAO`H$O^^G^4XUI",mfeAoeSVd".`GC4[]H[9mZG'A1ZH$4CXF1'-+_T'O2^%_I) +qu-QmrVbQrr;Zfqqtp6*NL\S05\k@[E,f`1H>RVDD/#Egg>M)FfGiYN?qbKiLm49iOHGSl83]mQ +8]9lbqtBses82fqs8MusnbrRfrr2otrr2iprt55-_l]rBWNN+mZg[nnrr)fprr;usrVloqrr2p2 +qe%/s*KhK`+[RCi,nV%4,kh6;oDT7"r;ZDrStM^gpAP!ir;H9fJ,~> +nGbNGjd9Co_p$?J_Sp@al1rPANO0#><; +_oBa!J0nr'85EYeQ)qgCa1f."a3W)P\AYIW8nC:]?"QWk\Al_,^Wb!GrPr$`aMuFdFQm?DM#g.4)/n_F`q_?HY[M?CM0!_f\YZ>f%[?68o8s.Od2)mPEA@, +5"\X7\CT6]c-48N_8sX9rQ,#Yn&YOKs2Y2_rlkD^1q^DOB3JD7=B8OCYJJB-dEThQ`Q$!Cb/M9; +`lH6Daj/C7)Aj3D',)`Rk@s8W#no`'F~> +o)B'rr;ZfOhZ*WOq"amg$2s`#s8Mupr;6BerX8Z(r;Zfms8Dors82fq"T88urVcZo"T8&hq#(-k% ++aY3me#l@s82fjq>UBnir8uXs8,^oqtg?j]ML=90eH*G0p%ZdNgc&qMfDm%]A2,qi8NCGEH5u=G +&MA>Ec?,BCfkfJlIYA!p](-Tahbm;kkY8PqYUb_tX30s +8W&pqrYlfq>1'i2Y6h@ofdG=F)c;DE-H,@Dfp)>G-BKQf>WJq +YgHls8VoprVlcno)AXg$2aPor;ZfrqY'X^rt+/YmI]o=o\TWJq"t'js8;fjrr<#ur"K&).3KjHn +L>S&-6OKU+ +nG`^hl)CN5qtpBm!<2ut$iL&)rr)`jq"O[arr2rtr:`> +L2V$JEHH&9DK'K6Eb\Zun*AWmrV?Kll,UTVaQE@!rr2our;HWp,2:#R_oBEW>&8D/H$XgbH$Xgb +HZsaXE)4X&cG[VHD/XlMrd+Ti(O1.JG(b-aEE(-&`Q>oSA8up@G^+L[GlDnQH$=^`Fa8.s^rF70 +_SO+Ls8Vrps8DoqlH/f"rr;ons7ZEeofR24DJX?6DK]f;DKKl:Ffs9Mf#388@ul_8R$IZ8&X\lE +PVtBS0fE=3p\Y!fs8Vrps8W)ss7-*grVccqrVca-p[k_lV6R/$W0kj-s8MuprVlirrql`pr"JcL +)'!#M,o7C=(E+/.*$^C=s8V]is8NK#rmIF1S#5THrr;rgs*t~> +nGbK>a`LpT_o^0H`50C6c-+;PaNMoZg"P*+aMYp7c-OPVaNi#IaiDHHc,\)RbK.iKaiDE?`Q$!A +rQGns`k$"r=]SsENP39O_o0a?b0&_bGK.eA_9Ks@g>&Ge,UY,m9Hl-MPE_#nNg,B==&ulcf\>^:_e?d)3lAa2uQQg?[JMmd]i-^Q>P3`Pf^3_oKd5 +^V@J!Y(SNJ;+X8c7!k`@^;n4(\A?5.b&P2=^8cT;E_o;.C0YME^rF.*cdBhO^VRn-bfI]@\C'g< +o_A%Bf\Y?(a2lEDbgXV0bK.N?bg+VXf%Jk2BlA$4GBIhGBl\$1Df'Qse(W +o`+pi.Js/7io^:@rqQ2\>j@c +DKg'pEY`V<46QD:-is4k`9_TD->r;HNiq>UCaqq-dVDf9oU66mQX!E+)X;7ojCeQ^O2)Q][Q!O-u,pP'0SP5!Co[og@7) +s8W&trVlcno)AXg"TJ;oq>:0h!VuTjrt"AZoC22CnCdjAq#(-ks8;]ls8O)5rr:q%-R"]MQ7O(# ++s8'T.@U24s8;ops8Vupq>Lm%p\t3miU,soo`+sfrquEgJ,~> +o`+sjs8N9#_5=X2p\k-irr;p5s7kr+ARMP's82fqrqu]nqu?]ls8N&ur;HctrVc]ps8;uuqYpL' +r;H9;R&-OTW3>girr)itrr(dSs8OA@s8;`lq4K0%4=DgD-'aRUQBI_uQBmYtO&A_pUpd4:E,9Z7 +pi$IUF?/O[s8)Zmrs%B'`PTjerr)k9r;HKls7sIN_8F:+G%>N:H?O[[GBe:VI<^-`F()6"d*9hJ +95SotH?FX[I!0j[F`r+ZH?*Mg_TBL'^2.S,H?aR[H@'a]J9Z?cH@1-fH,.P:`4a1,e,B4Ds82ip +s7"5"s8W#ps82irqWBkWEVseTE@,UAEHH,,;+a;XC-%ZJQB[c#Q^Ei%OHYcJ8h)nb1P>3`deNkB +rqQN`rW<#rrr)isrqud-rU]*RVQm>(Y+V6Vs8;onrr2rsrql^8p\t0K*?Z8[p6$d>(`=/0*[I'I +r;Z]pr;ZforV?HlrsS\oW2#fS`:s90s8;NhJ,~> +o`+sjs8PUXP@npA_9^?J_nj=5bfn,Gd)sI:;GE:oc,.EAd*U"\air#Kb/h]HaN2NHbg"ASaMu3: +`Q$0Ab0eARbJB`R?s-`CDRdU`a8s<%rlX6A-cX6J_TL$Ff%Prt2)cm94 +]tLV,6=F7p7TsmjO12^:_T9=#^W=7)]thCeP[%Eq>?"sX9m!;M^r+^MaLJgq^*ge6^r44-\b5gg +o^2/0hVZGubeqTHe'"T)c,RZBbf.`Re&s%IDK5Ys!HE&WD'j(;Ec#,Y846Bi30]_nP*:ojQ'%Q$ +R$2Ya,r@;'E7>i+b0\>Na8O$BaT'E_bWa=sb/_N=\"mV9>#S1!=')fQajA5Sa2>p_o9gC +_91-:%2]Y)gNO>B)]fb0&eTQCb/_NE`llTFa8*dgb0/#DY?+Ij@%QNXs8W#oqu-9hJ,~> +n,EFGgA_*qpA+O`s8N#qr;ZfhpEM"9,9h1,s8Mlpqu?Klp\Fjgrr;lprr3&tqu$Hn"TJ>qp%SIc% +bTn/l1*^*s8W)tr;6Bhi;P,(q>C9mr9HK:VD:P.0NTYTPaRl'Q^F/,Od;Aq5[eb=@V9>"EGp!pE +X$](6-B*%rVl[.Bg"X%3j).1q#:[EWp&Fsds7uWoqu6U3rMq.\,e!aK=sb=;,q>8\s7uWms +8VuprVu`n&H);#s8VZLmd/m3s82Wks8DZkJ,~> +nc&jnr2?+=p\Xjcs8W)trtP(t+!DpN*H1lUrqcThs7u]hq>^Hls8N!!rVZWms8W$1qu?]qqZ$?Y +TVSN_X/+5Ar;Q]os5 +o)C0;qW)QoRCpJWb/hK<`lcNJai_NH":c@_#uK!raN2lXdDs_Ta3huJa8X'XaT'C"bf\#G`P][7 +aNDfK`Q5s,B4PFU@pY;5`r=-\aT'EAa=#*H`R3&W_A:X^+?)G6<$s8\Q^*c"QBmesPa7:m?ZK=Y +AnH=-DKP\oIrfaD/u6_:cHF>McG$d#\&[(NbJV9Bb/V?=h!CfOnMA:ZarXh)O!aiME0[D^#"^r""+]AE#GlfR6c +cJ-[daMl9Dcd&].c,[cB`](r\`R_^P@r$(*F)c2:Ci"66DfKc7CMRp0D)6s.QBmPuPEVZ'T9tq4 +A2Z7X/"/$ud*pIbb/F7ul,`nIs3*^Nc-+2Ka250(?r9us:0LMa^WFRC`5'C4aNDTC_o'I6c-=S[ +KaeA%S_;CM%1jEl'R8?cbfe)Oai2 +p\t6mqYpTQjSo0%rVQNls8Mrqrql`kAgSB;,:]epq#84Yqu-?ds7lWjs8)`orrW,qr;HWsrVH6e +rs%uDp#P/rl2:P]rr2cpr8IW%rVuosnbrAQ0ZQ@83a>0nR?<`"Q&UfkQBIQ#O#:fqG]mnEFED?o +EY3;5E^RtEs8DuqqtJOU`5^a2rVmT0qu$KloZ)N8HZa[J-lE$c!!3-'rW!T9!sTN"?K:tSm.K:C +5SO2#!!39#!#,P;!<<*#-UZ2IoC2;C!es6BU^rs&K"q=j[X +rVQU*rSd&+mdTl5g].MK9p\t3eE[`@fbl.D6E[ahYn,N7Xrr)cnr;ZforVZQhrX&N" +qu-Qph!FOerr3#rr:L#>~> +p\t6mr;QrukG,$MqY^BnrYbb8rql`i@j)I%*$qQ^p\i"Sq#'p^s7lWjq>UNqrVc]p&H2P)s8W&t +rpmmgX/;ks\b?"#!<2rsiVk2&s8W&eqtrc5`&oh7=@6qlOd)/sNK0BlP*VGq2/[GXFD>f9EbbAg +F`2>=6H]6+rVuosqUEC$a5[!rqtp^CdBH@gToG]S4]G^4ISFNr=]aj%_/ +Bl\EHG]S+\H[^BcIX-6hH#dt#Z,45c\@&A2C3suJIWg$dKQhZeI"$NlH%WuA]<\cUUpTmrs8Mrq +rX\)Os8W)srr<#ts6)LdCN9;lrGa3%Ci=<4EH5l6Cd*0.PEM9"O-GukOHl2nQ"!3j2+p>.8G;oR +s8W)ts6]gbr;ciqrtt_7s8;cns8LfWZ)b"-Whc?Ws8Dlqs7lWnrqud;p\t3dD^6JRaSYf.E$\/J +m/QqUrr)cnr;ZforVZQhrX8c*s8W#XURn$Eg%YLGs8DZkJ,~> +p\t6mrVm?'n&AdA]#2b7aMu30.J_Y6uJBSPa7SoNgQ,qQC!XiDK9rD +CN=K;C\VmuE,fYIU;tO#aiMHE^::kc]un[Ea2uWN]>h\@m*FhZlKcESYI1s[c-"8N_8X@)\t!4c +?r9Wg8nCSNYf"Do_oTs;a2GU1bKJ&C[Zr[b@8p66BREC/\%Kf(^V\+?_na"#^Vn74b5U5H_S=?m +R`X%'`l,sBdCZs8bJqL"_`c&g`Zf),DfKiVH]s\UQ'RJqR?3W"Ru`b> +6!5a$0J+a&e&p(aaMu3=aN2NHmE#7K5KCskaN)?B^mSeB<)67h=(/MU`5p3G^W+@;aMl'9`m)KF +bf3B~> +pAb0lrVm)[htmBMr;HHj(]XBh,pF]X,ppY0s792:;5WjVs8W)uqtg[2\KNB`:#QtJ7!!3'!'*JON.6uJRo(2MF]K@YS +!<`W9$Msi'"oeQM#9cHgl1=N;q<"S<*!??D!s&B%#7hRh)]TnE13@JF=A(tU5f3*krr3Q-rT`/5 +r;Q`rrVZ]noJ1Q/pi7^#EH?5ADf9`?F];S3Q'[Q"R$a("OdM;sR"In9/Nu +pAb0ls8N<&qS&iHpA+UcrVdW5qV*/5+;Z+C_tsE';'J`1me$MYrVuflrrW2trVc`urVZ]lrr3?" +k*W$AVQd;_s8MuqrVtjU-NEo7s8Vk#><4W-QrG_^T-Z3jBEc5f3EcZ;"=d>29O-uB&P<=nlOHZ#_6X2*&3Zqp>oDeL\ +rr)l`rW<&trr2p1rr;roqYgHoqT#qDZ*1.1PeHhjrrW0!s8N#srrE&trt"o$W"p-fp\k-lqtBaV +s82cp#lXc#s8W#srr)Zlrr36&oXLNbT!\:lrrMlmpA]X~> +pAb0ls8NQ$ifRSp]Z/+:`l5s;aSs=!c-3['&/l&m$`)8&atQ`kJQ[AmeqZDP=n``r=-:aD\1`5TKuWe)R4 +:cq6E;I!+i]Z7ds\\Z5`aEbV>_T'[jghUV=RY_8X[6\?Dg&R?a,(NjO^V:-h9C +/YU>4ai2?BdDWK@bfIZ;^VS.?dMc_1DfKi>EGoZ1Df9T8F)Z#7F*)M&>*bD=O-uB&Ps1@tOd),] +5Z]0c.h,3@dbDgaaMu3<`lQD(c(s8VijrV->B~> +q>UWts8W)srr2pGeCXa#rqcHjs8Mrrs8Mrmrdl:;.3fldq#C63,9e9Z.WW>up%SLbrVZZorr`/o +q>C6l!VuZmrs&)PpZq)#kPkJ]rr2p!r;6EMr[.OBb"hV?2'b"P7:StPG*SDWRl6+gMjTZqOd9!G +DfBcCj7iKJrr)`j=K%=0>#8F/F*(GS/L2DR'*e@7&eQ$/ +`U)p`rU8lp3#i,&$ksirqZ$X2'E.tm#VA-lmeQMKmG_M"%KQPA!"'A]00i--;,9qkF-NrRd`-cr +meZ\Tr;Zfpmb@U7rr<#ts7uTb3Gis:ErU5/G&q\DEcQ5DFDu5.3g>elS!9,$O=+h*R@08)Ma(NQ +.jud$o0fq[rVlcqnbrRerr2p#rqlNgrVlg*rqucqs4HW'mIBZ:c2R_ErqZTirW)orru1e-/14a' +qYL6ks8Dorr;?Tlqu-Qmqt^0is7lTnrsSW%s8:g?lf%mDq>9gaJ,~> +q>V!)s8W)srr;rhWhnePs8W#rrW)utrYkh4r-fV)+rqOOpAOg**ul4F-Z?]ioCi4`rquctrqu`n +rtYJ/rV6Emp[Z.iVk'rpanYc5s8MurrVtjU-N!hd:@r!N+>QMT@S/\(Pa@Q&2Mm]WR$*l"Q;pk% +E,fi;G].J;pi(7oF)*brs8Durs8W&J_8OC[s8W&srr<"#Ul'G+?#4VENb2mBG]@YIG'SCbIWTpS +=2i\4d*]bKF_GoEH[0d_GB&@_I=c`fF`DVQEGeXk]>:k[XK>W@Is6*hDgQeOE,B8u>ZOd)ApKE7 +YcW#2qZ$Qls8NZ0o]#oLrr)lrs7uWc3GWeXqJ[j)EH#i8E,fr>DJrlAP)tipP*(of-#&C+R$*_l +5$_;<,UA-87'ZLcrr<#ts8DKe!WW/ur;ciqrtbS2qu-Qnr:I.uXK/S+X-*&Mrr;cns8N#srrN,s +rr36#o-Y_cs8)Wls8W$,s8N&ur;6Elr;6 +q>Us(s8W)srr;N6E+#qDbeR8at*"`>_cd^C<"q1kI#Z.,>bf\AQ_SO(+`Q$!A +b/hTA`Q63IaN_l?[YA$rie9C+6[4`5om:a3_`:]X5Q#c,duF`ju.1&H2ZR$mo/!ApB6)UT(l>`lHc8kNGSI'LN([&9^o[*ln4be:?i^<4[#KK=6P<)?k6HV'4/\%Bkp_6fP\Ea1se/1ND; +6;D$3KN)s@fZD+Lai_iI\CAgH_Sa10`R3*W?>a\&F`VVDDf9c=EH#o;.<9NODfB)EPED&tQ'@Pp +->JU/QB7;d3a#E))&7/B,)F:\`l5p:aNDZoaSj9\a?@S``Q-$A`PAck@oQ0% +q>UHnrr2utrr4VJgtMf.r;?Qos8;fps8Mrrpa@F.*G/Snp&F`,/0#lU+[^u(4aiNZQ9R?YOd26!P*M1b +@!-L5Df9W8E-$-sE[l-NF#WdkrVulrr:ngZ`5Lg7r;-?gr;HG!j5]Ucca0'o@n&:+5;t)6&H)Ss +'fSkOp\O7Vn(:&]%KHMJ(`t+0!!!'#%i6N+!!!3E1njF0n+lS?LGTPF#RU\U+sfTgMm(4QhVRSk +pZgJ+G.@>?qYpL"r;,m>qYL3jrr3-!rq]'$qJuUX!HN2ZE$B:BF)l2AG!qO`QBdW)RX$nOPF.?' +NfZn<@k9;mM*Nfcs8Drqs76-jr;6Ejrri?!rVZZos8W)urs82Olg=E2o%Ed9rrDokrZ;+7rVu`k +RO3^Xs8M`lp](6hs8)Wis8W#srVuoor;$?krrDinrs.WHm,[O5rVQ6eJ,~> +q>UHnrr36&s8W&FWNYsms8W'#s8N#rrr*c-,9IX9@9I@;s7C4V'c\&0*@2qurq66hrVm!!rVQNk +rt,/,qYgHop@+BETVSTikPb;Ws8N#ss5Er)rVt&,/Yb$H+';oW5!44UQ'OWoJ!$"^Q]dVs0jo-E +FDYl5Df0Q8q/?OYE,RQ(s8MusAc'rm`Q.?Fs8Duss8W"'h:h#Dbd<^g?qjKtBPMC*IZelDM"4S\A,Yl^:LYR +MK6W5s8W''qu$$Gs8Drrrr<#r"8P?$EVOJQDuOW!DfB]8D0'ecOdVGtOIV\^2`-Y`N1c/k8QfIM +.l)fB0^o&@rr2utrr)Eerr<#s!rW#rrr<#s3r]0Yr;-H>XKAM0VlQ93qY:*jqZ$Tprr<#trr;`k +s8)SX,(f=arqHHfs8MoqrVlcqrqufps8Vrnq#1j&rr)lln#W%MT>LR*s8Mlop]#a~> +q>UHnrr5^ls8VGXB5=oud*9_N`QHEE`lQBPdL?j#%Sf,R`n&C6((V/f#RV.QI+ulg`Poa7`lcHJ +b/hTA`lcNNc-48>Yub@7=_;TR_nsL7`r4!Y`o>&fb0/8/+r6=g*un9e>WOihOHl+r3.I'UQC!c" +Otg?eEcl8;E,]`9F89g*E,fhEb/M<>`l?*H`4`jkaO&,MaN)<6V#XFY5r()C/2Jk/4%j=8OK#t/ +_Ss-dHSo@u7QF"=F#9,m]sb"rB8!S6`l5d"IZ_On`k%jS=CP*!@:s:MJ?$)t`3-(e3[upK5tkBk +B5M-a=_D,DV<6@Ucc*l@b/:jBd*0AD#fOd+e(QH^CiTDr!-A-Z!-%pU,B.XCEGfl@6^!n'QBRr1 +GW7RDQ&;/kNCmA`.4?`m.2'_Nb5T?_`lcNLb4*LNb5]OHa2Z*ucp!^WF^< +`l?*@`l?*Bcc=)N`6N]9>JS_/]u@_-b/200\\?J +q>UHor;R'%s4lE$s8Vferr48Aqu?]oqt.BE)^Qriq>('coeI=+,TnNZ+EcZJID>A2aFE2XeoDJXes8W#mf>PYIkl1S]@/g3*roS%go_n=NoC;/(gV'M@>W!Q.!!WE6 +7ucp,q>^-LTK4(q!!OMg<]h?%"ptY\.O5i4!!E]_?g7(Pk3h371*6r+!?N^KbhqUNnF5f1mIU/A +nZbrXYNtZOrVZZp"RY7Fq>UBn#5nN"l:>o5qf;^Y.<0ENF`_\FEHH;BD+eW/P*hT!OF0>cCm^NR +SUBjs76-kr;$6hr;Qlur;?Qjrse/Zo(;G9jO!r,qu$ +q>UHor;S#@or=h8pAXpfs8Drrs8Muss7N`@'HJCOp\Fj`o.:Li)]'[s)%7Z0g\UjIrr<#srVQNk +rtbS2qZ$HlpW\kpV5gH8pAP$irr2rtrSIN$s7uUT7r'n#1fIU020bFSQ&.@fC65!LR$!_`1hq/R +CiOH6EHZA +q>UHor;SVQiGD_\]u\78bJVO +rl,hubg+YY`OUq*>ZP*@NP&fb0J8b+#I@X)`:]%/MhL2PEUh\7V\$UP*hAu +I5G\]Ec#i;EH68ED"qu_Dg*\P`6#s?ai`)S_8*Frc,RbGbg"JW^5Dt^+s.s[2b..^@Sfft>B6FF +^!=`m?U7^K2*O93B3C_4\?DB.:f=:2ahb3G6V+/t`5Am;3-8J-;.F)u;:8njNPR@9>% +HU(&7Q'7H%P`nC:@mi*r68Hd/b5T?_`lcNLb4*LNb5]OI`l5p:aiVfMah"p"?;scu +`l?*@`l?*Dda-.Uaj/TKEl?2GdDsMNdEBJE_njO=a3VK?cc!oKq8`ubaN)H?\pRLR=DcU[oDa=~> +s8N-!r;HZqrW3&urVnA&fDkmMrV?p1@;=S_srs88i>,9n6Q*?lXL+se]9rVlfr +s8N&u)>s:)q>^Kns8;osa7AlnkMlOAqu?Wps8;lps5EtWr$D&f8k_?9>\>iGQ&qGuPBB2^BpXjH +Q'RYH9lk2lE-?5BD0'Z4!#\>8o`+samG(5q'*&">+=t8g,SLFn-SV#U('=O:"8ZR$*]&Oc;P53%7m#I676%rr2iqo)8ahqtpBjrrE&tr;ZcrrrDrqrs\#Xm.9W2hs17AqtpBk +s8N!>rVlfos8Dkfd/O(Er;ZWnqZ$?is8Dioq=MnerqcZnqYpKo!<2ut#lEWFmbIjBrqlBgJ,~> +!WE#ms8N!%p["lTr;-?js8DpC6krr`5tr;HTo +)#jL2s8;c[QEI*_V6B^ks8W&srr<#sr;H-as8Vrprr;uss8=;Bom]J'3'gVT0eJWNS!9+Z6:YaV +NgH#sP\*X2DK0Q9DfKQ4DK>PlF)Q,644XB1s8Murr78g)_Wh!tr;HNjrVcZEG2_E3gXFWsc-4>J +`4p;*GB8"SG%.8mcd9bQaAm2'I=ccZ<7>#lHus@N?_:2/I"6HgF&Jg?\@SZLUHs!BFaJ1;al2.1 +eBuRdbJ_0.];J`?^qojHrtkY5s7u]_m/?k^rr)irq>@rrCiaQ7Df>Sn.W99FDf0K4E,'N8CL'Z! +Q'@GrQ!m6c +!<)iqrV\YSrr;rdc"C%i`lZNK`P][=c,R`Bce:bu'Ga$cccaMZLC4k:(`*f%(D[`!%bT.PaiV`N +bfp(-s2Gqtb0/2T`3N?T>?4gC^r4IDai;<>`lQ6@`pq.M`qmdU`rF+/b0ADff4[-/2*tMZ1bOrL +QB./N6:YaVNgH#sP\*X2DK0T;E,oc8Dfb_oI<0UL0X&U>b/h]He]G\BZcg_9`6-9LcH3M1!%f*$ +)Bgan5=.k4@Y.ZtS$U2[U-D7K5/]6,pm``lQ3BbK.Z7 +SR,Am=&rX0Ge;&;b/VHBaMu6@bfn>S`llTWGG!%*ccXJMb/r/M`PTR;e'?(/Y0+W&a3)R%a:-54 +`l?'2P[I-_@BSkds*t~> +s8N/tq#:9m!rDfnrVlr\eE-]04T,*Nqu?]ls8VckkU%Gu-)CS>s82Xs+se^Kjs6i.V4'k!"93jK! +PF.StP%IEj;3mB6QBmn9=`JM&EccAFCi+B5BPMU,E,]l=GBJ"*T)AWfs8DljhSd@Omf!%[rr4hS +rV6/`O7E,Is8;]fnFZ>Fl.;4_+9i)o5Mc#1o_@[S1*?c$!"C85f75q.%i.>lc:K&ZrW"5Z3+Lk/ +n*oi$;)o0t.Rgl8nbrCco'c#9rUKX87ZrO]Ph>[.rVca`n`fiEs8W)urr;b/?#4Y%EHQ2@EaE!E +:0;@PFDc8BEc?,65a@\(Q^*\&@SpBuOHl0!Q'tWV:fq'X1c[Vgr;6Nlrr;Nfs8;orrr;io%KH@u +q>^KZjlu4&o^U?.rrDuoruV1;qu?]ps8ViXs8;`nq>UEkrVZTls7Z9)K./+!rVlimrW<-!qu-O" +hWO7jn,NFcp&BO~> +!W2fjs8W')r;$3-WP8-$rVl`p-MdZ@q#BFt)''fls8;omp/W+R(a0qB+Vtt9(*h!!s8N&ss8Doq +qYq$$s7l1F_bT&EG]T2EH#r;DGH\us8N&srqbHh`Q%6Arr<#trr)lqs/hLHeCE.L +c;;L#_Sj$rQXXuTG]?Krc-F;RaJi?_G^b'aBNjP_Ed)YGDb@`?DLHn]H['FF^Tk2OWi/d.D09r< +^>Ro/eCMmibfIZ-_73(A]tqMYs8W&sr^?_Sn,N:brr)iqs8$J8CNF?2F)>o9?!Brr=);erD09f: +DK9>QPEhK"PE84A:g9 +!VlWms&T*pqY9ses8VcQQXk*F_Tg?L`59U=a32T>eC#3M'b?#Mbg4Yh6P1.J(CLWe',hr,(U)?4 +`6#s@bK@oKb/hTA`luKN_MDGsr(?bf[rE`W!mIa99N%`Q$!ub#$0sb.l'7bgF_nf-m4B +;DT[k>W-\dQBI>lAP?0%Q'%K"Q'Y?ME,oo;F)lA$$6^T`SZB,=dE[A\U8M_:(+ +_8X2FC58:.AT1.^nC%bNRX'a2Z-?d)<]Caih]EbKS/Q91ioC +EccJBFE(Gb8P<&uD/j?4s)ATgF([_'QC*u&N^I/K=dG&1rfdq]:g$Lm=t(pJ+O\`^b/ME`a8j6W +a:H80`jp+o=]SL'=\c?@aiOG#4N5=]b0/,Vb0%rJ]$SXAd)!`G`Q?3=_TTmFU2(JUAB`'4aN`&R +`lQ6@`llKBai)-+Db`l-LueI1s8Vlm!<7Q~> +s8N/rp\t0ls8)fprVlrPchIG="T/&kqY^?m2#71FOWtqj/b]#?qZ#;#,TA,7^k!^W,9%mSf_k^F +s8Durs8W#oq"XdbqYUrr;QTnnc&RdrVl`p8G`Aap&FJPCFUlH>@KfW+deLY +M`[)B7VFrKRZ<`"PtPG(F)c5=EH$&H-u<"F@6egTDKBl@E+DfAs8;oqr;#6g`5V*@q"k!i2#d1J +nBcTRqu-]0[q\!!!-9/2cAM5<'l<1sFNL*-4(b\:>4t" +mGrU^/LNQ`lMgSMs8D`bo()\We6.liI=@4*o`+mhrr5CWm.gMYs8MlpqY`3_G&VJJDfg2:2C:7S +:O%#(Ec6#?EH>hUQ'RYuQBsaN4aW/YJs)7]PFPNP?=d@o0KrORpA=dbi;O2_p\b$js5WG2p%%h3 +n+cndrquWms8N#t#Q"K#rVuosqYpNpp^=Wd-Rp8`-78H\s7lBg!WW,srrhTHkj.-ss*t~> +!VuZhrsSf)r;5faWnI.DrVcTm+oMB9M]3KO.J*?7q>]"l*>BZs^ORFN*>KM7eGB.As8N&trV?I% +p%"EBW2?W,mJ-SUs8DrqrUKmcs8N!jrVlisrqZTlo`+ANBdbE>>%'TS+-r.UM`[#>6tSNCR#I;o +P=]"uEGof5Df0W@->HS>?TrCLCiO?2C1'd3s8;oqrqkQj`Q%6Crr5^krr;`ms5f93gs40nbg"PX +`4*Lq\P-d_Df]1)c-sbOYukdbG^b-eD-5of??'q#7_u`=Cj:;TGBS"F8$ob0V5e<L9ks8N#ts8N&rs8)`haJGDuV"4H]rV$8A~> +!VlWms#U,Tq"jmbs8Vc8H=N&I`6HNL`l,jQc@$QC%M7".bfe_U)&3PnQD%as)'9_9'YDi9 +^r=7:bPfR$aMu-:b0%]@@q/S;?ZiKc`lcWObfIc@`Q%Dg"NJF"`lS/%;96eu`PKmC`n]*&=rSSg +mMsPY,5$Ec?#9E,TiD-YleB?pAUPD00lBC/ch/c,e#KbgX_G^UVM4 +`PT^9_oB]FeA@Y.,ss".3C6A48lA>b@Wt[d_?#2fd[C!Q`Z\pG/8If3iME$fN2N,/Z +_8*UWJh;.T='^,OBVC824!+n?%Nm;a7SZ`VC2>N.h='UVlds8!aMuBNa1f[:aiMTFcH+9D<-<;* +HZ!hHAM>5[8PF8IEGo[nE[>[FBKDHEOcYck3b^X"MF;rTNfoKl9i>2)=Xb[L4J9sCaMc':i5e[; +`kJ$T@9-E(<_e=c_og'?`l5p:`lQ6GcHFAUb/V]Pa2cEFaO/Gga2a-G$lBTn*"^6+`k]pHb/VND +`l?0E`lQ*;^P,QR:28GTrVuWj!<7Q~> +s8N/rp\b$js8E6&rr2rqf@9d!s7n_Qr;?Nnr;-H`.O62aIf06Cs7_*c*@!93q>Kr&*uuLTmf3:b +s8Duqs8W#oq>:0jq#CB]e*lhnhtI'L"TA8qr;QZpn,E@brVlis9E"n`s8)`pf/e:22E,)J>q%^\ +P*2/"23&WBQC!l#5\4tWEGp&>F)Q&$0d1%#LN7HSF`_\GCGkterr2inqVT90`9dEtqY^@u +q!HG)>f,h$pA!q=m/$AFp>s^p5VGH[pZh&9i@?W#$NU5-$8"1%o@$H/:R9_a;^2JK"U>A?-;&(% +j7(ou<^es\nFlVKs7Q*bn+->?d;",SH?+:ie+E\=rr2p"o'H2Mrr4#;s7uG*>]aq,C2\#Z4B,cm +Bk):(D09uAF`MM=2ZAs_R?i1aBKVHH7k9pjS!/ne:gREt0.S_-Y?(]'rVQWSrXJf$qtpEnmHU^!r;?Nns8MurrtkJ1rqlQip]'rlKIJAs*$c[R)^ri+pAasf!rN#srVm$"hrX4[o)F4~> +!W)`mrrE&trsSi)s8CHEYOqShrVQQn*rQ'9r;Z@6*?-,JqZ$HmomR\n*@p^VrUOY@(E+Ucrr**$ +rr;inrV?I$pVE)jV5^ZKpA=acrVu6`rVulr9E"kfs7ZKhrVtJ0H7Lg/?W^P]2LCOKQUE]kO>Co/ +MjT`oPEA%=E,ff5F_u,8Dc\S6@7l5mEG]c8DJa#Aq>LH;IcGRB2o`+sirr2lpnF6JUs8Muqs8;_0>B=Y%BPhWS4&]QiBOZ($Cijc< +F8p2(C,D-R78ZlPF@YpKMPiW7P62G/$E"Rs8Dfoir8rW%K-&!^9"B=W3)nhjno)U +rr2pWrVulpqu-Qpq>^KmqYTs`nu[Ef,p*g@)BKP9a8G]3rr)irrr2rtrr;rrqY^ +!;cZos8o>DK9W6=>1k,987G,DK^/CD-IUqbPoR;bgaeI]X>i) +aMlgQBQPUAN>g<6n":^R#m8Y9jM!l.O-/C(6^N*d)j5LiQ2&A% +E#lkH=0fR9itD$WPcjWaSj+;aN;WMbK.iKaM#[4_oU0Mh:Im(%MBEf)A3es'VNI[c-4GR`lQ6@` +lZHFa2Gs:TN+NY?H2I2p&BO~> +s8N/rp\Xsis8N&u"T/5bgZ8A6,l[Z;rVZ]ps8)]g`[)]K,K0QYs82@2/0c3hp&4X[=W]"5>l=U$ +'`S(2r;Zfpqu$Hns8;osbNA9\lfe"`[nSNn*h&rVlfr7e5pBrVulss8Muhg-BOtFE;_J +?ntGo92]_HH?=.FEHQ;AD_dQBQ^F"1=C4deO%hZTQ]Rc*R>=(5@6Hq0BJb,(qu?Qkir0#Wqt^3j +$f^[Bn+?&+lMpe^s8N#tr#l";rr<#tr;6Hks7cBfqnI#"-6j]R.3fu\)^HMXq>^3brW;rrrqufr +j9"c3m.:7-~> +!W)cns8Dp)s8N&q^SSdFp](0hrVmr;s7lWia<)6:+2RmQs8)4+-QO(To)AFY\$`]L\#iDJ91Ns8NZ0s8D`HWirP- +Y,\&&rVQQlrr2os,Q7Q=rquWirr<#nqYL!!BdOrB)\aG6)''G4)1_M^q#:9ms8Dp,rVufnqtg?b +[A'@g_>!m#s*t~> +!<)co2u`aOq>]oHB7IM'`lZBIb/VK?a2Q?HUCe%F#+j,[c-"04'Fb7XdF?Ot7hcpP6.XN.b/hWJ +a8O0[a;Dq9^q*icA6<2bXLuKmbfe5O`l5j7`lcHoaT'9[aC`T9aMu3>c-4ba7n,oa,9p2d?SG*6 +Q(""g0gCt`:K^;*O.2H#OuI/qEH-)@Ebp#:GW%TDB/*;,E,]o=E_'EUa3;WEc.'nJ]X,T%bK%fL +cd0\Uh:BKY*+)0$9M%<8:0:_/@fmF]S%aMuBOa1fa=aiDQKbfRf4.r0BRFEh_2 +01]eJ:MjlkEGfT3FE)8;0p@id*gMjYd- +S7#W!:f^_"H,IQ!b5]Q^`\tlZb0.oI`PoX3^<"dVU/i*F&eb`s*>]:m(Ca`ocbIWF`l?0DaN2BF +aiMNA`j9;Q:LKdVp@\H9~> +s8N2sq"t'frri;rgtVc*rr`/sr;-Bl)ZKAG+sS:3qu$HgrOXHu-7@=Crr(Xs)'C02rqlitrVc]p +!rVrpr;Qupg?%kij8/`S$iBYnqu6WqrquZjr:9jer;HWos%<4gq!pV%1Ij8O=^GK5/rc6ZQB5NI +6B[[?:iS[POI);u2/mMSF)l5@EHZ8BEHH5@EAN`CQ'6>WFAHM5RS?gnG`"Z +ec,RLrqucm+T26 +!W2lps8W&trr2p*m&?`8q>C3jrr<#prr3*!s7L:/*$J2Tr;QKh\0Dh6,16GjrnJS1*$2B^r;?Tn +rVuior;Zfrrr!3(ou!/iXJW#NrV?Hm!<)Zlo)A[hqu1a4QAq,i?qEYG +Q!-q$4G&TYQ'*q@DJsN6DK0T:D/<]S/Z4eBCiXE2Df7qjq>C9mr;Z`R`PfaZrr;usqu-Hfs8UNb +]!tGbbK.WF`jE7aZ)O_2WCpW#XjP@3T"i%S]Y_\-`IG@-iTp'uflFR9\4s.YH+n0X/DBM +r;HWlrs&H"rVuosrr2p!pU,;G*=`o-'c\;6*[+R6+!)Qrqu$KlrXJo)r;HNlqTkq/SY>BHp&BO~> +rVllrrVnVMq"X^=FC1QM^Ve.7cHF/G`5]sIbtel`$oZPVaMQ;j%0[4MZJ!RP^'k>\$Bi`Xb5TQa +cMkudaSs?^`YQ.C>[M,LA!XmB`Q?9Cb/VE<_o9X@hqh!Y>K-;-Z/Jf^/>+qt]mH_CIDI(ahQK8n2chaRo6"o^VM>hSro7 +=o\Ou+&O$_#SJ@.1Gh6c:h`R4^?4YOq<-2nfZM7U`lccM^!"O>a2l?DaZRhq.;!dFEGf`A8O#[_ +BR+61DK0]9F)c/<16@caOE*R*9Oj3O8lIC&O-,ZlRX.%s>t-1b.[qd+cI9noa:c_;`5KC#HX0BB + +s8N2uq>C6krs&H$s8VN=j8AoYr;HQms!%FAr9>aY*\!HQr;$Bh@O2sB8,rJ^pQ?UG+"mBfr;Zfq +rVc`sr;6Bk'%H=7lf@=1s8Durq=aX^rr;usrqc]no)A[erVlis!<2ut8,#e42C_:$?t!:s7[9.0 +O,Gr$:6^uk5Aj^rT9bh.1N@ARFDu>@G&;,ADJX/u<-`;%FE2SA7eZf\rVlcoqr,N3`8pdlqY^@u +nE-m&ItWt`iUcp6oDALcn,*"Fj1afjHJj[(K7\AqGB.\HJqleMp\"4La*uaj:lg*IEI)qMEH[P= +=DPbBp@2i?="=Squ6TpJ,~> +!WE#rrser)rVZWos4O*Go_JOcrVuos.KBAHrTYgR(F57@r;$Bf?Q^(/6iHlYq2lXA*%L^]r;Zfp +rVuoqr;Zfqr +s8W,r!<)lr3r8XFo=SXE[D'SraN2ZLaMl*6c-aTH'b(atd)s8RcThmB&1H8Xai\Z2(CLc#bK.rS +bfe/Nb0%fHrlQ+u^U%*Q?X[>cYJn5rai_ZF`l5j7`lQBJb3d:L`b!38dG*80)'p7e:KUk'0gh1f +T8n^a=A+jAHp2CO7\5m2P;7)qE-#r=Dfol5GAV,3?rM;cF)u>A@kuETb/MB>bL=SD]X>W$ccO8H +`5p`hnDDs_fs2E-=AVI\<`iO#@;&qP=>aMG!*1Ul_s-mip\".Gikd&t#RhCe"9;//-HY]Gnac,1 +k2bB~> +"TSJuqu6Qo#6+N"s52W+rr3-!s82`lrr3u9s8/T4*%36"s8W&olms5%,cpk>s77EY(aFD*rVuos +!<)oss8;fp"oHg;p$(N9s8W$'qY9j_r;Q`qrr2cpr:9jer;6Kns*"5%3JfjS@Rsp59JBY6Q^3r( +GXbS.Pa@Y)@r4DfQ'ILgEH6)@G]%P>HZ@FEhItq#: +!<2or1&_%Kr;?Ti^88gErVuirs8N#trVcZmr1Y,F+X?.Ls8DcY-lO$HW;-5hoH54i+F*e_rtk\5 +rVlforVuorr;HN`c]b')VQAGFs8;`n!<2lqo)A[hs8Mrr4T>*:2hj@I?UnL/9.sD0Q'@MuG=>A* +P*M5!@;@u^PEV(_DfBZ8G&2,6H#IG>EGff1EGoo8E-,SdpAY'k#lal&i5N^Thtd9OIf9[+Uhdrd`4*FaYI_3BRo7Y[b/8.-\&,hi_8O@1_S3^$^uXYQBm=K2F*M;=EH5r< +rbs6'E,KZ8DE#7f?Sjns/bo/#rr2p/q#(0gq80G;Wi3"t +V +s8N/tqYU6k%/oqmiK[u'^W+7/aiOJ*3Q/_Kb1)7V%2TPoeBQ+W`!lqZ$]EEVd)lS'#Rsk[b/hoU +ccsGKaNDTFb08#J\WBTi@pj)@]u@qhaTBN(`r:H>mYBEc6#5Ec?,JMd*0_S`Pm>Md`Au2 +\&Qk=_T'O0`QR<1f[f$Ggn-`F;ccOg93,bA>u1N#853)!.&b_bp\XdZmH`KC1Ed>C$NU_o!(83H +m.^DMnE0,kaZUgJ9Jn+[!rsYT#9,6Q6;(^+5f27@m-4-3kKiGEaMu<@b0\&@9"Q`QliKdFVP9'lDIG_8!n- +bg";OaMlELbK@oAZ#WZ=@?BL?rV->B~> +rVZQm"T8<"dG4a$"T8)pqYL-is8406-6jXKp&Fshs5@Q@,:;jUs7X#K*@2rWrVc`qr;Q`prr<#q +r;?R*e`QGgm-OcPrVullqY^6irr3-#r;6Bho)A[equ6WqIJQ2q1+l=.>[^`"EflWL?=f@294LMX +QC)t_G#2>,PE.M=FE2A?EH?;ADg?8@E,p)?F)u>DEbAMOqu-QprVcTN`PfaSq>C$crVufpqWb/6 +FE;_aG_=u?m.U2BiniU9f(Jt8nD[\LNf/U=GC"alMP=n4s8Dutp[[l_Q<937MM?h`X=`J!Z@fPCZ&+>[9!F;`ssUir0Jdq"asiq>UEbk4SQE +q!HQ0rsSc#p\=[drVuosr;Q]q(B4/'+X&=V[dWnes8;oqrqO#I-rKd;qYgQorqQHls83'#p#G]/ +hZ!QMrW)tK~> +r;T^prVc`qrr;8YVq^nErVccrr;Zfqs8Vrk7NrfjBCc'"rr:kb-QXJtqZ$<$+W;LIcMdbBs82cp +rr;rqs8W&qrVH2YU8aoi]^ksts8W&ts8N&srr)lfrr;uurql^drqJ]B/h9S#>@:MsEK?[rq+ +8n(;SPa6PWFA>o$Oc;)5Ec>r7DfKl9D0Ki8DK'Z7EH,o:DIQWAqu-Qprr;rT`Q#pQp\t2`@:`h8[l9Ec#i:D*E?2OuI2r1l[icOoA>o>E4+[I?fq_P_)8.>$EL;:cnLOir8rW&,lP(s8:ZX +Y,\G,XI%H3r;?QrrqlZlrso#,rV&c")'a")mIU2Rqu6U"pTth^9D/5]rt>>0rr<#srVZHgs8W)I +St;7Ao(E$5~> +s8N/tqYU6ks8=qKaDYXV][+O5aN)HJai)';b189`'FmmacGn#Q\O@/=#Kk?=bE`PE'b%%a`Q$0I +dEfeNaNDTFrlPbh\Qr9A=(Zol_o0^?a2uI%`Gc!1o/FYmH`lojijeN*u"tf!@oP(piplU2o'l58dHoK,a2l9Ccbd9:b/q[QaiMKDaO!Fa +DgQ5?F`DJBE-,`:CN=N4FE;>@Ec+*iP`Rb;DDI-8OcTK?'NpBA.>!qMQ&L)S?!Ubd1-HW(bKAkd +'#qo.b0J&I\<(#t:/"bl:R8i +s8)foqu6osr;ZcLh#IEPr;ZaLNsrV6Ej +rVm#Tkjn'"rr2utrVQZpqu6`sr;?*as8;`m-NEU%0hFK8?!pS\9U(j/CGKdn?qjisPa.`#A4pi$ +Nff)\E-5frr2p"kj8*DrVm'"r;Q:t>jcVaG]7^mD3p)=D)I9?PF%J'?XHQoI6'i]QB[Z"P!E8Y +@6#i4pAOsis8N#rr;Z']&H)>#s8W)qqu?!Mq>KXEn(%F;"8hohqYgF7rqu]os8DlqN[#fdq>C9h +s8MurrVlcb,T\Djr;ZZnrqlZos7u]p"Q8h>kO&6J"9&5urVc_G~> +rVm?+rVc`pq>^E>SZh_`rr)lsrZ_C>s8McL+rMC>[Jp.)r;QTgjGAH_rVuM4*u>tiq>L?nrr2lp +rt#).qZ$Tgc^Lc:W2SVIrVlcqkPkM]!<2rs./s2En38Np3*]]q;CP5$Pa#l9Cm8:#>"Fg?R?(fK +D)[E7Kfj6oATmT_&TM`#9_nYcs8W)urSYZ5`ROh\s'Xj(`Pf[/\O&W6YdCaE\$(OHG^G&D=gCmKl9Cc%';P*V8#?=$?kHoONVQ'7N"P<`;V>rF*) +pAb*ks8N#rr;Z']rr3Q,qYpNprOo52Y,\Y+SFlaYrr3*!s8Dops8Ms*r;MQo*bb6Cs7u]nrVlg$ +rTkRA,MDtmrtGD1rr2rtr;6Bis8W&mTUqUGc2R_B"9&9"rr)kI~> +s8N/tqYU6k8,*&RTjDN@`luND`lQb.eT$'+1,WaiDEFeCV`W?I-gAbZPJc&iA%Y`Pp'I +dETYJa2u%F>@`r*s[aN"4u"ieX+bfn6#aT'B`aN"4u.*0Z`d3p)"2-X6i:a\o" +Q'H)=D3\C!=@SL=RZM#ODE*W;L-9HsApd`Au3\%0u.`Q6-;_SaUQeD'E[ +lf-5[1/i..An#Cm2ulTA6:XNQ(dA".VO5]^cGZ\="99AM#Sn9u.S38['hX.GP[d`B9laS@9f"Ii +84NY@5>G'B0.LVYir8;Viq<$Cl-o"NaiDKBb0[i9aiaV(#0Fj%b0WnY99XZ^8!9)7NY"H_@9tZ)0[S1gbKIuJa2Z6daAg0s`QZTN`kSp"&lmZ5ccOY`bJCm.bKg_3(VIr.^;It7c-")HaNVoRbf[r>[8p+$ +AZ"\ds8W&os*t~> +!r;`krVnSNs8W)un)a*>s8;ckr;?Qnr;Z]pqg]k4,V&6^r;Zfrq>^Kls8N#on/rhs*h3!*q#1]u +r;?Tpr;Y[AjPoOqrr;rp!<2fon,E@bqYhi=s)0>I@V&nI1dRFiPa+fuH&kp+CcRWHP*)8!BKCX4 +HTc"aG&Qnks)\fe:AXtgrr;rqqV]?1`7Y+drqg'tr;?B[^4glVG^G*tQ#KK0Nk39ZCUF\q#1-ep@\.Lhm.NCmJl,TgM3=!FCc`dQ95\ut +I>ubKs8Drrrr_KOq#:9ms8E0!rUjN)mrBmtC34JeCQs917qn9TR?Nt#BNe,pKf;8@R?a/'R?9X@ +>Xpt?bkV&6rVZWolM^k_q>:0k$N'Vus5j(KmdKYsrr3)tq"OacrrW,qr;Q^3rVPG!+><<`s8Duq +r;Zfqs79>G+HlNrrr;iqr;Q^&rqQ +rVlrsrVc]p#5>Hub4kl6rVuos*rH!6s7q$`)BL3jrr)lsrqQNlq>^HnqX>LF*?(@?rr3*"rVc`m +rser+p&1Y]VPpZ2nGW=brr;lp!r`,sm/Ht`./s2@s(j&A?=7&=1I.1cP*8BmGE#O%CH.BBOH5hn +AiP4,GroSYFD^Jc&7nN0rr2rtrr;rT`PfaGr;QNl!oUl2rkhpRVPo)H8_i4Y +OBbn*B66sIZ^m3;k2"eFgXXa#cc;u+?#+_T]"kScQW2PbbJ^l%NeCSJLT%`#]>UbAMn$dH_SEt[ +s8W&srr)]UnGiFcrr3&o?XE2?.W9!@ECXc-I60o3Q'I`"Q98e_9Tt,f5'<6WR?X/&4'u&W3]bL\ +qYpHkrVu0^rr<#q!<)os$f7ncWNNM)QgF>@rr)lsrVccrr=/f*qV*24.e!97rVuclrVm&j;^`ND +p\k-jrrj~> +s8W,r!<)os8cAPdkC$Qg\]E";`lQ6Db/;?;cd6)6'G;#!d)edacR_aNb,)'Fo`?bJhHE +cd0hQ_8s[=aNDW=_.?tP<+C9_`lcO)ao]Z(`r`lcWVj&me]F`-_hHuEk/37?$i`Q#m=d`Au3\$j]-aNDTB`50IE +dbscblKdNQ\TTka6=jnU!(f.B6qooH5U8!)(,/]\=>;"2-ib;##T+X+,W81m>uNF(4A]X.BN8V4 +!(K">#e6s +>ZOS%]>;J-aihfG`P]U5aSs=:aiq7'$5V"3a2cKMbJh94aj33r&Tc.5^;It7bf[uIaNVoRbf[rC +\8G2@?&@J0r;HHkJ,~> +!r;`mrVulrs8N>_kl:\^qu$BjrZD%:s7l=4.3Kdis8Dusqu-Qhs82`ks8S<2*?6^"o`+des82up +r;Q]prrggHk1ntqrri?!r;Q]is7$$fr;$=$YDj6>i$TQ"3?GI[!l:F#9,GPFmqn1e)PK +GsQXpEHCYjF`_eB:&+bfrr;rqqV]?1`7Y(dqu$BeqY^0[^kmMdH$aXfIX?m2FAk_XQN-sZmH98J +o_\1NkMaRn\`2lIbCf!Sp\b!iqYBmWlf@*fkO8'2lL4<*GLZ$QqubKs8Drrrr_KOq#:9m#Q=]#qt=0%EU[uqE)^Rk8o0`A4>;kaP+7c#3,DhoPDD/*99>@%Q'7Mr +10n.4/5?!?rri9!rqlTWrr;m+rr<#tq>^K]o(DA:o]599rsAAls8DutrqlTjrr3f2s7[o`-+s3S +s8W#ps8VrqpT,\^8H&MarW<#pr;Q^&r;$0es8CmFle249rr)orrr.E~> +rVlrsrVlcq"mDSSkP>,Vs8Dp?`jJCm(*J@RN5(P#>>!0TqT]SW8I\ +6ue935BUp`EU[oLEb7r;rVm9)s8Clp`5U:*rVulos'X]s^VRe*^!+C/_6A]&8S`S+e'Q+MA$+MF +g"=KbZEggY`m_)5gZ@8Hf\"QuccO5Oc,ml;^V77e\#i3,ahbZtYVD-G\[]Ae^W3mJMi5'`aM>a1 +p&G'jrr2lmk4&ELrWiE%s8Mdr@W1aT/oFU%Db4`.IQK3DQ][c$OuI/\9Tk/h?V6BBQ'RZ#OYL'E +H7MYbrr<#rrr2fllMge_(]F:1rr<#k^9FcAXfA&)p](9ls8VrorVccrr>P_4s7IWU+LqCKs8Vun +s8W#sor&rJ6i6l_s8DrrrtYM1r;Q`rrqOj1T:_tIr;?Qorr2rsrr7K~> +s8W,r!<)os:&F\]csg.Q]u\F?aNDTHaMZ!=`mG#0'+J[,bJqB6`QZP=t09_T_Sr\[`7<4K75^%-dEq+/LDgGeMd*9SL`l6-LaLSskZHph@bfIcC`QHi_iqMj+ +n`]K#d`SkJ="5O(6S:Ml=TAmP()8Jb9h%B6@U!q_@UiYA:K_0-7ScTO +=(qe^YeeQ=l0n6(^::i=oCD/,cg99*a2l9Cd(R08b5TK^aT]Z(cp:iSrGhgXpMhg(;.j`9Bp!d' +/WH$VR?:YcdBtX`kS]s>#\L&;bqVN +_o0L7c,[iC_o'L8b0&#Nccun'%@bPY`ll]QbJ_*=cArbO/_%bU`Q8&$rl>bqbg"AS`l,HI;c$G/ +`pENurVlfos*t~> +!rMopr;R$$q>^!DlMph^r;6I7s8;l0+!VpQg\^pKr;6NoqtU3kr;Z4V-Qj^Qn,N:]rXJf&r:g6g +s8V!?o'55frr<#rs8M]knc&RdqYhi4BeV5H@UMPP6%+iXS5*ETI[=AADEEo;T95J-4ZmeLHp2@d +/TgN'6?@XrqeAKqt^'dq=cZ&1Ao])6%OFMb,F`q@n&-;nH;f[)$HApAG +s8Drrrr_KOq#:9m#Q4W!qY+!#EU[uuEC7rjlL2+!UoPa7S]6Yp.-P_M28240_mR$3`&KKi[2 +9K8^r;$0cs8W)tr;)'j*d%AZrr2rrs8;co +robdQ/D^7&qu.-(rr<#tr;$6es8Us@lJ)77rrW0!rVleH~> +rVuor$3'u)q>Sg8XS2Sks8Drrru_1;\f)P,*7t#`s8Dlqs8;]ms8Duc73WZoD!q?#s8W&srVm<& +s8)ZnZ^mhmZa'/rrr;rrrquotrVlcomf*4c.K9;DnRjI+>[Ul(-U2s/o_e[crVu0^rr<#t(&e.3s8(WR[Ag+/XHN/Prr;usqu$Eks8W$(r;HNfJ/fDF +r;Q]q%K-8)r;Z`W,8_f^r;Q`orXf,/r;?Qns8W)mjeJB;SC7?6rW<&urr2qJ~> +"98>squ-O#qsX![?t$L]aSa1'aMu3>`m:+Z().1id`]_J_T^=-*HO-,o+?9N_& +=]&lkNf'EiQBs]qEftc4CN(hGnSo.YDdbQMc,[cB`QQcR\\,>Wci2.$b/qcHc.^\,p[R_Hl1!m* +lJL4@5QG@X1dP"l!%A]=\&Df9]u+6tT]l7Kqu$Ej +s*t~> +q>U^!s8V0:q#CBnrr2rsrX/W"r-g4>,U^D,qYpL+q>:3frr;iof.@gX.^/jOrV?F$r;QKkr;ZfB +p?h,&m/I%br;ZcorrN-!rr2rtnc&Rdqu.u93&`?l?=,W71PV0cR$KdF5Cnu-p,RpA4R[s8;ianb)88q#CBjnE]Q4lK4&*lFLb7JphfaEdMe4'*\F@&63[g +EHd;io`+mhrr3)am.gSZrs/K%qtg%eA9%-[06))#2Jb^)P_gttPaIi$Q;gt)<*toG3,1F?O-Q&p +Sn_ +rVuos#lXc&qu".'`qfW6!<2ur!<2ut+8c"_-PdXE]DMC&s8Vols8)cqqu5+q',Dsurr;rss8Drq +rs\f)qu5oJWL^&pci6Kq>^Hns8N&^rW<-!rr)d-s7Nh)XKA_0S]LEts8Mior;HTo*W>p3 +s8;o4*[2lss8DorrVlcqpAabe)'tRFrr2rrrWN9#rVc`ors/Dk[[s(b[.sY"s8;uurr2qJ~> +"98>squ-ObqsWL,A:_G8`l?0Fbf\#G`Q$$M?Pa:i'rBX1cHOVTa3)-4aMH$&)\!5lP1\.%`Pop@ +b/hQ>`Q6'E_o&QVAQ2rJM87'RrlY8^rl+rZr6"NM0ZqV`b/VE?b0SBO+WXKc?8j#^KThjiP;cu6 +I#VN3D+eZ1O-u>q;D`Z]HUMd>q/J'*EBc6n5BW*C +.U%t"Q&h,+ +rr<#trr3-!s8UX"rr2utr;Zcq*r,^+Afr6C-RMHRjnJTMr;Q`mq#CA>+ri'Wo`+sgq#:uaHePa[`%LmL295C\o>8oSB/Pa@]' +P%68"Rt*P60gT^IEYC6krrhrNm.B33s8DqI~> +!<)lr%0$5)rr;8`UZ;(Js8Muqrr4&=s7TtA(`OS?7[E*-p\apfs82WlrcK7u-6`$]s8;lrrr2iq +&GlG*]V2.!VQfgos8)`prr;oqrr;6^s8OPIrUMWk1L+0b8P#)7OdCih3bCR&ODm[*@R-*APa7D? +/qTL?3+jm/D")CiEb7r;s8N&urVufQ`PfaGrVlipqu$Kns8Uul_SX4/_SXR;^;Rk)_j=A'@EO:N +iS*;UhV6]2b0J&QdDO+G^"hQ%gX4BodFZjhaMtm$^V7+d[^*#Q:K^P +"98>squ6UgrVQ'%F(D)]ai26Ac-=JP_o'@;d7k#_)&Nu[1QAC9kRra3)KErPeiYr6"KL0ZqV`b/hTBb0eKN(FN9d;b'kqQBdf!@kWJ'IlV0r5]-%2s)33k2pfgj`lH-CeB5D;]"lt=_8sdCcHO\leD9cjp@[n> +p%S.Xq!d1/3uf&`1F-QP#osj$,;1l;4%E%59E6:K"W&O91,:gP4$#Z"92f%f7UU"JG>p^f1hN.f +iVN'>jm9dtc,@Z4lh'N8im6uCaiDKBb0[i9aiaV(s2t_fajECXE,ff5DuO_VDu"B+DKAB(?9i2D_[<8S!]P*PEh8M6TQqS`5^$Ca2Z*@ +aNMfPbfIg'`Au#^c-sAHfGP)5R*WXbccjJMa25mAIM*/^bJq]J`l6$Bbf[rFbfn)Ga2G]P;+jbn +cM$u,qYC*hrr7K~> +rr3E)q>C9mqZ$NDf`2!Nqu7fmVp%n^ep\X0ZWVQ>[p%n^go^VVFo)AJ2me$5IjeQ:M<_B0cSQg,[G%+Gb"T\W($5?EW +Fa/=qeF`e>rr2p"kj8*Drr<#t"T85k?XN5A.T8`+4+WZ]/T[5APaQV^Df%UlQ$5N10p\H!P+7f( +QBm\@7k[!jq#CeGoRGrVuirs7uE`qu?]ns8VrqHn-7UpAY$jrr)lq +s8;o,-6Q%Yr;Q`qrVlirrr3,sq#(0krr3,Xm.TN1qu?WoJ,~> +!W2iort#)+r;P6@ZL[ehs8Mupr;HTo*;]\P*YfS-+;l:RB!h=,s7ZKmr9u'X(a+\8qYg?lrr*?+ +rr)lns7Mn^U8k'*rr3,trr;utrVl`plM`=6rr2iiBdPoL?8a^(P)Z)sR?BIC6ZJ?G2em7gKohXa +P("[7OG>`)1e)KRE#rR`q#:6ls8Duqho3OQf)GXHqYL-irr<#V_SjF3`W!_U^V.V'`3qt(Ui +s8W,qrr4YMqsC1hFM#H2_o'XAccjDF^rFOQ=:u/_'cd_l'MFeFdF5tVd*pAV)%mHsf?))4`W*sW +a:QA1`6H?C[p*BF?t=i)_90gtaSj*aaND`Nb0%fF`q%2)b08)PaMu410[p:1lm`VSWK2" +1h]0oPDD26E&NH5R$3_`-&dtB2e=j:Eq+,^Df&i@d`fbOa2cHSb.GC!^tR$3`XKi.bgYCrfC&8( +p@.MLmoK8RjipPnQ!'VAUD,7')^cRL2*=2p69n(,'`]9i-8$c'4#JfX853#a>Z+F#ATVWf\qt=6 +W17e*kPji1Z*_Np`l5OUr:omBcg99*a2l9Cd(R08b5THibf[rKcpq/WDf9K2rc&!XEHD"ss)0Jc +5t*b^P*%55PE:omEC+JV3Ju4'h=\tEp^Wb*Lc-"'*`W+!g_SsO3gLq`@9Z4oebKTt+(!4A=SJ2JdcH+5Q`l,m?bf[oDb07m) +`=9_[=@l"a[IX%cqYC*hrr7K~> +rr3'!q>:-j!rCX6q#;N:r;QKkplm9T+!hjN+rqsR.>6@RqYC0$,9IpQhY[3Ls8)`orrE&trt.MinauYWqtp0[m-soNn+62EmBKrUH$G7/1oH^$MK)]0!!W?%$j[_LCj1&S +O4 +!rW&srr<#t%KH7GUrW3.rr;urr;?NmruD";qN<0G(*!r,)]'S9,_4GDq"Ocp*?,kAhYdBNs8;j- +rVZTnq<=3?W2-NNp](*irr2rrrqucsrr;Bbs8OSJrVZ.44\Ag<0Jo&ZRZir%O?7,JB9&-1DJ`0V +OHu)oLa8+KM)RRn8T+EErc.dR*-#n/:&4bcs8W&tqqoB1`Rb%drVQNks8N&uiPYZ6>J\7t]>h\% +_nC_AT7:p8P,s@+f@SF+f[/!gbeqN>aK9+,fA,0-d*0b\c-"&A^:V.fZEB1"]YhV!Y"#[d9"i*Y +P)c#pNK/pV\])S&_8,aDs8DrrrVG[Gs8Dp&s8Drsoj_6HEVseUEW:"XEW'i0Ec"cR0/nKMP:)'N +PEqG->&[n=Od'BMBfqK:Q]dGsPEqDuM)7:Ln,34arr;urkPkM](&e(-rVZ]fc`aIWWNE(np\k-h +s8;ios8W$(s8DusqnX3k-ft=,(&n75rVuc:*ucL"qu6Wqrr<#trr)iqs8W'.s8DGfU7.dip&4pj +r;Zcqs*t~> +s8P(UrVuorqtoH8?Au\BaMYp%il!aV_,<&"V6U!n)8i6Su*@ilq1,q3u(OZDkLTa2Ym,lMpnRjNm2EaiDKBb0[i9aiaV(#gLK4bfrXSD/]Dn4*,C[ +D/jQ6DfKc8nU;NP+@u*R?NktP_qCc/^Vefa2Q'>a8O'Z +b3m>Pb/hTBbg";M_oTBL@oZDt=^,1>^rX^Ibf[rE`l?*>a1f.%e>j5s%_g3.bK7]Aa2?!CZP*i\ +dEBbU_nsF8bf[oDb0%`C`Q6*,@o#?jRH4#:qY:$grr7K~> +#5eE"s8;]lrr32Xi;@H#7SBFE)A?FE;MCEckr$q>L?ms8Doni5NXQe,0+Cs%WLmr:oiuKm.TZH$=OU +H$+FcMDVQ=3/VE:%N@)aiqr9;p%\7Urr)NXj)OVanFZMSr;?Nfm-4'3nE0H6\l4f>I!1.$;.un? +;I^CEqu@-0)-L]mFF0"so`+mhrr3)am.gSZrt,20rVubr9lkArFE)8CDfTjsE\_QWE-?>F($grtP;/s5`J< +p@7Y"s8Voos8W)tr;$6hs"sTPqu?C<,9`]Xs82WlrVuonr2Cbf+o2'8s8MurrVQNms8Mopr;6Kn +s7YR=le_[Aqt^0fs*t~> +"98AtrVca$rqN.\g\q!Krr2rr,l[iDqu?Biq2-CA(EFD1)]BM3+!+CYo^Gm+)'W#Pr;6Nhs8W&s +&HDb+rVQWgYFhYhX2Of0s8;Wjrr;Bb;uQarrr<#oqe.i8?q2H(PEVE"Od;/l6=3hmOI'*NCLfbm +QB[c"7$*ss1,0Oapf`e(<:!bg">M_o0O.;UjRSf[A0mbf\&Ob.t^*Z+@N7;np/i^V%'u +IB7I>\;lc6O-,WhNek6b`5TU0p&G'jrr2lmk4&ELrXf#-rVu_q96"leEGf] +!rr9!rr3<$o^gmJ=e?bKc]"uS1anNjVb3R,Gc,IQ@bL#$C-6-)R*g)YMR[0,'PEA4?=]:HM>$#NO +6]dY!R$'k!SVf0Y>#&mTH#[tQC2Ig+DK9]=FEVbID,*:=bL"AQa3N5X\\,Gaf$"'6B#i$9dG31' +nFQ8@mJ$2=k3M6[>cjl$cf!EUST?5f4X_jF76`t&7S?TW!%oWH/h]%B4?Gbr9ikV.;d+-d;9[e[ +kjdbiJ?OEriO-5,^<=gB^p`S,qXE@fm+9A"aMuBL]tVV2rl>\pc,n8VMDf$`F*`%PG%tf2E;jT5 +G&_YACf!qm/VfCOKp7pkNg)A +#5J/ss8Milrr3/]k5YJ[r:p74r;Z]hs3Yd6,9@pM*ZZFF+s\6[J05mU+M[aPrV-?grr2p'r;ZfK +o()5(nG`IFr_rals8;oks7;-o3a+p.Q][T$Q'mu+P=&hm6&;"Z3-T=%P)u;sP)tujPq>$rEHl5BD01&<459Z4p\t3ks81`p`5L4(rVulr?N9otoYQB/F`i(QG&_MSI"b=;Ui9gl +70`qq)a\N/p$VVOp@eF]p$_@3a7B!!qtL!cr:oaInFH)DkM8n(J9#RTKS#78SXh&b&c`1;"TSN) +(0PKoF*W_oo`+mhrr3)am.gSXs$utas7KW.FEVVDD/sW9E-6,+F`VhGEcYJY:+hWpQ]ml&PEgJ^ +E)p#(Q]4:E?UL!@R#mDlOd_T!PCl+S8"fJRs8)cns8VEa"TSJsq#:9m$iBu&s6f+Co_7_6mf!1c +rXo20qtpBmrVccps7VWu,TupWrr3W.rqQNmAgJTMq>C6lrr)ipq>C6l&cDY+q>L9lrnHW!kl:\[ +q>:-hs*t~> +s8W,r!<2ut#QEk^W9+$Ws8DrqruV1;s82NicYY!N)B'S5(E=84*ucPT*?,n9]D)@)q#:B0+iEs7QWj&=i\+ot&s8EW/rr<#trr)irrr;YP)BKgio`"jurVu]nqd:Kb-2.68qYpWpqu-Hm#lXMG +T:qmNf_k^J"9/?"r."~> +s8Mus#l=/]`b&ST`5hl"s2b/Ys2Z;0bK/529G%pK'GqT%*#0A0&fPQn#Rh!3dF-"Hai2:!aqDe8 +`m2cM_ld9N=Bo:$_8a[=a5P*>b/_H:b/;f[G82`.5;8IhQ'[`&QC!b$@V.l\RXI,:ai_rbdbF9_nabu< +o'?#3gP(Z`GaRLF]YCYMIOS)S-U'm^5"8.=;dTM>0J>%40g.ij6q'[G=]o<9Fu[S"lKS$@l.&_4 +Y[F-/\AZ,+`lGs+l1ai>i6UcAaiDKBb0[i9aiaV(:Wgr&aOJEu;/LQ&H?+%@C2S*+<,QZ$D/3og +8l.-tOd)#sPEM/V6>ok(PEq.mB4"#hPaIPoOcc)uP*:M`/3/I+ai;*>`lQ0>aNVlL`p^t`bf\)J +`l6!?_n1M.;+X_c=*M6raj0n.r5^q@`Q5p=a`7Ut$Ja%Wc,@90^X_+9'+Y`-ccF&@^r+46b08#N +`l?!=bf@G_=%l7qYjqbjqu6Wlqu;0~> +$i9l&s8Mons8VEEmJd+cqt^45r;HWps8;`kl_70!*$ZXG*[)XF+s.jL+Y*-[s8VloqYpNps8E6& +ps\p)mc+0E!;tgT7Jd#]rVQWp]hCIj<^(hkPE_GuOHPip=]9-$N0'Bi1il9#R#mc)R$=,&1dY,C +DK9`6G$-qQDfTf@EH?,@FDc@tp&FmfrsJ`)qr#N5`7=eas8N!Os82T`_MC-_o'b.do)JaXrr2Znp\=^](%CO?Lm*%)H$4O_LM0tbD^$#(!!``- +!"098EHlSNN7@M"rVlfr"6f+Hrql^bq#15q8p#&kFDYo7EG]Z:7l!S=G]%n#0k;tmOd;8uR@9IF + +s8N,tqu-O!qoP_Bnc&Rdrr)itr;HWo)>j7+l_.&r)'Bt9)&sS2*?#h:)^Y%Ks8Voos8Dp/s8N#o +rqt*,X.u]#o)J^err(gT;Z6Xorqufr\Oe\\<^1nnP`h>pNf]Eh=&E]qMN3sa12fWlQB7Q'R$=,& +1dFo;C2e*-F]^_MD/jK;E,ff9Eb]bjpAb!fs8W&sqr#H3`7=hbrr)orrVloT_#D5U^r=11cb?rl +.H'cd'bV`5BR3];K,`gtBd"ccaAK`PfR*]!f1^Nk +!<2or%K#b[UhFVM`5fp@b/h[%`\tr_cHsn^e&d4t#mLtT%20Qp&f25o&do5ud*B_Iai29Bbf\#H +`m2`J_N@])='o^H`lcEE`8J^Cb/(s3ce4mL.o&i"G`e;UQ]dDlPEo`e]YtTb/_WHceQt'nFQ6Vmd]f; +r8uYI>-4K.i4-G1^Vm[F5=\!o2a0Gn8PD]M$tPE_?"R?grT?UUQQP(5K2>#gZIQXF9kPF.Z#QBtB-/$RLC_8FC8aMu6@b0%fFn&Q9gb0%`D +`Pp'?^RSe28lAJq;mO9_b09k-rQ#2ba2Z'BcH,tr0O3M_aj%rOe(9in%gcCgc,doB`5]pAb08#N +`l?!u4.!P3i5Gr;Zfoqu;0~> +!W2lqrsJc's8Ua+p](9mrV?HmruM%6s8W#srV6*9I4H*\+U?m"l8)- +m-+*@rrW0!rSd`Ss7H?krVQKF.6_!7;O'p^p@RtLqY^9fs6Im-RXoa(Cj(,MLP%4>U-1%E!!*E* +!"0-0EI)hUNR[V#rVlfr"6f+Hrr2phrqufos82LLEHuMCBN9"iG].J27YsQtH#c/dGq+uHQ(",( +P&*I)3JNBUPYPJ(9jEsPO\ohJQ]dT$QB6>R2+[9;rr)lrs8V?_s82lrrr3#uqu6U"kN_[/p@cu7 +s!7I +s8W,t!<2ut#PYBr`qfT5s8DroruV+7s8Mops7uE=Hn#jU*#]V8*u5q;)M@\^rr;uqs8;lrs8W$, +qtd@`X/Du>r;HTjs8V'W;ZHLks8Mokf-WRa;,`gDQ'[MI=-/Z1@8:Z'G*J2RD*N/!MjBWkPa.Mt +.o\btEGone-U:IHDf0i3?s%AjEH=7Oo`+sfrVulsqr#H2`7=hbrr)orrVloT_#D5U`50C3_nF6% +OfW8hRYZfaO-#EXJ8Wo_cd'_T`Pfa6]t@(^dEpFkccaGM_SEgq]X+S2[^jGt]u7e&a1"d9XBp=8 +Lm+-_PEM!@`50@,_"Ia-rVlfpqW@;IrVf:bqu?WpqXugbF_u#-;fQ`"DJ`oOL-guEDE_f[.[6Hf +R$WnrC-Z\rKSPbS9NlC0>a:b?;/(fPP*DB#OE`oD8+Q][rr;uss8W)t!<;Ne!<<&ts8Dus$iftn +\ZDp3XK@lHo`"k8rr)fps8N#ts8Dios7ppV.MI7*q=smao7nS7'-k!^r;Q`rrr*'#rqu]nrr3?( +s8;]b\>#RdW:p2i"9/?#rI=~> +qu7-(oA5.4K=o+Aai_cIaSj+8aiqiIcd9q`d*9h<>n-iI&.oKn*"s;)#A2B6bfI`>`lQBJb/hTB +bf[i:Cfk%J@>)A5bPoQ@aDoYFa2Z*?e%QVR<(p&POI)>qD-2pcP[[F+5BiBWPAWX/1lR`cP*D5s +P:KX!5B:^_3u^(MF*)>E@9ciNF)>kLR)mUsa3)HAcIL"G^::f*bJq]Ia2uKMgsXsHoCMYDo(2M9 +k&$0ZFh>T&]>qk.9"gsD1KZIo5"&"=:.\&SC:K(._85;uYbgISEFED_Ca:b?;/(fPP*DB#OE``61<@_Z`6?C&`rF0^aN!\i)9U1GaMu6@ +a3)'#CfO5&/a([:02SsqYU9ls82ZmJ,~> +!W;rqrri9"s3fNurrN#sq>MK9qu6Wqr;QZnqtKjbp:"`_,oRm@+s@aTl21M[rVcclrW)iprsR3A +nEf07s8;oqrr(gT;uQ^ms8Muc1I"u=.?0LZR@'+@0U7oeG=QIZ7ZNk&R9i]d@?Zq[_=>Z5TVDe`/Hs82irr;Z`oqr#N5`7=eas8N!ls82T`_Mdro_%hDnFuk[s8;)H@uPqQE-Z5@DJX_Y24'1D//8$_ +$j@#4D0UAVO4]q>UEo"9/5qr;?R(p#,3%g]. +rr<#ts8NB(n"6H3rVufqrVl`p)Z0O6s82cns8Mljs7a!9,U+!D&02`(.H^I+rr)otqu6Wq%K?>) +kE`'DWikI]q>L3is5O#UrVl`ps8;=?4&o-+Nfoj!QB,0$PEV,O7;?7!N0]`q>Y\%$PE_2qOd21b +?%abM@l[*E87uITCiF/o;K$>hB2JL*qu?Wls8N&qi5NUPec#IGrW)oqrrLulrkhsW^V@\#^m/-& +OC+o9I>3]@P)ti^D5+toc-")F`5T[0_0;hY"`GJ\KQVrI% +P)YfjOH.)p_SX('p&G'jrr2lmk4&ELr^Qhcs8N#tr.IpfE)jQ[@;KdnEHO;2JQP?S4-+\EOd)/l +P*;(iCc.*8R[K3gG@s6fNg5kb9q7K4Q'ISuOu=j;oDe^eqYpKos8W)us7-'gs8)^Qs8;onqTu@J +WNW1n[.=4rs8Muqrr2rtrVuoqqu?KR,odjK7$X+*@2U+,*[+/Fqu-Norr2lqrr;osrr2p+rqcTk +ps>V)S=]'GrqucrrdX~> +qYpuqcZ!Wg^r4.6b0%fHrPgh<`Q6BMbK\5P`P]aGc]q07&IK0[)Arkq_pHfR`Pfg^dK_n9GqFDc,pPWEY>MjBj$O=kck6@"TVPql((Q'7JuPa%=f,n\>F_SsO;`l5p:b/hTBn]2ZhaND`L +aMu6D`lu91LK[YM<)d"@[DU/2bf\*,a>CiUaiMTPd_lP#%i7-TIXY3+&e>?X/]u>[aMuQ +bfn5N`l6$=ahtQD:esu#ch$u3rrW/qrI=~> +s82lrrr3-#s8Ua-rr3#rs7uWsrVucerVlg5qYpNnp\=RVjc)n[.iKT^Pl:UXrVccrqYgg!qu?]` +fCSIqjo#,Zr;,CO7K*5cqZ$Sd-QQk[0:S)aOcl/C0T;3[O#:rq0pn#iPF"jG5C/QbP*hMp/T$_; +C-c3em7V&=F*MS'XKY$.GX9@Dq>L0hs8W$'qr#H3`7=eas8N!3s82T`_M>al&,lP< +!XB0.CNb&TO4:'N6Y^O<;cec_FEV_B6Mn/l4D6X5O`-GIQ^O>&2/5Hs +PFIJpIRIgo1Qe2fBI-68PF%MtP*qGA4unJrrVuoqs8VEas8;osrVcitrr)j)jR)X6n`nR&s7uKg +rr3r:r;?Qmrr<#rr;XDN-6sf`-Qj][,Tn +rVm9)s8W#OT0s[jGZYT-PdaOOo,+RrVlisrr2lqrr2p* +r;FR$X/;o1q"X[arVtmV;Z6Upqu?\e,o^DS/tA&aOcYr=/rGdSNAGNi0:%TaOd/F?4aN?`P*hMp +/SgM5Bg#a_mRq&6D09YpX04g,GX'4Cqu-Elrr;utr8>T4`7=hbrr)orrVloT_#D5U]u.J!`3;k* +W(EM!GC+afM3a?fN.OREbfn/J`59C-^::aSa3)lXc-4DS_nERg\ZJfE]#DV*`5oj>_J=6pNM^6+ +Kp7sgOcPL:`PTI*_"Ia-rVlfpqW@;IrVlcq8c&>^j?dKp;,C8I??(%)C-ha)<]O\HMihqAPEhK& +M_f$3DNU?DP'fT-G;GAONbB_1Q'7Q!PEM>r@QQQ+r;HZqr;Q]qs8N#ss760hs8W&srr+X.gG)]]h<+4'ucr;Q`ps8E&trr)j-rqcTl +qW4E@T:ad6rVuosrr7K~> +rVuop%KGq*BQ^Vu`l#m?b/h[&`]1r\`P0I@c-+#Db/_QGd*BUL1_L`8'cD&?ccF/Ga2lEIc-4DQ +`l?0?_h:rs=^?6]`6-EN`PfdUaBd!3a2,mKHOp]q1+JWOOHYlr@PD(q<:R$*b+><[@t +SW]A*N\,(25A*)UE6^7CIX?HZ:o:?ZBPoT.d)j>FcH+&Fc.'hE^::f*bJq]Ia2uKMgsXsHrps@j +nb2b2>bnE&`id%raMu9E_7?e"!*Vrc;-$[q:.n,M@K8YJ3]]Vt6:4II?XQuC36U@VoC)8HrVu_d +UO/Z>Dh=[o_nWgm]'T1`o&J'_f?)(S`lcZA^<+OsaABq!`l>g7d)@VDC0"D>M.(^VE+:KWR6`T> +3g,\DOcbrsRZ'(;6#VpTMN`HUAp?+oS;q8/8X5L+P`q8uO^L'oA'2j7bK.]B`Q$!?`l5sga;`.? +b08#LaMuHGb0%8f@TH6!<*E8-^<+UDbf]n*(rjP;c-OSXgmS +"9/5qrr)j%pYP-6s82irq>LEnqYpL!qYpEjrqcEfrsnW"`eS5B[f?4(s8Dutq>1'sr;$Bmf]2Jj +kkY2W!r`#miVldUr:pVD&%Q^=,+Q<$"^Q'RFgDfn]YQ'.MtMEQ)>L7=EiR[02,O@<&9 +9\B2P?Z^=,AVE`FJm`O[kkb>Zqu$KorVcWO`Pf^Fr;HZprYbb5q"2FYH$+7SF`;MOAo">\66-42 +!s&B+r;])21$eB#o^qkOq=sUFm_4qXnFQJOp[e(Mo'u/*7#-;-I=$$UCfD2X%jj$$SK&m^!!!',% +oI1eH%(b&o`+mhrr3)am.gSYs$c_Ys8W)uoKI;(:M=BZ7riWl?=Wm'AnWkZQ^a#&Q'%Ao5&XY#Q +^*r.P?q7,@6p*=P[R$OQ'@]"QB[YsO?,Cqo`+pgrr;<`s8;os!<2uq!rr8srr3Apjn&$9n_=*Dp +\Facs8W!5s8Moqs82cgpU_[m)'pIM*[;sM/]7V_qYgd!s8W)rr;6 +r;R!#rVF7$_>*j*rVlotqu6U"qYpEks82Tjs8W'1nG^=XA7i.Hq>^Kns8Voorr)iqrr3B(n=ubX +Uo`ANpA"Xcs5O#Us8;]mq":LM-Wi!ZQC!r+Pa#-+C6Om@1i7;*>Eb/7OGGu>1PD0ZO-l/pR#XO2 +6:oi,@ptSU9D+/;YE*QoOoO_bh2jBelR$E_sN]_/] +9U1g-RZUiRCgTJlOHa0C8!T=,P*V>tOcVI[U%\Ncr;Zcqs8W)trr;Qgs8N0!r;Q]q(&e%,s8)B* +X/r5&XdIZ8s8W)rr;HWnrtYG2s82cfosc(]&g&,5(E4D2.Dc&YqYgKnrVlcq%fQ8%rr)GlS=lLe +q#16lrVqB~> +rr3Q.qYL3ZO^j-[`5Ta9b0%fHrPgh9_T^?J`lc<>`koU9da?Flb1i_*921]'bKe;OaN)3@bg"AU +aMu6@_R?GC$^rXgKcGdZ?ilHPpaM?$:a+l8g<@WiiQ^O>0PuTeZPa.4cDKJKUP`_;pM*,l: +Kpn3eR?`u(O$lc,33pq#:O%5-@Xgd6JQcS3aj85R_o9^:`QcoR\A#Pde]YtTb/_WHceQt'nFQ8E +oCMG.V0]1sMlOG2dEKVV_T0R&O9B,n8l&;\:d.ZP=]T&f!)#XD69mUq85`Vs=*R!Qna?,=lLb,M +_7-kLBWeXJ]t:rFp[dt9cg99*a2l9Cd(R08b5TIYahY^6bfJ;\-"C+RBk_T[FE;@s8(c-[ +?8"L3RuEYsOHkkiDH]c#Pa@l&>uYZK6'ICp@7EbPPaR]#Pa.Ak1FR[jcH+)N`l5p:aN2?>`q%24 +`lQ +"98;rr;HX$j5KD*s7u]ns8)^#rVuflrVuoqqu-O-r;$?hs7lHjq"aperVZ]ms82imrWrGts8UL6 +md009rr`5rr;>OQ7K*5cr;QQa0j7p9Q^!PtPEqJ78kG)4Q:kJ$A4MiPP*qVt1N?3%PaRZ#QB[Y7 +->%MqqM86iGB?_T=)(<5E's3qr;QWns8W$'qV]?1_q"\`s8N!6s82T`_M7L`?!>-o"S6I0PrW!?3 +&lNUiG'o;!o`+mhrr3)am.gSZs%31hs8D]lpA%a$B3ASY>=3IUF&JG$pPET-B9A=EPa7`$4^i4f +O-H#tQ'O7AF\-22P`m[t?'CJBOd)2sRuL*.qu$Eis82irm/I"^s8Ni5r;6Elrr2fps8D!JnauA9 +j8])Tr;HX#r;6Hmr;Q`prr3N)_(?rZ,:"N]-&:a\q>^KlrWrK&s8Mroqt^0grs&K&oBYo+f`(mR +q>:*hrdX~> +r;R-&r:m+_c27MU9kp\Fj_p\aper;Z]pqu?ZprVlfrrs\o* +iKC1+s8N&ri5W^Sec#IGrW)oqs'X^!_o'F0a2Pj,7&6YP +BlnuJG^F[\M3+*eMcdI2bK7fA^::Y_[BR&9^X: +rr3Q.q"O^NEaP!I`l#p:aNDTFrPfnta2Gj8bK@iFc-=GVaMlBFeB,tgcdC4dai;K@bJjJ)(Adq?P*qH"Q&d\> +:1TQUOHu/qPZ/l3@E?T%6uINu:IJ&q86K@NcHjML`666A`66ZM\A#Pde]YtTb/_WHceQt'nFQ2; +o'thj:oLL-QF"cG`QcNCb/M=j]9k9`=@u:_9i+bj?!q;[!*rc#69[Ip9N,"s>\@'hmJ#`+oChdc +]#MRgK9NBcK;Z<"^qRSQp[dn7cg99*a2l9Cd(R08b5TIYa3)TD^a(;Ecc(]GgO]K +>TdgIO-PfkR$'R>D)[K=Q^*f#1MLr%AsS[F/Q5r=PFRc"Q^!u!7l/iEai2QG`l5p:aN2?>`q%22 +`lQYbg"ASaMu<@`Q?9Ic,n)Jc-W1"&J5?X&etjh_9BsCaiOD& +(!">8b08#L`l5p=ai;3)H;mU7F4^'_rrN)qJ,~> +s8E&pqu-O#iSOV7p](9ls3goHrseGUmd0B6s8N&ur;$6eiVldUrVZ]nr/iX64)gLROIMAqRq>&H +NKf`d2fNdmNL#otQBuJKEAW`DP*qT#RVF-MZhjF+7riir7tP;`4)AY,p](6ls8W#srVcTN`P]UD +r;HZpra,X%q"2FZH$+1QE-H_.I@XOm'*8.6!<`K-!!!*$(eV@+mdTc;p](-gnEGp*459H.r;69_ +m-a0,U1TL9G^Xga<>boE!!!rr2p"kj8*Drr;lps85$F9m&'k +ASY7cH#GP:p\=#;07o@QRZ$5E'Pa@MqS=!k[G$RY,OdLVU1Q7QdOI),sR$*_[4]M=UqYgH\ +rr2iqrtbV3qu$EmqY^UBn#QFJts5iY6h>[EWrqcQlrdX~> +!<)lr$N9hbTV(uWqZ$Tnqu6Bjs8E#snc&Leq>UBn$i@T*VPgN:rVu`irr)lUr_`Rks8W%Z/lXcI +PaIK#OcYuA68XU`P__27E%m37Q'IT!Cc62eNL#ZmPEVAI,ZCuGqRK%2E,S*n@UM-.BMJ:%s8Drs +qZ$QprSY]6`7=hbrr)orrVnM,_8F1/a2#a/EJ^H1CNFcKI!^*_I!LI/P)!_Kbf@lJ_7dIc\$E8X +>uAbfb/4.o#J7je]nC=7^Abtob--AS=l_>q#:3kJ,~> +&c;V.rV6*[dq2^p`l$$@`5p+"aSfcaSj*ZaNFJ*'["M:`PoT?=B\j: +J\&hFdEftS_oC]WGg4=Jbf\=R*)/>0Q'RK%PEVGH6T'gdQ&.D;EAp+>;=LJ`Pfd?a2Z'@d`/f3]Xu_;`lcHEai_rbdbO?`na>N(eolb@J!@(I +^qRP%a3)38_mkk!>?`%792/&V>Zb6bBMiM^?lRYXt3ER"$tLN0K`lQ]d]&P*L2D1sX.[bfn)G`Q$!?`l5sgaT'7"b08)P`l?!C +b0.`:VI`do:/+_^ZbXi0bK@oGa8X*YbV72Z_nj@:g +s8N,qqu6U%qr?c;s8)]orr:IHrr3>_l1=E)s8W#ss8N#piVk8(s82cno;"'B17jbqS!K>-Q'=9s +DO$QT?rV)[6&q@sPa.Fs:hhqPrf\^tPERF_jo>2W6Z$pf<(1+eAoLA9r;6Kms8N&urVcTN`P]UB +r;HZprY#8.q"2FZH$+7JKmS/2Srr2p"kj8*Drr;lps84_DFBNL* +GZK$2EEd)Oqu?1O@?I%EOdDJA +!<2rs$N'VNUT4"erVulqqu6Bjs8E#srVlBfq#1d'rr2GaSu8Wij8T)UqYgBmir1A's8;lroV=*> +/tA,hR?j,)PEIjkCm1-L?;bZS5E(qkP*;"k:1uMHrfJOoOc_%Wj8])X6>17V;*nJX@Vn]0rVZ]o +rqu`prr;rT`l,gHrVc`p!<)lr!T(Zl_csdu]rY,,Ue#HKH[L0dG^"=SJ:ioBDM,sOb/2*5Z*psG +J6I#1:X$Sn_T0I,]"bY5GIbW.a1n[/H%1-aI=,pSA#tDdIufVRLpY"?_8F+^s8W&srr)]UnGi4] +2?!&d=DW)%;K?bp=D%F0s78NaO-u;sR$q8YC,(L.NLQ5nN^72k6ue3RO&&k:Q]dH!OID:1Pn/^> +T`=ujrqcWns8W)ts8VZhrr<#s!<2ut!rN#rrr3;q`j)SGWi2?:q#:3ls8N#ss8N#rrsSf)rqc<\ +q"":\qYC-jr;Q]qrVlcq!W2Zjrs.GXS=QA(q#:3kJ,~> +&cDV-s8)EV]ObN`aiM]G`Q64#aS$G&Cm +dEThQ_oCZV-dKWKbK>V_:bA6&Od_Z(R$m7q$!gJ^MBQ`5]pA`l-'K`jidr]@G*Eb0">:ai_rbdbO?`n`KH!]hk6"A?!;[^q77q +_nj()]XjP25$:KN78?fY>?GcJ"hL\<(rIfb07uLa2Q*?`l?0FaMl!1_oL-Vg!n?rbgb(`_oBg>aND`LaMu3:`Q$'Eb/hTA`l?6D +_7uXN8P +s8W,rrr32hiUHmIrVQToe,K@H#Li58n_=*Dr;Q]trquZPr_raprVZWic7_#.Pa@YtQ'IH%M`tN- +Q][8o4D8XoMNX9iQ(3nZ6XEWUPb4,'Q\S8hq>0j]<`aS[G@!#Pb;!!O&ZOJ"QV)@6BX9lFuqJ;r(Ns8Drrrr_KOq#:9mq>NSXTQ3D"AS>s[ +5Y_4Wp[lX!SW]A*P*TTOF>0)NR%'8)P%%@,@nMfNP&3R(1lIWdP$=-;PEV8s6g+1As8)`pkPkMZ +!<2ut(&@b)s8W&Xo^25Gk264=rVQTos8MumrrUBn"TJ>rqu6Tp#lF;p +s5rV4iUm-L"8i,urdX~> +!<2rs#l=83VS)R#s8D]kqu?Nlo)JC^%K5:PUo(9!q>UEmr;HWos5O#UqZ$Qns8("m1P:jUP`M#l +Ngkkf?8\"/Mj6>8D)d?5QB[Z)NHINl:R%$5P*D;f8_O">q>!rfDKKH4CMQ=&n,N=crr;lps8N&s +i5W^Rf)>RHrW)oqs'X^"_o'@/]!u43X'][aH$k!dH[0maH$=:]Mgfuo`l?0:`jiUbRpTuPGAp3e +ah>F*[_T>WIs8<>]th:1Fa/%SI9k_o9L/p&G'jrr2lmk4&EEr^d"fTQ*7q +@:X.K4A#MOq=i*(S<0&$Od0BKE\s8)cp +rr<#trr<#hrr2rsrr +s8Dut$iTYXS7H]ObK\5M`qd^Q`rF-Zb5]Q_`r*gO`rF-Xb5KBn`P8*H?qQB.Pn4^U+5OGoJeB5T_dOd;5sS;qbZ>u6fMSs#>)Jj@2s`lQ[u8nD^> +C1Uj6.*KrR_8=78bJqKDd`/f3]Xu_;`lcHEai_rbdbO?`oCV1uFHNbNS%d+ha2,R.^qme'\@>&' +Cd;Q)9Me5X9j(_6@$@apHn8r=?=-f,EbGi%9`l5p +s8W,srr3&Yg&1jL!r;lrdf0:H%e&-Co]t`@s8Mros8W#piVldUrr<#ns5#:VOHH!$P!"kqPF4aO +:gTu4k0Ye)$jI%L0MosVm.L5J +nEZ9bNd5SB$3C>2!!ubKs8Drrrr_KOq#:9mq>N\XoD6Fe<('E? +p%eLaqYQmrNKfTnQ(0mH:1fNMPF7Yq9j_[T3KfbpO?I/\99Gd/Hm3ErPa%N%?E4#%s8Vops69O^ +quH`prtY;(qYpNpi9p(,qLp$qu6WqqtpBhq>C9mr;?Tirr2p.r;6Eks8W&r +pAb0Zl0[Zurr3*"s8W(K~> +!<2rs#lO>#User8s82Qiqu?]qoD\pls8N&jrX&N!aI/j"X31D>rr*#urr<#Wrr;rss#^)WhC9t7 +O-lAu3ffYjQU#1Y>`bA;N&>Hg2ia#qNfofl6s<\hO,oKmP*TLHs8VomTQE=p=\(YH\bQ.'%fQG, +qu?ZqrSbc7`7Fncrr)orrVmAa_8F1-]=XG;UQJMgG'a+6s*77bGBe7ZH#n#H_oBI7\%95d6Z@?p +GB%_4]#_Us]XthgG_N]EbI2J6IXcNhJTlQjH=KsTP#?%JMiY'U`5o^0p&G'jrr2lmk4&ECr^-8R +`/[_^8r<*4q>^?jT4X%FOHu-#3FkfoN006hP)N%>Bl5kkQBdOpAR.rkSWeFFD46]RQ]t^Rs82ir +q>UBns8Muts760fs82d.s8N&urVc)fZ*1C6URq&Qrr<#trW)usrr)lsr;Q`rq[!2uqYgHorVccp +rqucurr)fprr3W.rr)lrs7sF(T:`F]rVuosrr7K~> +s8Drs$M<#)@#2D;ccX>Kq8iHQs2afO"3AL'`r4!Xb596k`Ogh(?X%#k\AQJ6c-",I`o5!Gb/_TF +aP+QO-'+.UR#a=lR?Eq'BMVorRZrdiDJ_@oOe[r"RZU$C=](9BPF7Ss=,>PTaOGlM-p:$W.lajgj`EN,OVU;bI&aMl!1]Y;)%`P%^R4D@Mb +7o_uTAqVq#\\PkNIlVY"Al;E$=_:$Ug[Y=6[(XZ+_TL0@`4`=*C:pKdPdSb/hTnaSs1"b08)P`l6!@b0A&I^o:aE9hS2U=LH,jaMu3< +aNOP'1WIDO_7dY'bKJ#Lb/1s3`l>p7b/h`JaMu3<_o'I9bfn5L`Pfp@bf.W9HrNg?HJ.odrVlhI~> +s8N<%r;Q`rgsZ3%!ri)sdJj1G"P)`,o]#lM#6+Mss8W)riVl4Er;Q`hrO=s`Padf"HS0N.OI9TK +D(hKCPaQJXFC[CrNg5ouR?BsP3KoksR#RPq:%nG`q==F]O*d\crV?Kis8W)trs\l+rVcTL`59F@ +r;HZprXo2-q"2FZH$+@^P:qia.juJCquH`urr>Cg!Ws94OR;r9qs3S9]f%e_!!`K5&1fcCjm2C' +lfG9UM/u-%#mgS3#6=f4#R(qe-\/'j2A?H/=)re0H]?PIs8Drrrr_KOq#:9mq>NSVs8Moiq"FFU +rr;ljqs$P1T95V0P[7-t5F%\$Q'db.?upESI@$+U2f3P:;jNN/M_n=qPF7T#D2n8hq#(-kli%%e +rqlTjrVm?*q"ad`s8VKJn+6>CgA_-PqZ$TirW`E#r;Zfpr;Q^"rVQNls8Vims8W,s!<2ut$2OW" +s8VcQmH`d.s*t~> +s8N#t$N0.fVWR^Qqtg?ko)AIbn,E=drr*K,oqnL[VPrh[rVlisrr)iriVl+Bqu?]jrjY!^P*qAp +H7X3'NgF0CCFu';P*^&PEagtlNg5osQ]==B23F5kR#RMm9_\Mcqt'^]N-CrTqXsj`rVm?+s82iq +s8Cor`5L7*rVlcrrVcb(hSI+I_S*`VS?5M$I&??(!KH^0\O2f3P:;jNN/M_n=qPF7SsC5hoe +q#(0krr<#drW3&urr2utrr)fq%0$/%p;=#;Yct'pg%bOGqu6Kms8Dut!WDrqrrW2trVlirrqcZp +r;uuus8N!/r;Z`or6h=2T;^uOs8W)ss*t~> +&,Z>)s8V`2E*o0JaNi#P`U_(M`r,\rl4uZ%EcSs@U<;G?_k<4@lT^5Q'Rf)O$RA9P*hQ&NL,>[f@&*kbLbEa>(aiL +dauae`4s=5d)sDJaj@u9^V%2/bJq]IB#i$9ceQt(nFQ2:l!ga-Asg3IaiDE>^q7.j^s:3ETOgr7 +<(0SV=(uSBQ*7g;[_/5`7;$=;;c$G$?>%p_j1i4E_8=F4\\GSYYF9X$Z#tr&\[_%onEoDdm+9A" +aMuBL]tVV2rlG)]s2[aNaN2WQe'udpeC)Ugf$)Ilc7Xd +s8W,t"T8<"g"$*%s8E#udJj1G%*S24o&9WIs8Moms8V!Us8PI`rr;YFB98CCQBto9G*SM^QrI4+ +0U//eRS-n-DDmoKQB7?!HU;:+NLc)rOdgr;qt0pgr;HZnrr;ons8;onrr;oqrsJZ%ptis+_peP^ +s8N!0s82T`_MEJ[H%B1-T5#l"&eFmIrsV='$OHn?-:4Kks7H6Me=Ar_+qY@n$iphU1JWn`jQ>gm +a\%:N,U=BP*uQ(?*%<-_,r.bAUR38J'cKJaG'8OueF`e>rr2p"kj8*Drr;fn/cYP>p](*ir;ZWk +s82^^Ng#isP`D"`DEUBqp](9[rr +s8EK+rr;iGUp'FfrVHEkrUKmcs6]gas8N!-qrsBEV5UKms8N#ts8MuRr_`^ns8VeHAWN%`_8F1-\:AY(Kj&A7Gl;pdH:iR1FEr4;OhJrMaM#-A94i6dI4Z&+'gKnbM?N/E:IKSkA;J4QQ/6#D(+WQ;omah[TLs8DrrrVG[Gs8)`ls"+!Bq>^Bm +qu?HfrquX\N0BTpPDbVYD)mZ9OdVJn:0DFO=&>0GQ&IS=9P0RhQO8IL:6Uj/OckN%qYpKsp\t0l +m/@"`rr3-#rVZWnrVuor$N/baYH>%0S[8"grr3-#rVc`prr;lpr;QlqrVulrrrN&srVulrs8Nr7 +r;Q`rrr<#ss82ZjjJ8? +rql`q$M(9-AurYbc-FPPn]:^K!li=&q9&`Yqo\r[rPnlY&^&"j:Ld[=B"#Fpbf\#H`lcH^aD/i6 +`QcV@c,mrHd)V:;Ds0.[(s5`Y-FdT5?tWU9[UPj`kog@bL+MNa2#a/\"?[$W`bReZIAF2oC1/of?)(S +`lcZA^<+OpaT'76`Q#p@`5p$Nc.("fa2uWVh(O87P*_Q!QnE)3OH#ElQAn[DDJrWSQB[\o2/c0# +Q2[*bL0.37P*qGuJN;$&c-O;I`Q$!Abf]Isrl-&&bfn5K_o9X>cHXJL]9QSq77g9WGe_8=`5BR: +c-4?0`W4*ZbR_e5^r+17be_3:aNMfN`lS+u)T^(B`Pp!Dbf[rE`l?'FbJqGp>>@k!Z0:`Qrr7K~> +s8W,u"T/5jgY_o.dJaRSo%3R,n+6SXr;6BhrVtgT;ZH^os7U;^P+Ru*PZgLMPEM8nRQ,/(6Bn(( +P;R8p@m,j:Ss,Y0A56*5O-l6"PF#IWr;$'as7H?kq>^Kms8Moqs8N#ts82cihSd:Ke,91Err-7( +qtTg#Lj=,jMC5$Y.=-&B?sR#B?!LQ:>@:W7<,,FVmd]r(b@k=OHuEk=AnGphCj_=d;Sq>Yo$g-q +H"h#4Bm+6$CN"*1Dea6;;JDk9:j$M:A8Za;NR[V#rVlfr"6f+Hrr2rnrW)ips#TlMs8Vrqs8Dgr +O-Z*#OdM=uBKLU.R[98)A5@5c?9*n)Q@LZ0=A4sFP*:l";bE= +%fQD*s8Vo4U;-['rV?Ejrr;Ths8Vcks8Voor;Zfr#PuW5W2?Q4qY^Bnrr;rUr_i^prr2rlD1Rh? +P*V;76qi`)Q&V.hCMuJ$S<&dgD/*QDNg-&rQ]b`K;IGQQQC!Ys=gA#+qZ$Tfs8Mcms82iqrVulp +qu-Qos8Lut`5L7*rVlcrrVcb(hSI+I_72k7TLuH%[^`fT[C*HQ\?WTT]",+TZ,OJo[(0mh]t_@q +]tMA2_SX."LJb1[[C*&)]=kqp`k0@2^;e1._8!Y$_L$^1Fb@NMT";YX]#"(>s8DrrrVG[Gs8;lm +rrDurs$$8Ws8Vlns8DgpNK]WqOdD.nAib:(R$Ei!@SLf[>W7J!P^Y<,=A4sFP*:l";bE= +%f-#!s8VVP>B[Ec`m)rSrl4oXprWf^`l?*@b0'\,s2tA_s2G&]rQGAdaN2C#`>$>5b/9*:@UtYc,7fI^;7e1cHXJMaj@u7^V%2/bJq]IB#i$9ceQt(nFPkj:U$["bi7UF +m.C&Fo'G]-oC;>;a>mO2:/k;=7rm>Kp%S=Sp\jRRld(2=G][_1CHBr4p\+@Iqt]p`qsa7Wq"!0j +I^=m(j6OeRnb:kcm+9A"aMuBL]tVV2rQ,#Ys2@RI`Po^;c-*uDbK7`Eb0/C#J;fSTPF\+1B/Y%$ +R?j&%@nq#_>r[\%Q%(K.=A4sFP*:l";bE= +`luZMa2>2m;+b#akO&!Bs*t~> +!<)os"TJGhhrt"XQ'dVu:hFQD?B:88PaddrD)mB5S!02+PXP$9s8)Wmq>UEjr;Zcms8Vrqs8Dur +rVGBh_o'purVlisrET9rotlH/H%Sn6TjD2`FEM\IF)uJFEH-)EDfg)Q;;CN\[ +q>:-hqu?Torr)\p?BpkEQ'IPo2.&_;Pa%H#JNmpc;bEIAQs_[eD`cR +q>^Ens6Tabrr`8rq#(-k(B44,p](9js8VNRn*K]Df`2!HpA=miq#1Bqr;$9gs8W#srrW2us8)^# +r;6Els8Mrrs8Mp&q#CBms6K(:j5Tt9J,~> +&H)S+rr;i(VTf)7s82fns8W)ts8V?_s8VrprVm9)rVc`lVl$;fX5`sQs8W)urSdbUrD`gprqU8U +Q]RT!P#>19PED5uNJ'^Ejs8DuriQ&mUf)GUHrVc`orsml%_ns3/Kr]-J^qRq1`W!^n_o9U3`PKR3\5gDa`OUCuT=VJK +]t_M+^r=:2rkC+j=D">2ZX007a2#X7]>_k-_nX.(aMYj09VeR\[Dp&#bItg0p&G'jrr2ilk4&EL +rqQKnqYpLXrr;rsrVZJl?':M?Pa.Gk10m53P*2#pIm%L[;+R%9Qs +N)9EOq>^Emrr;?a!<)os"9/8rr;Q^+rr;uqs7jL>Z)t1.PO&#Crso&,r;Q]qs8N#rrVuflrVZ]q +r;ZfsrVZ]qrZ(n9s8W&rrr;usrVuoqqY^9g[A9Ch])2O,r;6JD~> +&GZ/"s8VD6=bFK%aNW&QaN"5#s2P)[q9&`YrQ>)_rlPSh`l?*Bbg"AUaN"4u!65#Z$Gg7X>@(j3 +]>r(8rl"u\bfn5haT'4ra2?'G:N)DYS!T43:,J2uR?rqf3c7,TPa@Dq:M"BB?B:56OdM.fCcI33 +S!')"L+^`nbJ1p=`66ND^r4=4`PoX:bf\&Jc-jP=]Xk`+bfIlF`Q?9Mgsk3OmGE>IX`pDoj6l:) +qYU-aoCGiMp@7hB_BWr.A8":mUuC\6nFlYPmHsB:o'>#88PFGKIS[*,qXO4Nkk=lFoBc2Js6ngh +;6I61ali'fs6J[ilIX.saN2TP]thh4`lQ0>`W!kK`P]O1^;S(4`Q5s<`Q$)\98f$uOHl)p3F4t: +PEV5tJ3I^_;G!7=QX;IbD` +aN_oG_6nDf9L_f`;R=3X`59L9bKS,K`lQ +!W;inrr_BBrVHHl!;tjUrr36$qu$Hnrr)Zkrt#,/e+*.tg].Bm3WRQC=&+Q>Aoq:Q_04R$EtdC%qB%rr2rto_o0prVGBi`5Bpo +r;Q^HqY:'ho=fm(H@nATQ8jqSF`D;;EclPJEcH5BI"$s5?Iul97;[d0IXucfrHS?f/UD>\I!gcf +6<0FtB9IggG]n4NF*;hSG]mtKG^O(8Vg(BeA9N69Iu)_JrVuiqrs%]To)AUbs7-(Kq>^?b8s#L1 +R$F#'3+"b1Q'@W#P +&-)V-rV,WTUu_4Ks8Dors8N#qs7Hhk*_3eD+P';/_^:_D#_Sa:.^qde*^W==-OD:MIC50==`4Nt%q#:3k +qYg +&-)S+qt8icbQ>r.aSs3ZaSa1$aMu6Dc-=JR`Q,pA^[gc-"&D_oD\uilFL,`Pfa@aN@5$M3jU'P#YC:I$U%_P`f]QECN`kQ'H;:Aoq!FPF%Dt +PA*9e9TGO*Q^!VQ;:VDgn+$&Do^hV?mHXH5hQAYe@l$!%e)]`^rq$urmHa**p$D>bf]:n"Nng,`lA#! +'?\D;`O_O>:K("^:PQX+aN2?A`V[^Zb08#NaSs0]aNDTFr6#&\r5ScXs2P)["3S^+`r='Zb5]Nj +^4]K_:hI]3qu-QpJ,~> +!VuTkrsJ_cjo>;Xqu-Qpr8R_Trs/Gsr;Zfrr;$9j&G=`Ip#b<7rqcZprr2rtrqtaS48o3ZrVuk1 +H^U+cO-s-?S@b)R?a))O\s:#q>UBns7H9tr;HKM +`Pf[AnGi=`s!dmGp%$(RI=aD;SP4?hLiRM&&FFA\%eai_>$3'u&kMu@9s8Vlar\jfL +s.WqeP*2&qQ$c"rUELrr2p* +iTT\&p$LQ7s7cEgrr:IHs8N2[jlG=srrE%K~> +&c_k0r:o'AVssT`rql]ps8N#ps7uZorVulps8)`prVum:rr)irs8MupqYL0HR\Qg^anl&9r;HZq +rr)irir24Crr;uprqpARR?X)"QX*-_@$ln@P*D8'>%hJ.PE))i5@eS3@#L;4Pa+pK/s_E_Q'R\s +:Wre7s8W)srVlfrrr2iq%0$2(qr#N6`nL4fp@eIb.JE`$_8NidRA4#S]u\+1a2c*7_Sa=3`4s:* +\9<%%@[agG^W+@._8=(g_uIRQ_\9u*_8aTUFC[s*]Y_b&_8=.._SH/f&\c?#`O8d2WEsPY^;%h3 +a7fK2rri)npZ:Z]nQ@24u<0iu9Q]jmHCJJ/uPEpDM +CN3$ZQ^F&%P)r4H94^Hns8UsT&H2Il\[&<=WMZN^rr<#rrVu'[qu?6d$iA\U +U7nC>q>U?krdX~> +s8No6q!GUo<3XHiccF2J`lQ6@`l5j7`lQ=%bl5cbblZ&/`rimZ!Q'Y'HBkBGkO.2(k@VSnL +M3F!`P=f(2P_k]kQBmFoXO4l%_oU'HbfIfFb/hU%`?3+@b1,+aZF@6TdFc@N`6?ENbhLq2nC2$9 +Tkh$sp?qqKD=R5hnaGl0kkXZ#Jmpc*KY-+*q>0UQo^hP;lKdj/o^hV?n*d-!@SB;Cli6hYq"+%I +o^h_Ko(_M^Xe$8Ng#Y_ +P`h,N5tt@GRuint6saS$>EFu3FYd`M9P'QXQ'7Am87kM'OH5fmQ?u!Eaiq`K`l5p:rlX3@&&uJc +@T$*$8PWa,aNVfH`obAA`WF-!`q%1Y^R&,$;I6Hgr;HZqJ,~> +!W2forsJ/Oli7"_q>C9mrSmhUs8Vm!r;Zfrqtg9irt*uBo(;,>s8;`ns8N#ts8MrTr\sfTs82ir +FENk1Q'.MO9klJCSs>S*PE\7JDEUdfOID@nCi:nkSX>h,O??6:S!KA(Q^DEDo`+F[$2jbui5NXP +c2[eBs!da8qYKs"H$b':Rts^mIW]^KEcubVH?aLNGBJ(QHiPES>(D*NFEMeMrHA0_3-KLlI!BUC +0-MoQK33A9E-$/EF*2_PG'8([E-c\WDFg7Q/pVS_F`W2apAP$fs8N&am.^/Ls8VWg2Z3RL=GWN2 +RZX,'85_Up\t3err:dQrr3Gnjm2^9 +o$7:8qY^l/_U;rrE%K~> +s8NN,r;4X2ZMOV$rVZ]qrr2fpp&=mhq#:^D6F-ViPP*ToVB/5@7PEV/lP!!Mr4&)9+R$'FEDCh'CQ^Ehp4%rSAQ'RW!QXu*JrVuiq +!<;urrr4/@r;QWP`l,jGs8N&sqYgO.5&c1F^VRk* +_o':h_>qLQ`"^,*_8F40a-VU;WPOh8]u%e,`5]dp_]?M5]ue7/RUW:e=gbiWa1f4\s8;oks8)c\ +o)JI^rVm!!rVc`os$?Y_rVlisrVlUnL7+-jOe%\-;a$G.NLl2e3,W=tQBRDd6#U[3DO-ELP*_\Y +4)7T(P`qB!PB_*Brr;`mrr2rthYn)bqtA(9WirJ(POJMNs8DlqjSf/ZnGWgnm])nRUriT8rVcbH~> +rr3`.m%dcm\]W4@b/q]CaN2B@`Poj:aSO'ZbQ>r.`r +!W;rrrsIZ?p&G'iqYpNor8R_Ts8Vm!rr<#tqtg9irt*H +s8N?'r;*pu_YF*/rrL=(r8bo27A6\XVnKgba2Z!4rkB5b_o9@1`6#!;Kn?)>`lQ-7]u7e* +_SlGm'Z@l*^:qaYM2@.la26!C^raO5`lA"r?Gj^o`PKR8]3C>fBpHBJ`l#des8Vurq>0sEo_\[g +rVQWprr)fprr<#trr)fps8W&tmmNM,OHu>pKK;)DOdD8tQ9o(p4cktnLam.W?Ug6FQCF%tPX];1 +8!0+)rf[\&NVibQrr;uss8UsT&,lA%fsRrbXfIiErVlisrR(TPr:dFfU7esns8N#tJ,~> +rr32tk)EYf\]4Wm"Ne[$`lS(t!6G)\rlG,^!Q`:[`=Ku0b08)PaN2B?`lu]Pbf9Y$'$8>;`kQ+l +>?kd>^WXsG`l#^8rlX6A5/tX\aj&#OG$g5oTqIWs2JZ5uMOBWoR[&dcG]["dO-YlV4_n;'M3tN`Q63ErlG&\!6G/ZHcaXc`jECg[aN%4`kp!Ia3N5`e'qF"ZsUZ+kO\B; +n*ff:oCMMBnFlAAcEaRQcLUAhlg+*=p@RtFmHa0.oCqtPrPl%DZde@(me-&DlfdEjlKdd'p$VD; +oC:USQ(W6%lLaGud-TT5_Tg0EbHAe)b/q]C`Q$'Fbo4gI`Q$!Cc-=DN`Q-KO/W5gVR@8q\1c'WE +R/W="5[e7dNg,Z\-tmF&7$Eq&S!&er7V?s9Od;AuPE]Emcd'MPbJqN?`lcH]a:?2&MHa"U:JjoZ +`lcEFa7.1Ib36nP_6mlM;-8D3qu6VG~> +&,cG+s8UX&r;Zfrr;?QnrSmhVrs&Juq>^Kor;QR%r;Q`rcL(,fmJm4_rVlfurr<#Ur\slUr;Z`i +]NLM`R$*_u5&tL$N0]QiQ][G4<-!"L=co#7G"$=2KpS-lQ]a=78O9dJ*/0s8<0!i5NXP +ht-jICB"2-rV,]oMd^FqD,6%AF_u,?H@LEmH?jaXG%tkc$jm7R5&>(RFEr+VF`V\JF)uABEHQ5; +1(j]p$or!?F)Q8HH?j[VG'.kLH\-H^C/Z!!0WpftJTGj_OjNUqs7ZBjs6/_?s8MrrnbtQKo>Odp +Q(!r"Rm9SIPa[`&NG)((4+`rdLcBKs +s8E9%qsgY]d.[D9rWE3"s8VflpAb$h!rr9!rr<#srVmK/s8W#\R&Hm^^&.g-s8Doqs5*bTs#9rS +s82N"=-\u:P*M1kDJV:iP`ColO-!@>E,\6IO-PoO6uG&=QBmf%OZ-Q9PaIZ*OH`Gcq#:9mpAY*l +3r]0Xi5W[PhspaFrVlisrr<#\]SCR#H>U@9`l#d4_8!_!^qmn*a2#p,Ll[X\W6)fd_oTd8_Sj@1 +rP^\2_S=*ZN09TnZ,ac%^r=.,_o9U4_o0I-_Rm_.^Q!h +&cM_/p"4RuLUY7Fbfn5L`5]gpaSs?]a9'B#`W!mV`W*pXb5TU0b/hTA`Q$'Ebf[rE`Q$$B`l5/e +@pN>Z[(j]-b/VE>`lQlkO]GD#ECO&lPEBoQCcI$2 +P`q>m2-WM4PEDN#O$0eoaMYp7aN2NFaNDZLbf\#H`Pp*P`jWUl_oU6FaNDuV_T1N\^m'8KMhTsQ +nac;@n*ff:oCFU)oDJ4TgT7p%alNKjjm)C0q=aFMn*TN2nacJMrm/3f_T:j3mI'<3mcrrskjAB> +Dsm2]o(qa\TNnt"M`m;f(6&CbqQ]mh" +78oA;OI1o:956G9R$!([EGn`7PDc$!Mk-,*=DL3"Q'7K!Q]jn-cHO;QaMu3=aNDZH`o4uL`P8'T + +s8N6$s8U[%rVm$!qtpEniVjYnrqZBis8Mroqtp6gs8:U@mHFYF +PEM#mR$MeYCMQ1oR[oS.R[./1F_WsfPadb&A8!raR$=&&GWS$OP+.Z'Q"ZBXnG`Id2Z)FO`5U(& +rr;ooqtg9gkJE66V5c51JTYsJEd2t]I=-p@rVuirs7-(Kr;Le_Pa@l+ +P(YhRMisWnO+p);;Il8YP$V.1EE7g[SWT"uQZb`:D)RN@R?<_uQpp-5qu?Qls8UmRrVm)algOT? +l-oY3rr(:Es8N5nkORutrVllsJ,~> +s8NK*r8aZOi;*BPs8Drqs7lTds82fprrE&ts8W&orsnqNR&Hscc2@S\m#Bg.EARu`l&PZ]PYBK1I)RZBgGB/bm1*irr4A+[8!Hq7_HFM`PKI/^q[Y"_8=(,`lYNDRZU0WkPtS[rVlisp&6WE +rr)h:G*/&XQ'-e]2iNohO,eo^BMi9.O-*^IF)Y-5Q(=&!Pa?P\Ec!^sPaR[5PR0_=r;6NkrVlfr +rVcfsiVjAfqt7b/WNNA"U%nWdr;Zf]rr<#arX/MNU7IaJlMpn`s*t~> +&cMb0oYPNXR_-S^c-+5La2Z-uaSa0caMu6=_o'I5rP]#]aND`Nb0%a'`?*%Abfn5L`Q#p?ai;,] +>\%,KK=8M7cH=5I`Q$!Ab2LEk`Q?*E`mV[ZLQ7[kSaNDZH`o4uMb.t3! +<_?.b;HUWRa2Z +s8N6$s8Ug)r;Qlsqu6WSrY#81q>:3lrqu`kqtL*ikNVO&kPkJ^r;Q]qrr2rtiVkk7s8Duqs7K$L +R?Q'Rf'8MkjZPa%N#N)Td#=*L0J@=<`QE.;nVH?sj]G'.tLCJ5i25u!Q5PaInD:g-SZR?W1^EGIFtR?a/#Ss5:,?>rAINh);qS!8t0q>C*hqu6WMrr2p%nEBH6 +o^^H0rr`5tqY\hA$ig2(jn&$(oDejirdX~> +%K?D,r77aJo)/Les8N#rs7lTds82fprrE&ts8W#nrt>.2SYiBmi;9qu6Kms8DuRDMO^!L:P:@^sBa7_86,frkfDdZ&4g.R#m9!`PT=*b/_97 +`5KRm_E,TTbFaiTP*_2fZFe/m]"c5!^r+(+_8*n)^W4:K^AVR$2tZE,%4pR$rAINh);qS!8h* +q>U6jqu?Zps8Doss5Eqds82]>Z)t+0X.&JTs8;lrmJct^mJ[=fjeA04UY5VGJ,~> +'`7k/nZ6PEZ+n5ubKS)J`PojEk)3Q'%5u5?`2@3g>\dPqR;B +G`e/QQ'4ZlHB4/QR[8dpe'H%M^W"4:b/hTFbg"AQ`PTL6dDWQ2^:`%IfZhabbeh3Sb@H@$I\GTe +o^i+OoBY`2nalABo_7IX^:(bf]@6WKlgFH>n,MqVmh,*^q>ARS^;J"/_s$CTkN240rpqB,q"XRQ +mHaWD]YqIhVHp@*:sA(Xdc]Z?ce") +s8N6!s7tO%r;Qluqtp?NrY#81qtpEnrqu`kr:^-ig[t%$kl1SarVlisrVlisiVkk8qYgHmrqToW +OdM5nR['.;EcFNfQ'[c$R?B!"U)%>Fp11KnP?9nb;lt@RrINj7WHNs8DKe3Vf"UPa7c$ +R#s9dL6\!fQ'*t@16RocPr`]"3K9)eQ'@PtOA9&">!S19PEDDuQ%`]Bs8Diprr<#Prrs8NAdo_%D4s8W)tJ,~> +s8N&q#0EdJr;6Ejrr<#ts8Voop&Fmfrr2utrr<#rr;RK1s7MeQVQ@>urVuiqrVc`qrr2lpiVkh: +rVlioqt=qQ]IA5:3'T/OdqST6t\QCS!9/&F?E-[Q'7JuO\Nsrr;Zfr +s8W#rs8O/>s8Lus_nh":nb2bXs8N&uqo2L5Th;u<]YqY$_86)frko>e[Z$3+QC*MkVS^0g`kfIh +`&trS_8=.0TotS+PDYEW[)BYp_83k#_8F1,^qde(`6=1%O,]3RDeu,.CqRNqq>U9jEFh[!8`ogH +qYgFR$!_t2E?PYP`h5m1Mee.P`_1jC2Z(jNgGupQ'7;)>] +'Dq_)m$VL-^V7S)bg+GO_SsO9b5KNdb/hTBpr*ssaBODmNt300Jj +P*9oI8!&guR[TC*YLU_3^Ve(8bf7ZFrldCD`PTL6dDE?.^iW2ue^E!m`P'IQ\43] +rVlrdgZeY9"8r∨$?lr=&W&s8Dipr;Z`qrVlijrXeu)s8W)rqu-6cs8UdJoB#TCs8W&ts8W'# +r;Q`riVmoqrr)`os7_=fQB@N!PF7SE3cT1'J=;alP*_Gu>#fQ1FH`#U?;`e:Q'7`'O#f"\NgZ8u +P^mB>s8Vupqu?Qgs8;oos8W)us7cQL_SIa,G)&?Up&G!crqU&aThBHWG^+LZG^+CTG]dkBE+VcX +$3^J0!"oW$CN=W@F`qnLFE_qVG\_.J'E%oX!=0&tD0C,HF`hkKEHHGMIX#s4'`\=9"9o8[,E/<` +H\n!4p\'VKE-#u".Q[8#qYpNprV?KlqZ$QprVlcps7uO^PaR`!R?EUe2342mR$;>V1-hs8Dutg\q3Nrr32[oC2GIi8O\8s7lZmo)91"rVuls +s8Drsr;QQmrVuogrr;m&s60+Lki;^Crr7K~> +%0$2)qm`BDq#13jrr2iqrr2rr!<2ur$i^2*qu?Torr;uts82fors/Q'rr)fqs8Mus#k*qMWN48; +rVuips8E-#rr)fRr\slVr;Q]gLM`<=QB[T#O^g@hE'&N9PF.GsPEK$HDaST/Q'>QO3.mHYS!8pn +0U@`eQ]RA[B_VK*#Q4W!q>^Bmqu$JtNfB"&`l,a3_8=+-`Q#j2_njU2Q&:cfOc>6O +:n"OiV7XS3s7VOXEcc+s-opkoq>UEo7K3/`rqcZos8N&ts8Vlj7?X%$OdM8l1H(>_QBm\J7o`]Q +PF,QMCJ&E(R?Ei&Nfct +-iNr@kCd3%]tqV+aN_rK_o9X:b08,TbfRoD`6H9?bf7fEai29BaSsC/b/hTA_o9^>bf\#H`Pp!@ +aM4iV@:E]$[_Kr)ai_cIaMulM'P_]u@e%]Xk1WUSV&+f#H\4g3Q8"Ame%F,h(,^b/q]C_9Bm:aiMWD`l5s>b2,%2PE;$" +QB?+?k:.]ZD_TU$Cn]26_a2u?AaMl0>`5][;`Q60la:$#/[U;j-?&[b;s8RT~> +rVlrXh"9/2qr;$?krs\hurr)fps7ZKhr;HNerWN/us8W)tr!<#ps8(RIn)F$>rrE&ts8W)u +r;ZfUrr;orrrEgW/ZPb+2'3GWUeR$UEgs7kG!LOk51LO;nqrr3o6_IK['4c"E,G^4RZG'.kDFa.M49G7X3 +!Wi9#"9pbsEI%P+$?U<6FEhtOD)`.0r;\SR"pd8'EcQ5AF)ZA@F)cGSE`3eX"9AKbi"0q4%c +O.4nkDG>(fEGp,>8QkogrqZQn7K*/aq#:9mrVlfns0H*rQBdc&Q'!LpQ'IK#Ou[)5NguAo3-8;% +NL-#qRZ`t.@r?*ENKo]mQB[Z#PZkHkr;Q]qrVuoPrW)iprs%]Sn+HD;g&D$Oq>gHarWrQ'rVuio +s7uTlrr`2ns8DNf$iB\unauJ7i;`iVs*t~> +%/p2'po1(;q>UBlrVl`ps8N#r!<2ut%/oo!rVc`qpAb$grVQNlrVm$"s8Muqqu7?-fp&5/XOd7G +s8DurrVlfrs8MuUrque2r;3QuQ'IQ!Pa%Gu>#8pI7rXcYQ'.Z%O#Lcl/XMZcQ7uZ?NL5]mOub=N +RZN\sQAnVsq"Xjgqu$Kor;Zfor;-Hnq#C3I.>E8&I#*5Gq>UBnr4#h+P@2,A`507)_8F41^rFO4 +bIEN1N_P\$P`q3.`l#^2_ns7*`kK=)`Q+[5OHGipPE(W^[)^/(`59F+ai)98_TBd.P)PWhNf'*a +Lj)drQ<^i6p[r>tEGof2FD= +'E7t*iG`(f^r+.1aNVlJ_o9X:b5TX(bfRrG_nF.7bKA5Ja25d8`QHEKb/hTA`Q$!uaqDe8`Q60B +a1[d8?!h-+]>DY.ao9EcaMu6@b2:9Y`Q$-Je;d._PEhW'P*;29;/0i:EL2rVPF[u#3,3=]Q'%5o +/mV,.QA_&j3%olNP*_Z-M*Jr'_844/aNMiFa32HA_8XI0daQ,RJp_rgJo`p&bJhWPY$%QcFPuBO +o_A4Rn*B9$oD8%Uk.AUPrkVUM_SP.-p%%P@p@[kElgaQ@qpE$c\\5o!`5T^YoBki3oChYJn+#f6 +nEo>R]Z.n)\&6.lQX?GLEm2JdhGlgTC2@L$ANN**d*9VTaN;B( +-W6(iNgZ"f@Ps(3P);,:<`"[ +rVm8[h>@6Orr<#tr;6EfrXAZ%s8W&ts7lWcs8W#rs7cNjrt#,,qtL*ijl?(%ir8uVrr2lr"TJ;r +s8V'W!WDins'Yd)g..EqOI2;rRZWk#C3=,RPEVB#N0Tlh3cJ).O-l/]3AH;ZQCO.I085pVR$Wr' +C5;Q`q#(0es7ZHjp](9mr;-Hhni:l@LkL\5Lk>C#s7c4lVl)\'H?aUYH$FK2EXHr5D/DiT"onW( +"TAB5">s_0H?XOREcuPREcPkO'*S14('+OsAoVg3FEVePD/Xc>BOVta"98E)rW!lI.PuL@PF@Js;d!`nOIM]' +P*_M$A8Q +s8EE'n"?T=r;ZfqrVlfps8W)urVuj*qu6WqrVuolrq$0irVliprr)isrr2lrrr2p,rr)])RA?ab +kPtJ[rVulq!ri6"hu3ZVr;Q_>qZ#5;PaIQ!P`V;oPkR>3b?<+1fRPC>JFL7=Ht +P$fnrS;ru&OdKm^Bks7cQfs8Mfns8Mrms8)=HG'esoMLUM4nbiF_o1bUK;67RN_na((_o9X6 +a2Pd9[#'m,OcGNdP*;!-aMYnd_8=+,`50O/_S`"(MiX-gP*(fcO1!'K`Po^2^W=:4cG@02Q&1W` +MjBZlNJiR1PGDcM[Ij/O89/lnD/aQ7>tG"irr;utpAY*lr]C/Zs8DccAUou.QC!i%N\s1cOdqPG +5W"XlN0R:CAiPj?R?Nc!QU,/!EAr??QB[Z#Pa.Q"7Gn(DrVuZl!<;'Xrr3B'pX?IPY-+jpn+ln\ +s7?6hrsAT&r;HZqr;ZKfrr2upo)A[h$2qf7R[C&Arr;tJ~> +-ia)7dUZX[`5]g9`luZH`Poj_j +]QR1W?YQ=P_oB[:b08)PaN!,YFN_YC`luTV\i,V8OI_f%Q]RFrBlmoNP*2/tMj0Zd3GhZ$N0TNQ +2D0ZNPF7M=/;'IQR[]M,?ua]F_84=2beD'7^WFaA`l?KUdj&UOLOY,1LND(kb/_q$T;l/:jQl:, +p@e.Ilg!s4p&F6c^;e73_8-r'_SP.+oC22>q"O@Hq=+.Re%N9'^;%Pa`-95bp$M&3o(25`lZ]Tb0@fBaN;N>n]2'U^4BB^;JX2 +rVm&Xh>dNPr;Q]ur;6BhqYpNp%Jou$pAFsjr;ZcorVccjrr<#urr2p,qtg?mf';kokl:\]rr2lr +!ri)orr;$XE;]_0s8N&ps7JgAR?Eo%P+%o+P>#.sBL%iGR\#q.PZ_"%0U%`bQ!I$XN09Nj:-Fi* +QC=#!Q^C#0p\=dfr;Z`qrr;oirr<#qj?@^9LP(A4IXPumh"UTAHDkseI!9aYH$FIQEH-&@EH+V]% +KZP/(Bad>!!Y>oEdDtTF`VYKFEqLQ$jQk:"T/66#!-C;EclSOG^FFJDeNW#":5&/#lXfF"ooKU/ +"r4iVV&dSFDQ#:HuX%AF_ASCGGaXDSPa7W"Q'IItp%nXbs8;lrh#7l_qY^?ms5o^C6.s8MupnbrRdrVlg(q +tg0fs8W#sr:g6^rX8Z"s8V +#6+T!ihWLErr3*!r;HWns8W)urVulsrsel'rq6-frql`pr;Q]qqu6Tp(B4:2rr;uss8N&spS4CY +X1@p'rr2lqrquots8V$Vs8P=]s8W)nrpiC9R$*c"OI;Q$O\/_kAj2E?R%0M&P#kUt09VN^P[$gT +Mij__*^qmn+ +rl,,[^r]\4s&r8\aW15&kCZCisK+CiEoDq"Xmbrr;lqrr)iqs8P1Ys1V6nP`qDuPE_>b8!TR+ +O??$IQ'[_a4)7!4R$EktT8lNJCihmdR>I5oQBmc#Q'+B)r;Z`qr;Z`mrW)uXrr2p*r:oEZYHG(3 +QJ;66rr;Th!rr2trr3?%q>'sgs8;oqq#Bpas8NE(d@d##U>c(MrI=~> +.0'/4_.6iZ`lQ6?`QHEE`PojaN4A's2b2Z*6?:FaMu6=aiDNB +]4"cC@<8ir`Q6!G$=AifaOHGWf +>"D;CO-G\u3g>hoR['&%Ot>2.`5op:ai2NDai(p9dF-I]-=h=PJqel2J7'P,bL28_W/6Dem-F-4 +p@e+HmI'N>s6@25`5g!<_nc8c-/&%`nF#i?p@@kJs8('N^;.S'^qRY(`5Up2mHX*.n`fH+me?Hj +pqOPd]YDM%_SjX)XjU:VX]2slZnQ<6G]S7PEcGhm)qNHea2Q9=`l6!;`5]j?et56jP*D9!Q'@J` +7?a.#OZc-GPEh;Y3GCR,QBRGlSW$0FCihmdR>I5oQBmc#Q&RQPc,[Z>aNVg+a90Z/aN!2[&BW,= +^7AD%8kMuUS\rLf`Q%Mj&B;T*aj&#ObKJ8`dE9VM^Wc5h$HKoB=Ai.+Z0V>fJ,~> +$3'u)r86E3rqZHjrr`2rqu$1-`q#CBkrV?Err;Q`rr;?Qo%/Tr&s4$K)lgOlQ +qu-No"TJH#qtpBmj8NHarV?Kls8Dro;2CR)Pa.N$P`V8q20EYJ4IMA!P+mr(4Dn2*O-uGr1cL;Z +QBcQWR?6#CdhG^4[iFa%\0B+k:G$31&1 +r;^+0&mS7NT5%ok96>/pEc5`>F`_tLC,l(grVuoqrr)iqqZ$Tms*ARNPa7T!P`hB!P*2&pE^;mN +QB@V3>[U*gQ'dW%N_EVsBg%Q>S!B;)QBmc#PDr3.qu$Hnqu6WNrW<#or;Q^#hsU40mb[I4rrE#f +rX\u,s7lQmqu?BKo_eahq#0d_#Q4K!s5`G3i;N\+~> +#64Preu#VErr3&ur;HQns8E#srr*K+rVufor6=s1s7?$cs8;lrqYh--rqu]os8DrsrVuiaRAZsa +`Vf`8#QF]#rr)irs5X(rr2fmpeHX2QBRPsQBI8oO>CrkB0)B@Q&qYsQTK%a>`P2>N&*AK +Q^*eVPF%>uPEM>rQ?.EKs7lWor;HHes7lWop\=;IJ;9#7K6iB/J:M3]qXd%MThDc2rkncQ!Q2kT +`GiW1Zu^hYNK]QfO,oEdO/U(8_SO+-_o0@0`1;1(M2m[YN/Nd_P)Z`d`59F,^r3h+`k9KDG^bg9 +OHPN`PE2#i[sED!F*?h=C2Is1D.Rg0EHQ)548JdOs8W&srVlfns8VrqG\`\2QBd]#PaRpKsCoDARfq=4IarsAY\Un+3QfDY^IJ,~> +.0'&+Z"%7Q`QH?B`QH?E`l5s=b0A/QaMGd<`m*&8e^E0j`luWD`Q%\os2l5#`l?!?`lZ3+='8O1 +ID3eFai23@bg"AO`o"j_bJqK=bKA&Z4b/ZZR@9G.PE2&m1j!GF4.).rOeI`$3GVPsN0]ff0f4ZN +PEKpMRZa#"OI;5nB4K?<^WF[=ahtp<^Wb$Ie^Z`l4KO/3?eLd`'@*n-&lg=<9o^hP? +o()hR[DBetaMu39^V.4fddmD*Ie!(&p%/(ZfXJK1`5TX-]Y_V)_;k+PlL"*2nDj'0p&Dn?^Ws^4 +`P'+)^;I>/P)Q\kG2kYlC34W&n=?OH>rsQBdc%%$[$@LcO,5`P]X7bf]q+"3ep-`o4uMa32cGX'\Rl +9N+i=_T]s8`q.7_`l,j;`6?f\f$:eGc-FSL`ULqT^Rne0 +$2ji'p>#$7s7lEhrr`2rqu$rH9QI-P*M/t=@-)P +Q^!o"QBmc"QC!huP=5*@s7uTms8=#=o`+YhL5C_4J;8l0Jqo/84k>.0VeKI`Is$!ZG'3b)"F+`e% +1E(6'*SI +Rt5!/tSq%F'Eg1G\hVRE,fltQ'R`#Q'7))o)AOdrqcWoh#7BQqYU9l$K^[Jn*TGss8W)snbs'tr +;ZcrpTn`g0V8.;rr<#hrWrH!s8V6CkiDR=s*t~> +#64\qbbMiErr3&ur;HQnr;Q`r%JKi$s7%T^89+Y/s82cos82d.s8Mons8W)ss8N&qgmP1BXO@%F +s8W$%rr)irs8MuVr\4BOs8W)urr)RpK9VddQ'ISsSsGS.6p_SUEoo)9$srqu/aSY)Y"r;QVE~> +.KB8'U0qrI`luZG`QH?E`l5s=b08)P`lbm4b1=iJ"Xcsnf$)+Nb/VHsaT'C#aMu3=aiDQBU/tJs +?AuV=bKRuEb08,S`l6iVs2eZd_TL-Id7J\GQ^=81Q'7i,PaXsCG%!h&P*^uoO`rm-0TVBWQ;p.K +O-GcmO-uE!OHPlpO,GaBccj2GaO8>V`Pp0@g=AT7JUr#nL5:J5KS4b,`,'4cJtUiclKdj/rq-6\ +>P\'f[_oko`Q#m:_S>kRZ2>Q<^i.6Od;2jGXH7j0NX^-P*VAuQBmc#PD)BQcc!f?`m2`JaND`L`l6oX&BDr=`NsAT +7nloaB#2a/^rGue&B;Z+air>]NCjH`G0e@UbfB7p$H9iaB2_]/O5p'9J,~> +$2ac&kM>q5s7cC*g&-)P,r;#"g+s8Q;o_SUfqYC*jr;Q^.qt^9lrql]pqr-i)kl:\V +q#13l"TJH#q>:0kj8K/Yqu6Qms&o,'K9_ddQ^*i%PEV/mRo<"#?p^9KR@9M5;,0euPa[bp4ue^m +PEVK%R?Nu(Od;H#9%C"PG(G:&N/31:KS,,?M0D9WYFRe5H?jXVrc8R_5A!!33+"9&9/"#OV1H?a[YF)>_M$iBu+!sJi+!&G*RDKTu?FEi"XEFfjh#64r.!!rZ+!!N?-% +:=Hl@%DL-FEr+OE,ff:G'%kEF`;Rcs7cNm5Pb3 +KonpR?a1l4_]'nH'+PXQ'7JuQBd]#P`/N4s8)cqr;Q`Orr;m+qYpNpo'c5^KiWZ<<*GPqF>s8)9b#Q4W%kiCphli-p7~> +#64\p];3OBrr3*!r;HWns8;lrrt,,.r:eb]*?6X-oD8Lequ?]nrY#81qtpEnrVZZos7XC"WNNGb +r;Q`rrr;p#r;Q`rrr(jUAcMc0rr;urs7TZLQ'd]"Q'ISrOcYZm;+bb55aRe'R$O4<9hT,_R?o +1-q(hPE@h:30K\lR$%c`jLR +Ybmo_rVccroD\ah%Jg#'oqrZL)KbiRs8VrcrX/W$oWt3^Tt(%"qg\~> +%fcC^NaI1Aa3;cH_oU!Arl,hsb08#L`Q#^7aNh0s#65([a2lHE^W"46aSj9]aT'BmaMu3=aN;T? +N*'(Z?C\h1bQ#Nbb0A8V`l6iV@`cg1`5p0JcUrPDPF%l-Q'IMqOcc)195n6(R$X2.Q^LTA8S+$N +O,?"VO-PfgR[Tb/P*1flRZ9J;bf%N=`llKHaNVWJdNi@KJ;B2?KRe]&JVf/)4?TO8@,gM9jQc($ +rq(1$o^_IY`kT=3_8=%)^qmt._Vb7`nEfK1oCMq0]"Pi"^rFF8`P]R-]Y*.nnalJMq!dnGoXVQY +`lPI(cc*Z5a25["l/A:TTS5sL@r?4$CLh1-I!KXNB4Mq:aNMlQa3;]HbKnXq99b^.QBdYt0Tqfb +N1-#m1I@1fOcMD22NX8dQBIP`3bEOhH'+PXQ'7JuQBd]#P_VKRd)3i@aNa\+"3ep-`Sno +$2FQ#f%p0%rqQ9frr`8tqu$AW +qY^Bnrr2rsqtpBmir3B`s8;oqqu?FoLR+3hR$3i(PaRl)P*SLFF%(,4R?EerPsT+IJs;UiC,(7) +Q'@T!Q^EH>Duqu$Bkrr)irrVl_BG*82YQ'IYsQB7?"Od'Z4O-u)rF$ri2 +P`qK"QBOaGF>Aco`+pds7-*frrhBEm- +#6"PpVP_N5rr<#s!W;rrs82d's8Durs89#C*[5"err2uprr;lp'*%t.r;ZfrrVccrnZnX]YI*j! +rr;uss8<*"s8W)si;R*`rVucjs7TNIQBdW"PEqPuQ^*htPX8bt6ZA-ROHPZh6"W92PEqSM0Ro@P +PE_2pQ][N"P*;)TFSu19s8Muss82Wko0%STK7\c#KS,#0Lk'r+HUa*n>d1oVaMPjo`,NW2_S`O# +G'8:^G^P.$NK93cP`Mf`_o'=.`Poi]H[1!fH@C0iLQ7IYPEV!1`l#[3_8=%.^jL0QG("OdGC5@2 +P)YQbN5EEH:S=a\C2S'1E--,>Df'E7DKKl&B))9$rr2lqs8DomGB/h2Q^*glQBIVuQC +.KB+qG%6-8a3DiG_p$?E`Q$!Abfn5N`PKO2c..pc$O8uGe'Q7Qa2Z-tb5KE]ar/4<`Q$$Bb.;k" +:f_53_TU'D`Pp!Ec-48KhoKl\c,7TCaNr*tH]F;WT9Y_0PF.Z%Od/:BE^Xo0R$!SlP!ZhE'[3&OcYikPE8+;EAEH9Q]d\uR$O#'Pa%N#LGRH,_SsO;bK@p*ao]Z(`o4uMb/hfK +\r9W[934qc]>_P$`q.7_`k]^>e]udI#mC`%d*p4Vc1/j^a2,B0=AMV#hY7&#~> +%K$2*g"ZQ,qYC$gs8MrhrXAi(q4K#X,*Mg$s8W#lrW2rrrr;rrrr3Yin*fK$s8W)urr;rrr;Q]n +rr<#Vrr;pXrr<#rqHecDPF%W$Q^3f"P*_K!O=+dS30TMnP*hAd3)EY>PEh1b9p2$-Q'@T"R$Eu+ +P*h,/o`+mhs"!pFs8DUZKSP84K8+u4K7Sf3KSP>9K1^/h]SCQPH$Xa]G&_D2;@j3@!Wi3!#mCP8 +!!!0WCM\4oFouD.&cqh/rrE*%rW!?65B(dmF`__CAeP4Lq#Fk)!sAT.&QO%+Gt"Bs9Qb8oEH-&D +CgD%*DK']=7)JWsrVc`qr;Z]g>DSK/Q^3c)O-c/qOID=j7$EjtPWEJ2P`qH#QB68UE`?#%R? +s8N5gV52`@rr<#ts8Drsqu6Qo&,Z*k+<2D^p\b'krVulrrVlfps8W#trr2p.r;Z]iTV&']_Y=*. +rVccorVcp!rr)fRrr;pXrr;uppg&EPEh1b9p2$- +Q'7JuR?j2,OHbQ%o`+pis"!pFrr2RYK7ec(JV8N+JUrT-IY!-(KM6T$f>GA@_ns.)`5BR/Z?CJ< +H[Ga>hLV>c!iZEH$#?E,T`+L?arr +&cM1?F(CKKa3DcA_9p`O_oBjubS\LD`P]U9aj>MH$jUe%d*BbN`lQb08)Pb/h`Ji5kr=BZeTFbKiIoPa%`0Q^=#$Q'.K"P`R8-AN5m:R?Er"L,`"1 +PEV8m1JO%&O-c9'PEV#hR?<\\5L@fs_o0^8cHt(g-uO6WI"[?2M2$Y9U`PTd@aNDZM6@=l^Q^3c)O-Q#o +OID=j7$EjtPWEJ2P`qH#QB68UErT)UPF%8mPEM8uPa@]#R#u\UcI'q[aNM]H`r='Y`o4uLbfA#O +_l-^29heMYR_ct_o#MB^_SX[Fc-XZ:&eGc3c,RZ@n]:aN#eu3u7RLM/pA9@~> +&,ZD,g"cW-qtpBms8Mrrs7lR$rV-?l@NcjBYl+Csrr;fns8;iq!rVrprr3,VoCD21qu?ZnrVlis +i;WcTBE%r2rVLPZR$F#*PEhJtSW];%PaRamCJn8pPF%PtNBq8GEKlTSF=3',SC#"G_([&L5L\5f-bK8MRBV,q,HF)5An90b:: +A9E'4E-Xq2qY^BnrVQWnohUR8P`h?%N1,usR?X%t.tX.YOCV0Z@[*+@Q&q;"@ouQdPa7`$R['5, +PE_C4PR)DXq>U?lrr;rNrW<#pp\k*ukO7p1o[r[5s8DKe&-)S,rr<#h/fuAed/O(Fr:9jdrrrAc +jRMj)rr7K~> +s8NSiUo*,Ms8W)urqu`os8Vuqs8NK+q#CA#)]Bs&qYL0is8W)urVulqs8Murrt52/qX^2MUoho< +rql]ns8;osrVliqiVioYrr)iqAGl7AG*S/RQ'7K"P+@c%P*DB!1i#ukNg#fpP)VbA6ZJ?QRWKp: +N1?2tQ][T!PE2)pNDB0Hr;HZjs7uNdATrWXIt`]2HW,!:It`N.JV&H'7dSdH]ZIn*^r=4-aKBf4 +G^945GCFseIuBJQNj$CE_SF1+R=0:$H$k!cH['d]Its#GOcZia`4*n,^qjl4KR&)nH@'s_H$t@* +P*Cfa`5KR-,\PTQ*SOHr:E=AXs@QC=&-Q'[l%PEqGtQ$.KKrVZZos8Dcnir8uX%fH>(r:@P*YH>%$aS#E0 +o)A[hrr!6(s7%TV*?`T)s8W&frX/W(r77^6S"o*Es*t~> +&c1k-DJ5]YaN_lB_pd/U_8aXsbVI>^`l#[.e([4t$k89IcH4/Ia3)WNc-48K`Poj>b/M97`QQTG +\62m1>`-54_8aF:`qdd9a90H$aisb0A'e%^OHuH/Q'I]!SW];%PaRamCJn8pPF%PtNBq8GEKlTS +F=3',S=#_7O-#?]OIMDd3RcHs`5B74a3iI0@rd$EK8G>5=E]7GMhcq5JU`#2i7dJcqXZ*a@-rF;iD-g^m4NGje`6$-?aN)F`K9hgdQCEi%Pa7`%Q]3MI +R%07K:e>,\PTQ*SOHr:E=AXs@Pa.;qPEhT%R$a,$OD%b=ccjVUbK'FuiQ)PR`m2`IY[gL%8l&!1 +]Yhk^a:QA0_9LBQc,0ku)]5p-b/qTja9]_K +&,cJ-g"uc/rVZZps8N#ts7uWopAY("nKfD,+k?Vmr;HZqq>UElrVlrsqu6Tp"P<88kk"]O!r`&p +rVlisi;WcT#6+Z%rVMpprg#:+P*M9"NgQ&pQ^!ng1N"q1Q'dc&RYih289A;KnbG"pG)3"U,#1!!!$&#nfXBJ:;EZ@N,ae!!*3&"TSQ,"TeQ%*sNNcG("rn34,VP +E,%^0A"rAO>Zu,aF`hY5?haTsrVldEqtWmHQ^!`#PuX-6Mk?/sGU8Q9P`dY92NjDhRZs2#3,1L6 +RZs&%Pl?dUQ]mZ$P`h;;Y5A1qrr2rqgAV0OqY0sg$1[EPnb(brs8W&erXJf*s8;in@NH^>ZMaRt +nc&Of#laJTnFGf@s8RT~> +#QO_TU9!_]s8DrsquZlts82d2s7cQnrpCmL+<8i0rqucqs8N#rrVlfps8Murrt52/q<4-?V6AVN +r;6Nms8Dutg\rf)rr)irrquSXAWr7?Q'.DsR?!Z!PF%N%J2;'tP*MDuR$rmeAOqrMQ]3AKPl?k* +Oe%T%SX5J&PXP3Er;HWps7lEQ3.lj;JVSM/c%\/XH&$g!ItiSfA*r*i_oBU1aMGj@WdJi3rd+Tk +s*>H*H[:!mOHbfr^;%V%_304jG^4[cI!Yd;*-cg[L5h7TY._iq^;mg0F+J7SHZFR]H?ja\I>7`[ +@>MM3]t.*+Y+n@(CdUa%S`em5=)`&"F_kDip](-irr<#tqFHR2Pa%K!F`-'OIUOtLT0 +P*_<"PaILkC-%?CPa%GuP*(lnPE_H!P*TFWqY^6irr;oqrr<#Trr +9DI\RC2C&ga3;]B_pd)S_8aR=bfn5N`kBR>d`i+2'F(nrd)a2GaN2TMc-48K`Q$!Ab/M99`ll`G +XAi>"?C/LG_8jL9a2l?Eb2(-R`l6'Bc-4DYG>aHdR[fV-PaRQ!P`qGuR=I)20p7fhPF.er0kEn< +Q^<[ZLm4*gR$3npPF.f)P`7EKeB#VJ`lH0Kb9_@[Jpr>p1s:P.;0@\GJ9uou?Wc+jF` +-'OIUOtLT0P*_<"PaILkC-%?CP`h5lNK9-gQ'Rl'OHE:qbg+DUb0.fGaMu6Ua:HJ6c-"/9Dbj)" +:eGZ;_8l,f&&uH'dF-4^bruRQ'U?q[a6q"S`Lg*t6X71ipOE~> +$3'u$g#E&3rVlfos7lR(s82]np+%@..J*T>rVccpq>UElrVlorqu6Tup"8m)k5>8Yrr;oprr<#U +rW)orraPj(b#OaoPa7N"PEh;tQBmbuQ'jF"3f0/cPF.H)9N!m%P`Ck]=d#5()*[KS+qQRD-@kMUk1IMMHq:F[k]7L3.KYF),Z-65'G-"8Mot!s/?#'+R?JG].FV +$3C2/!WrH)!W`H+!WE'7"Ubl-E,oi1=VLrO!!!6(#Qb#."U5&-!+,g<(0>U,JPS^XWbPa\3jS;; +li-[5;KQtlI!7a:q>^ +%fcCOUU'Rjs8N#trqu]nrr;lp%fcG's7.KV(FASMs8Drsrr)orrr2lrrr)ir&cMY&eX*,1YNl,b +qu?Worr<#Rrr2otrVldRrVO`9PEM&lPF%JuP*_K#P`h<"1J'WaQBIH!OJ#9F3J!ETOYU(&RZs2& +Q7RrPQC*l"61k-S.K9;DpfMfnK7JH(:78i0M2KkJIYNT/Is=``ikiOK`l5a6`h[p"H['i5HG&r.THZsa]G]n7UJ:s#HLUP+; +_0]!tV.WtT41"D8kP=b%;KHnjGB,_+q>^?ls$6JZs&"Q7Q'dhq-]==[OI)0"4b]8_F@SKJM3sNi +R@&Xd4a*3TQ'mi'PF[r'Pa%N"Q'P,#r;QZprVucorr<#Trr<#ur=&W(q9ZFOY-P"%pAFs]rX]&. +rr;upr;Vj&)'5.Ds8VTf$N0r'mA6>CU!EN7J,~> +7JPraSs1ma3)WMbKm+NN0]j"Q'[]"P*_K#P`h<"1J'WaQBIH!OJ#9F3J!ET +OYU(&RZs8(OX>d=S"5k*0%JIpb/M69cIYF$I=crrIR]I.[=NkR5(A9\sR]"Z&"_nj1,^r=1*]tD.u`PU^9qYBpY[]dig^s0g@`l6$B +bfRlA^W+=1nGDb=PZDb&DI@]HUXm9"g"UL;DJN[0FtLbT7*NNia2,aH6A1GcR$EUXMjKloQ]mk$ +Kp[jA7m2Q_R$<`$R"RC]G*nDWR? +s8N/lgZ8>5"TJ>us8Vim&,H2(r66N3,"_h2r;Q`rqYC-jr;HWrr;6Kn"QSA0mcFHJ"9/?"rVl`p +rr)lOrr;pDmn/b2Pa7N"Q]Rf&QB[N"OcPe`7W=HXPaRMjQoe>CR?igiOHu"-PrFg\KVXosQAST3 +rr2rsr;ZSn;0n4WL4iorhn[1NdAS[SKS,/2>);g]J:DW[EboAV'F50P"9\N%!!<#t"q!5)Chb[5 +q>^Nt"8W!)!X0/oAnc$b#71b9!!36+!9mR? +%KH7CUV$?ts8N#ts8;fns82d.q>L?iaWMNB7K<2]rr<#qrr2lqrr)lrr=T&/s8Mih^Rh=&ZLn1o +r;Zfqrr:dQrr;rrA,Z>o8<9%!P`qH#OIhZ%P`_ArO-VOjE0-NQR>m2n4'#(DQAmb`QB7AqPE7MQ +Um-q)M,3^?rVlcos8&`iFbbO"JR"8o_8OCBW%!FNJqe\ZOoO+Jc,.B>`PS?nDgQVOGl)biI!U!_ +J:`Q9R`*%[Oa(u!I=6HdGBnL[H[U\kOo',Q?s7fT+Ecl27B1VpqrVuosr&JD9Q]mYtOt(jGOIhc%PEM)nPse/AQC!f&P*M;U +4[>9mR?Ys~> +7eYi+Apq>+`lQ6?`m)cJ`Poj`m)T? +JQH#VA$a4n_oTjm2n4'#(D +QAmb`QB7ArP`./DUm[O8KKR`4c-=;HbL2D:EJ]C#HrGXFZadWcS0iuAJ;&5GId,))o]u)FrVPK^ +ZG=/kqSdd&a2Gj3]Xk`#qY:$7\[]VtaN2BAaiqiJaN2B=`Q#j:i;``Tpo_-C]tDD(`P]=#^;S(6 +a2,C([_hn9o&clG2lN]JF#u84`l-<\ff`ANG'A"80[[hZaMu$8eld3^Ockii.X[SESsGS(P*2&p +6qC7BQ]mf"Q'E>44[>9mRus#"P*_#oQ'mu'PEUb^dEBeYc,e#IqoJf[iQ)PQa3;ZI\VaB`92JSf +[)9oSaT'6i`6?]Vc,kPF&e[s\_og*kaT'9d`O/rA7p)>Fp4*~> +s8N/ehWk(>#6+Mts8W)rq>Lg"s8Mr"+h$i10M#Qt>;#6=uH7qG+4 +((:cT!R11Y-uKhlko +Fli#erqcI]BQS9:F&n.ms82]ns7h^lR$*`!Q9nc;Q&h&nQBIZ$M(^M-JrZ1cPF@T"D_lQlSX,P& +PEh@uOd)6"PEqM^@/Km"rr;oqs4[GRr;6?grs.`Sn+6"ns8VTf!rr8trr3<%d3p1[8H8_hs7$!o +s8N&[lLF)us8RT~> +%fc@8UrN6+s8N#ts8;forr;lp$N'l%qQ(VV*+o'Er;ZZnrr<#ts8^&=+Za'Q'@Q#R;SIUOdMH%S;]]qD3C-KQ^j8)FZhFm]!Q@^3IQ&:om +G]<J-C+CGC+[_rd"Nj=F>UJN4?gK^nX&"AtsrP +FB*DAlM1>RqF>7@DfK\qM>I8Lr;ZfjMe8<=Q'@S$>*5)2Od;;rR?`U^07o4NR?O)$QZ"['LRj`r +OHYro6'7D!Q]m`%I:E3R"TJH!rr:^Os8W,t%/^#$k-hOtYHODWrVc +8+tkoA;\+8`lQ6@aNVlL`Q#p=aNDZHaMlQOdu=SC$qJ^ic,[fB`lQ)=`W4*Yb$iQG5'E3[RZs#&R;SIUOdMH%S;]]qD3C-KQ^j8)FZhF< +Q'7K!R>m]"P^XR=Q][l)DHP94bg"8Nc.!thN.cn8;LR9B\$iQ7,\D[ZJ;/PJLYpM6jm;d4hoE13 +\@T;crP/?F(rGr]bL"5D`6$>P@Zun;P*SFb*PEV.qOd;H(PE_2P:"78,b0.lIans0Zb2LDPb/hiLaLmdA;+aSb +;5_=No#MB^a2Z +s8N/\hXLLD#6+Gqs8W)rq>L^!qtg8m.jld!krWN2srVZTl +rr<#SrW)orra5MQE0uuZPa%Al1RF8gRuinoQCC]bP`q>nS!&r&,X-C8S;W_uSW0)$>=+L:Pa[hL +Q21FXs8DrlG\DVZLPT$,qtB$f_8g8#LP:YFIu$k2m`'s&Hu4.$5Yam<8432@(d15X8kDB==#b,+ +=]8I#;c6Oo=BSa/='5H&(0+C; +&-)@)UXJr8rr)irs8;fos7uX#rVQEgW@&l8SbE'_!ri/uqu6Wqrr2rr&cVh1rVZ8]V5p]@qYpNo +rr;uos5Z)kC?\$`TLZa-j>Yd:^F[^EKK[E=iL\@B8Z\@]&O^UCDX['onA@CE\V^U1;`^po.Y +Cd=hl8o8Hihs(1:s8DOJGACu9E&l_Qqu$Kopq9OgPF7Yu?oF(1R?X;!PF%D25t$^#Q&h8oQ'?D9 +NM)ApSX#P0O!\V,QBm`&PAH'Ms8)`prVccMrr +$hW1q@@"[G`l?*Bb5TH^`r='Yb5]Q_`t$5nS!&r&,X-C8 +S;W_uSW0&"=?i"8R%9@CH.'sZbKJ5Z>?6!!Kn;jTj4qk`[(BLAItN]>I=jtjhq$?Bp?hb)a4f8# +iSXRnro+mSjlY[`gYhD3n`AflnacA@lfm^"l0.?rlKRR3k^!'Io^D/;kO8-6nG`(Jo'>W.o_87Y +p%%V@in`n_hOcrD4f"lHChI@s_U-3DaiPWCE,p)6+LokA_SaF=Valc-Q]dG:2NO)jPb*\pQ&em1 +7#dV"O-buoP]\41TTG>/Q'[hs->Jm7QB@Dk@<0Z8`lQ9Ba2uEDqo[m=&')f:aiM5n@oH&p<_epp +`:Ct\`l,sEcH45Vd1-Wt1rRMUa777N`sBYrEDT8-I+\%@~> +s8N/Wh=U[G#QFPrs8W)qr;-C's8DunrV6<;irArQrr;urp&>!ks83K-s8UI.o&TcLrVuoprVQQk +r;HWps5!\SrA=NRpnqSdQ'mo&P%QM%R[0,'PaIc!1KTa,Q(!f#R$)hT>*,)0ir;ZaDqpn,iN/*()D#*l(gr@=..>O+P*CDCPF.PuPF7]#OZ=nPPa%K$PF.W#G"\*` +Q'RSsR>m.s23j>hPaRS?V"Xicrr2urg&;'Nqu$Bl#3P4@o^^N*rr;Nf%KHG,r;Z]l]%m/Ws82ca +rr +&HD?sU>#GCrVc`qs8Dlps8Vrp$ig2*q"saZc/AX"qYpNkrr<#qrrQ'RSG0nYp\P*_E#QB/iQ^Eu#Q'RVu2Hl31Octo4 +Z2ak%rr)cF5_FfBIVWZmq>K3l`M4o8JUr9%J:BcCr8,3-_8X40_8++1_8uSo!6"lU(rjP/]=u/$ +ai208a2>m7_SsF1`PfR.`P_\lG/M28`kop6_S?Er;HZV7$a%'P*CDCPF.PuPF7]#OZ=nPPa%K$PF.W#G"\*` +Q'RSsR>m.s23j>hPa[\CVYC,frr2urg&;Q_rr;uqrr;`,Z`gU:REPF)o)AXgs8EE(s8;`&f_>+> +r;Q`crX&W'qn])#SYYfTJ,~> +"nC2Y>b/\0`WXB,b/VI#aSj9\a:ZS8cH+M`gTJ$3aMc*@`Pop>rlQ5%`l?0Dbfn)G`lQ0>Yut1: +?&lY>bK7fHa2uEDqo7U9s2P)[s2AEdc-_DINg?,tOcj?3K:8*hQ^!l'OY^(%R$F,%QC*h_2Hl3/ +R$3`!QB[Xn>EtkKQAeFsd`TSPc.'3SHA7'$@TM:9h8HY1R2gF%LkUYU-gqKVc0r;HEap$hGCp&"^`p[[nIoBu/?oC28n+uYMnF6#Dqu6WhrV5mRnFQMS +r;6?fp\=[RmGci..s#AqUQRoV0oX,q`Q#p@aZqo24'PlP*Li)Nm#bfai_`FrlFuZiQ)SNaNhoK_6%EP:/+\\ +LV:XGo>hK_`Q69I`lZ +s8N2Zh"q!Krs/Q!qu?]oqu#IQs8W,u"98Aur;Q^"dd?>Ws8VllrVlisg&>7Tb#4UlQ^*f!4]6Eh +P`V5tPa7W"P`_5rQ^3c#P`JILQB@T'PF%MrOt:j>R$X,$:>#:;q>^Eh>BPaSMLe(fs7uQK_Si<3 +J;K&H[0^E9NQ4RF)uPJrcS9arc8fsG'&+WFEDJ:BfB=? +H@'jYG'J1OF8^8iH$FUTFaSX_DIFbuEIonmEGBMoNi6Pr/;3Egrr;rqprPjFFE;Ifch[PmK!P+%]#R=%Y3GES;WQ'G7gqu-NorRq/[r;$Bl +s8N&unEKQ8oB+p/s3LZLs7kI +%K>OdU?;@Or;HWps8Dlps82fps8W)urVucqrVlfps82fqs8W&srr2p'rqPcHX/iD`r:L$Hrr<#u +rr2rtrWC8=P`u*3;3O.@FdJ>SQ^3l%QB[VrQ'I]%OdD;n94jKPR@08*P`LnUG)hiPP`\erqYpEm +r:jH:KmeZ'55,$SrSbZ3V)`sVJ:`E$KK8>lhSR7H_ns:i_uI[S_uI[S_[j]+_nWq"_o8dY_SjI1 +`52Gi!5nfSs2,G_`l#X4`l6)q^r!h(_SX4,_YqCP`#?M/`kK1(_84(+Nl]2R:bm*)DGl@f@Tt=7 +mf*5Rqu$935]La]Da2MBr;HZqoKe_0Q'@Dj/o6f9Q'%8tP`q;nQBRSqQ'di'O-c8oQ'dZ#P`_;Y +8Qg1tQ'7Gt;TSe9rr2rrg&;Q_r;ZWlrVlT:['Qj:Vm#moo)AXgs8N&us8N#q!WE#rs760gr +%e%MO=fK9U`Q$!Cb/VEAr6##[rl,nub0A>ae'6%XaMu3=`lQ6Db/hZD`lcHJb/hU%a9]P^;I*L8 +SAEAFaSEj7aT'9[aSs1_b1*IZOd;AuOHVh*FdJ>SQ^3l%QB[VrQ'I]%OdD;n94jKPR@08*P`h.Z +GE\PdQ%_3'a2Ps>d+Us%J:3#k/(`G$f#4i-TeCSCLP^ko8o(2MQoDe?.n+#l6p%%SEp%%dul1"*/m-aE9pA"@OmHX!&me#ugP`_&gM_Aq5PEqAoQBIAoOd;2rOHl2u +Q]IK$NKolpS!B/(HV.IdPEq>lMajqt`r='Z`rO3Vb2LDQ_p$9Ja2501=A_[f=)#:c`q.7\`l5p< +aMu6@bgk:pccH+)!64TN$HL-)[YdsU +s8N2\hYmBOrs/Q!qu?]oqu#IQs8W,u"98Aur;Q^"ea2M[s8VllrVlisg&FE_s.GkcI^G^"FXFa/.VEGS#b;/^GrFEr(TH$ORUEcQ5EH?ORVDK&i;+AH`u +H?FFUH?saWG'8"NGBe.RH$jmX=t(.oEHu\:ApA9=6`d+Pki2%+s82cpr;Bu2DfC#@8,3)_rV6D5 +G*A2[R#*jpOIq]0PE_W(OdD5qR[P.\OHts!Ng#ltPF@r*R>uApG>afoR?s./g&(aIs8CUL%Jp,( +s8N&un`TK6p$191s3^iFrs&DWlg +&,kLaVX=?]r;HWps8Dors82fms8W)urVlfos82cts8N#rrr2os#laGJQE.-qoDJ7\hu3WUrVlis +1]I+76'[UuR?Dhl>^2BrRZs,)PEM/qT95J#PF7`#T9,8!Ru``&Ng5o>3/3`]Q'@S#p&=t7rqEuo +L4=i+:8Rdds8(m$^r'\MH@U3jKR\\Kn(P^n_o)Jjpqni'_ns=,_nj4*`NG'"_o0O1`59C-_8F10 +`Pf[3^;7_(be]p6\&#o"_ns7*^r"",`;[_`_n8SP*DB$Lc@kaSWKJ,PFRo#Q]dT%R[0&$O.MAqR?s,)R$3kqEa*Z_QB[c& +PXsL+s8N#trRh)[rVu`mrVlZC['Hd:W2TLio)AIb!<2ur!<2oro)9!prr;iHUn"$Pi;8$~> +&+%/F>d))c`Q$!Cb/M?@b5TTeb/hZDrP]#]aNVoRb/h[%`rF-[b5]Nf`lQ6Dbf\#Hrl>AWQqppl +C9GIsqoJTQiQ,EHaNDTF`l6$AbqbBER$Wn[:gA7-Q(!r'Q]mSsQ(O2*Ng#lsPFmr#OdhDpS;rl& +AN>U:QC +Km8&`CGVEE1= +"onDYiW&fSrs/Q#r;Zfpqu#IQs8W&ss8;os#5[-BkMQ=>b5PKBkX^u'Pa[b$Nb;^aNg6#sNg?-% +OI),qQ][AnOckupQ][]!Q'n,*6TU9rQBdbYG5;%,s8)U`G):p,?_I&hs7lNJ`PK=3838OsB827o +3kW4ZG'\B3G5QL]G5ZYYH#n%@AO$Y58nr9;=H#mhF=+pJCs8VuT9Tb^,P`Ipn +J")RaS!o7UQBdf$OB$"`Q^3c$Pa.Z!P*M5oRq?;r<.0suP`eYpqu-NorRh)Yqu?Zqrr<#nk4&$< +ma_=:d/O(F#6*EDn)= +&,Y"VWV6Alqu-Nos8N#ts82fks8N#rs7$$ers//6R&[Q!MQ'[_sSYFd6J,~> +&*L?3?FIkq`Q$!Cb/M?@b5TTlb/hZD`Poa4`Q#psaSj-NaT'E_aT'BgbIW@(>?u0V_u@gYaSj-3 +aBm$1aMu3?e]9\/PEhSu6&oSbQ&_,tP`D&uR?3buQ'[VpPED#nQ'[W$P*MH)P!D$fS=,V'@TUam +^!4aT3cft&G"I2*aN_`Q^U1el_a$.45[SqhGVZ_]h=UXAoD\:YoD\:Ymsj`jl14HEk.S@fq"+(D +md0?2oC;A>mHsB3o'u>Mo[:%Pht6L/m-a94o^qbDlg!d$p[[\Ep$CtSYf6P/ilOoAF)p)4E11Ti +beq6>@de;H1*A`q.7O`r*d`_o9X +"on/Sj8\oRrs&K#rr<#rr8IYTs8N#sr;Zd$nE96'iW&r#r`B$f6Bdb)Ne0]_Cg*BqQ'@W"SW/Vs +Pa7St67\4bQB[_D309nrO-"K_4-l5%P`n_pqtpEdrI871KS*rcp](*io`*dh`l2qTZ"/bqJqeVE +`5ma[F)q)!qfZ1gG'%qA@Rpb+$V"lNGBS1MF*W(VF`VVHG'.tIDdY@1$kZ0QF`__NH$t*dG^+CP +DesB7G'%A%*"WPk>A\kC)/3\6^A1C"W)(t+s8VinqZ$-dgHBV!G&_L\63$fVqu(#VQBIc#85`BP +RZNl!BM"DRRZMJ\>@W_ZR#df#Pa@])QCNqDEg(oFR$s/%:Y#@@rr)orfDZ6Xs8N&urVuoZme$>H +e,KBqrX/])s8W)Ql1!a2s*t~> +$Mr&DY5A8!qu-Norr2rtr;Q3r;?Qms472Ks"XKA6'.A!Ne0]_Cg*BqQ'@W" +SW/VsPa7St67\4bQB[_D309nrO-"?W2j'AmP*8Strr42:qL)\'J:M3Xp&Fphp]'*i`5?GIWF(E^ +ItW5IcfMsZ^;'9W%`6-#b/;61LQ7P&`59F-rko&X^r""-`Pf[n_A:85V2C4l]?.t-_o'7(^;.S% +_oBa9ao94d_T.t+Oc%/r_m_[NBeR9q8#)hRe`d&5q#C-hp&E`,E,Tc6Da46rp\Xr!JsDIfO@E&D +PaIJsO_74KQCedQ'dW'Od)2sR?a8#@;q?U8sYm,PYfp/rr<#trRh,Krsno)s8N#b[]Zg; +WLEATrUTs`s8N#ps76-rs8N&oiMW09U!NRc~> +&*'[#@^sD!a2l?EaMl->b5TTeb/hTBpr36Prl>)^oZ.'QaSsp\".MqXaXT/+<#sl14? +][aYDBl8!,@k-B`a2c\)HB=)RN^lrGQ^No!O(CeEQCedQ'dW'Od)2tQ]mhp?ZD6X9p_3) +MF+r#`Q#p=a8O09a:ZG4a3)]L`4r0U9M/&X9s;ISo#UjNr5AZUrlG,Zrl"lWo#M0\aN)#e?;OL# +R-&o~> +"omuMkPt8Ts8;lrr8IYTs8N#sr!<<%k3_R"lMpn`rr<#Er`B!`8!T=.OB$mrK2[\oP*D2pP*)$# +P*hG5DKeI6O-u+r9TbI'R[?cd7@TF'P]_3Hp\t'_:i_8FL42kjs7uTlr;#Eq^91;4Q<*Wk6:_!j5#"NNSH[:$\F*;eOG'.qNF`qV>?n`#m"qO=DEH$#EH$k!bG^+CQ +Ecu2CG%PD%#QP5BA9NQP)Ib7hp\:e(X+kHTo)&:`s8MonrMN)0Ed;G3DY=&.p>SE%Od;>'G"71L +Sro-[>rI\2OHi=^Hr"O>R?s&#S(OHZ8ADNpSWN09Ws9$mG5rr)orfDb^J#lXf'kO&!=p +$M_W9Zi:"(qYgEmr;Z]ooDe[drVufpr;R'"pSOR_[E84Br;QZpf)G[K=8_ARP*25pL?nqW,]1XA#OQ +;Y"L"D/VZ"e\0#>`:Llb_Sa41`k$qfNfL?\^qmb%_o0F/_o'F2_o0L0b/U68N/in-`P01-a2>[, +^qmn*`5Tm3`l#F4MMm=OK#bmIYs37bUAFV`Au:/smIgJUs8W)rr;EHXF`)>8@rCr=s7bJ=OcYio +7!(kKP+7Vq,$[!bRZNe)H@/M(OI;GtPamu'PE1lt>\oRj6&LemQqGa'rVcfqqu6Qoir8rW%f?8* +rr;]&Xfee,Qg"AGnG`%ZnbrmqrVlWVWgfNP`VY/~> +$K.OdB=c"&ai_]Grl+u[b5TTcb/j@trl+oW#0=s0bf\#JrPncVrl4u\$,jGf='9'e]uJ4naSj-Y +aSj9?aT'Cgbf\#G`Q$9K1l[]hOAgapK2[\oP*D2pP*)$#P*hG5DKeI6O-u+r9TbI'R[?TZ5a[^t +O_\CYaj88Y2e.GpJ8[9od_io=aNhl<]<=f!LIU#l3`T?5e'm7Op%A:Uq"#9koC;8Bq&O`lH-Cc,moAZ"$F!92J-T_8buco>^=A$H^?.^8>@9 +:/m/5J,~> +"omiEm/QeYrrW3"s8DlSrr<#trX8Z$r;ZfSoBPH0s8W)ts8UCD4lSi"QBQ>aQ&BZbPEDH!NKfcu +P`V/l:4\U,4boPiPV#n#P`hAV;05KsPEhJ7\G,mtoSd:;KR&AB`;f]5rtPG*ikrH9PH8K>pA&H7 +4$`2JLOF7@Fo?IaFa!_+'Q\A+=qM;M!!Gf/GC=gbF`V\JG'b7rs`r,i9qZ$Qps8MgcCNFT;F#VhIs8)G/G*SJ\ +=D2JJPaI`*AO:4#Oc2JTKml^ +#ku*.]Dqp/r;HWorVuiqrr;lpr;ZTlrVuos!WE#prs8PsS"ZaeebK+@rr)lJrr)j[iBiutO`F3^ +MfMp`Oe.\pPa@i'OHko&JWitpLRaKk/QJ(#P*^>dE(H+RPEo4[qu6TfMdM3jH&+r-s8)]o'DLM& +]NgPuA&e6>C1]\$q:ra*^\5PF_Yq@d`6,s3K8u%VNj6I:^qIS$`5BL0_u@OQ__KEUW/ch"O,g]g +_8O@5_na((_8=+.`5BX4_97UlNffWg_S=%"7Rr=4r;HLqS#KK1rr5=]rVlisr:i`pDfBQ42;%[' +r:P&QPaI\9Anb))QB[hK6#;pNN';fHIS=mNP*MPqO%XY]P`T$fQAgtaNK0E*^&7U'rVtmVrr3#s +rVlg'qnK;;X/q`BrVc3aq#Ba\$NL&(rUR.^R\7@jJ,~> +#2Y\TDn*^*rlY5[rlG)]!6G/Zs2b5_s2b2Zs2>#[b5KNbb/jP$rQ,#Y%E6?0b/M"s=B9!t^W+Fp +aSj-YaT'Babf\)faT'Cfbf\#G`Q$BD0U7ofE_MaVE`eNfSro"sQ^X/#Q&nalP>XQ!S<9$fn+,u9_:@2giFd%hP`f*\Cf?OaP*fW?Cm^?D5(SE:9Qc\b +Q(3bn8T^)VN`U7fO-MV]M2t0'c,KY&rQ>/]s2P)XilM,=s3(nn`l>N%9M7rR8XI<6o#UjR!6G)X +s2G#Xm)TLQaMP,o;,'o +%fbYJoDeU`s8W)us8;cli;WcV!<2uq#lFQ"s4d>6iVW?KrVlipf`"pe=d"i41lINeHs2JpP"%Q. +OHYitOCW3jP#t;$PETNQ?'(D#=N +EHl_OG'J7SFa/(AC)[0XrW$"2@s!$C865-PqYg'.=JD9bp%eF`s8Dilr:Xr:E,p8EGs&A'oSl%& +RZiYH?@YM&P*D2H>"4I=I8# +$hh-*_uKc6rr)iprr2rrrr2rlrr<#trr;rrrql]rrVl`p$NC(uPbb4]htR'Io_njjh#@9P4MP!Z +NKl>YP+6o'N0]Vr4ACp$P+7VED39u`8U6M`A5?KmQ^*h-FDk0?R@JRlq"=R]@;LLBKnaJGs8Drr +(B==4il.HlWeb6\p&Fg`92P3:`4j11r58KM!Q2eR_[s](_T77dNfK?hZc0i"^Vdqf_u[cmrk\WP% +`YN/O-GfiOL`BL`lH*^f(sOHYlC=[n@*LiJ,~> +$ephQGIYW2cHOGN`VmmXaoKN^`rF*[b5KQaapQ50`Q63Gc-=JTaS3[b`Q$-GagtAS)\c17QaD,PEMGtA8RHl=@me%P@RHpOI)5t +7rN-=N1-2Y3T/?3cU(c`Jr#(j6/g;8`59O:fZ(@efuqjdcRCa*gZ%VioD\:Vn,;c,oCVSI +ZG"#c]tN@pnF5i7nF?/CoC;;6`4isn]))0e +g5Bq6gs4!ZVa[&=7G6#)`l>g1`6He1iV`AlV +$if;Dq#CHad +Pa.MrF'FZrOASDfP*%b36fn(KI!TsUC,ce/r;[3;9lP5q +DK9lCG&qa(Fp<).G%2Eq$2sp,#8(HaH$tI=hY-[@s8$\pWF3%0rr;rsrVu`opNp%aEd2YC7TtmW +KP4MVQ?$#ZCfHagQ#fr$GE@SJK7SD5N' +$hUs&aSu5:s8N#rrVccqrr2rlrr<#nrqufqrr2rtrX&MkOK#:bjno#Trr*$"rr2ldrW)u\rr<"$ +rqEKuOI8+aPF%;LE5;:;+,MhOZ-rZOdV;Y:k31cOd;%m2Ep^*AO2'!KS=kC +jnJQMqu?NlrSGC]TqkQDr;ZfqrV6eXOG?5VJUBV>5*VM!P<4_jG +&($5DJ@ie>c-"2M`lQ6@`r='Y`rF*[b5KQaaoof*`lQ=$bQ>r.aSNmfaMu6@bf[o0941q5V8UHt +aSs0\aND[)aT'B`bf]_%!64uYs2slQs2n]mb/hTAaNKT;NLW1fPF%;LE5;: +;+,GfO#UfYOdV;Y:46STNg,Mb/1K3%74`e8LkBe)aj\J\_T'F;f>OmVQ^9nRf$;CYbL"r'f\Pre +l0[m6mk4"rnaZ5Em^^*I^:q@ihXU+'mI9W8oCMMBn*ff=qUNh$Vbfn6-aSs0ZaSj9Oa9p&/aMPE';bg).iIC~> +&-(#2s8W)qq"t!grr;usr8IYTrri?!rqu]nrtX5Xo],uNs8W)srr)lps8Duss7lWkiVmouqu?Qn +rqcZmFaKC38WK+"P\4T[15h+53as8;ons8W)QJ9Rf=M>.&LrVufmp[l=XH$+:UH?jd\G^4R_G'81LDerGf#R(;/!!rp%CiOT? +G]e.LrcnZpG\q+L(An.I#8'aOF`qnMG]e=OCN4< +"nJLbchIA9rrW/urr2rSrXf,/s8W)rlBe9JYO2;frr;usrrW3"r;Q]tp]('crquf`raPp*s82ip +qZ$D5G`dhQNg>imB4c`NMis5bO-#Zo1maDkRoDY!QV_X,A^Hns8VllpK)$ECd)fUD+HjCJUKbEPCH5PL-CKXP]0cN6]upeJq$a6O%jkbP`InfQBZ;e +HqT`iPE]-oQ&/?5pAb0ks8VrorVZ]brr;rqru1n8rVZ]is7lQlr;HTjs8Vlnd'BmZZ)4f!r6bKM +s8Mri_P*ToU@j$~> +&&)ppOhANL`Q63Hbf7WA`q[XRb503Xb5'*Z`lS)#)TKXo9iPD/YK+o2`l,jdDj8K +rlY8^s2G;_`l?*Bbfn6"aC<'2aj.rFcdQ'9P)`4rP`h8DAqDdrP*.nfOHu4iQ]m](<'GJj9Q+-E +PEqG&G^`t4OcNmSHSm`B@!I*LI!A)-dF?RdbeD*AfY_WBQ"asVccF/Kb0SVsf%oian*B<.nc&1\ +o5s9^na#f=m(C*K_Sj@,]\rAPnaQ/?na>u8m-jE4qprj)`5oj3_Sbd9md'*%naQGMoCMJEo(Vdm +[D9Dl_S3Rmp$qA2g<8sBc,I]@a#KcW376!ic-4DVai;TQ4'?67FDkt]2h$1#4,f5J89fbKLQ@KO +CK5RHPCQ)SJ5p?G:3i1kO?AVhP\jBI99kd+PZN'mKL45-c,[cDc,n2P`l5p:b08$"aeUa1f3=>>7Ub:M[JXo#UjRj2_SMbK@f4H;dO4A(lG~> +!rpd)rr3<&p\Xmdrr<#tr8IYTrri?!rr2iprr^jCp#l;R%K?>(rVcWbs8MZis8DuTrrL?jqtTg#LO"#bH$X`7G5lgeG6`P7E,/>i$3L,*&dh6JEHQAJG'8(P +H$=CVAiUS.qu@K;&PmIlH$FOYIW0.DD09r@?kWYS#6Ff(>m)$VFa87\N7IRurVuijQ@,EKQhpaX +r;ZQjs8)cL4`l$oF(RLeJV/nu:RH`*Lk9\GS!SRtKNquQ4&\16H]sj_P`h%jOc>`sM`jOjQ^dN&rX&W(s8VNF +lgEd4J,~> +"nALdde3P:s8W&trr<#QrXJo+pu.R9XL%KtrVlirrr39%oDed]rVufqqYpEms8N&so)ARe;uHOm +r:j$DP']fhPaIS7B1&;QQ'4=jSr$ZsNKochP-,.:7%/j8:k#QPrV]$8pQPc5*D"d4HYVjRYs +&%6:iR(gGU`Q$'Jcc3rD`o4u@`lQ/]aNDcPb4E\b`l5p@c,n&ObgeY(Q@_u%P*hA5B1&;QQ'4=jSr$ZsNKochP<4)>5Bg=HOHkYc +Lk\QIMhOKKH[p'WKReZ%HrX7rd*g:g`Q6$@e&j_:QRgS.`PKO;`QZ`eeD9Wan*B<.nc&/JoBkl5 +l1=]6]Y;1u`5KO.]AW8Ona?#=naPi2lg4K@fYtS=`5]X-^VT=2m-3a#nb_hJp[@eHqtn"5a2Gp5 +_SNjrAbGZ^leg"Whp'9da3&dHQ"#!ed`K\[`5U!@d^CK8F`qk<3.HC-M/#_LI7/X5CeL"SG=R9i +@?rrP3D4[]Qp6dsNBND`R$rpr7ZWn&Q$?/iO[$k4`Q6-=aj&&P`P][7bg"<%a +!rpd)rr3<&q>:*frr<#tr8IYTs8Dp%r;ZfmfCSS!r;R3(r;?HfI$AaFrqQUEm +rquHbs7UbDEb/$+KSbD=H\#[DQo]tRHqT0XIT1fP6A.R9OckutOGlDoO[>%oQ'IDi-]S+i$Bd?6 +Q +"mi.`fCo.@s8W&trr<#QrX8c)pXGS/X1J'*rVlcq#lXRGPB6Bpq"k!ir;QWomf$;frr;rsqu-Qh +oKAJ&=E'RkQB=+WrVuWjrtXA*_o'F1_o0O6`Pf[4`50F4^5[l-rK)JjOGpE^`Q,j7_SX41 +_SO=1VN-UqP*M>sOH,=2`PBF0_Sj@+a2>g9^qYtjR>d,nMj]`eMo28H%07BH5_;.1O-#QlNf#ugO$J\kQ'DH4N@0_b +PR*F2QBk^>s8V`jrW<,urr)larr)lsrr2p4p%X_@8mCugpAb0krSXugYck"$o)$f5#lX\paJ5H" +U$I4~> +%^BhaSA)kY`l?0Kcc3rDi5c#?aNDa+ar8:3O@rSXDS+0;bJq?6`QZe:G>ul8`kf^8`r*pU`WXB* +bfn6$aC<0-`lH?GbL+GY.#sU#GF"GYOZbdmN0Tcr2O0DW7Zs1)PF@LsO_.gJ=D*__6$n?9:5,!$ +GCP3sK8PD;J:)_ie()OXcbd]:b0JdOG+;lce'6(Sbe_EFdGNI0nF>u7mJQD[mI'E0nal1_\@oZV +_?[lj\_clHn*L&BGO+Yan+ZD(]t:nh_8="&^:ie'm-Es'o(DMDmdKrCo"r)_]thh)bJh31qY9^R +jNd/JbfS&NcIb+FShAF;cHaPO_nO+>dUGseA55"5KS"u*J7<>]3JW689SSg^J_o9X>bf]Ou+j/'SaMl'7`lQHPccOUW/hoN7 +f"fDK_7Q">:e+8X>I25qaT'BgbJgo?;G9bmb('~> +!rpd&rr3<$q"t!err<#tr8IYTs8Dp7r;Zfbh"'q&s8W)us8MroqY*17IRTb+s8(XP6i-i]rr;i% +@[!$CQB7DoMCaIJPF%Dh7$*KtO-PokR?9F!##DB8o8QiFF&4XGBS+P +B/gJ4"p+Z&'*AIM:3(Q(F`MSJH?O^[Ci(6q#5S=/";tclG^"Ooeb&e:rr;rrV.?KXJGB!9s8Vrq +s7H?Q2eGlmJ:i9%Ko(M(6BsTVMMkhOQ%DDVH;8-NG`eDZQ^XC5I@Wi`Ng,fkR +"llJUh=pjGrrrE#rr2rtg\qQ[rV4L*WiNo2s8;fp$iK`]N-dp]rr;lqs82fos6fmcs&AjprVlfl +Zs\uV5Eh1pOGYTCQ]mbuMFm4_SX4. +ahjU$Mia0gQ'.;mO,^`j^Vn(.`PKF+aMYa8N.dFZOcbfkP*1c^`59@._SGmFs82fqrVE:#WIoZp +r:p +%]O5WU;"L]`Q$'Hcc3rDi5c#?aNDa+ar8:5KhtlTF2#i>ai)$1_opLTHYP0Yd`fJF`r!jU`rF-[ +b4E]2>\).!c+Ca3b/MKO_aC)d6+PRk_ofj6c-4SmeD9WlnGr%Vm4Ro"n+Q4^`Po^2`5KR0 +^:E1mnaGi5naGi5oDSF4\\>ho^q[Ru_nj%Mp$_>9nG`!%lLOK7s2;Fe`l#g9aMc'5_>O3*p#aQc +fZ;+Ua33(n=.XVMd`]SOa2,mFakb2i?;"LNI +!rpg'rr3<$q>:*frr<#tr8IYTs8W)tr;Zd4jP]Xro`+mhs8W)rr;-.ZQ'RXnme?b?r^["drVlis +rVfcVP'g;pQBdaiCmL0KPadRtO^;shQBdi#RX.q"=[%=0I$[0CK7eM6P;J5NJV8c4LkgD7F'/,2 +rql`os7uZn'kio7=.[_;qYpEms8;choYQB/F`V^'F8p@fF)c%o&-r@=q>_-87qup_Ed)\OHZF:G +4qIo)"TeT&(BXgH%SCY\FE)2>GBJ.QD/^R"!s/H&!rW+("W:lmG'81heF`_;rVlcqgKp2E;qLaB +r;Zflr;$0_OWfL5L4k>g3`nI[@[`LHQB%8t5ET,mQ][i)P`04@OdV;O +@#gC@m/?bYqYgHmrr;o\rXJi+oV>N5QB=i5q>^Egrr35^lgXH4dJs6prr;m%rVuooip6!os*t~> +"l#rNjS/TNrrrE#rVlish>[ER$2s_6TrY66qYg?irs8Ps8s5s=\s8W$]rr;uqr)8N7 +G>s`kQ'O+?QB7DsRu9Uq?>l!mQ'd`'H;d+34ap%MPsTYHK6]P@15^X4JqSf3KmSPo=S;Rhrt+u) +rVL/iW)]G6s8)]ms8W#rs('s$_o'F0_o'F2`Pf[1_T&p2KSbeSP`q5kOHGU/`Q-$:`PTI/]>_a[ +I"$p4OckfeOcbZa]#VY*`Pfa5]u%\%bD1J&PDPQmNKoTgMi5Nj_8F.*pAb0jrVuipg0^2B:Y,:= +s$char;-,J+GPN&K8>;5Ko1V)7R2-NJ9j\>K1/0f<]Oo&R$!VtN0KSjO'$RlOe%f&N+SE=R?2Gs +Nfls"rqlZkrVuiqr;Z9crr2utrVm`6s7N-cG*.bSoDS^fq>^E\Z*^d?Y++tZdf0:Hs8&Y* +U"Y"~> +%\RTNW4p3e`Q$'Hc,R`Bi5br=aSj6eaMko?<*<@N^])=e`504+bL8A-Obt@*`QH0=`l@/^s2KN+ +a3)ZPcIFdsML/KmQBRUgCmL0KPadRtO^;shQBdi#R!;Im<]b\&H^7'EKnXn:Ou&#JJ:iQ0LPC23 +E_tN>bf%K9`P9L?ges03^QB[>?Q][\tEaXob0Z_\aa26!Eb/hQ>_o9dBb4D4&CBS;MTUMGXarKJ:WH,I>3E1Jq?O%antl2 +q>^Ens8037UlA^$g@5%?qu?]mqtTg#LO"%AErgE,rcnHc"DV'O!!Dur%LGX?Df]rCF`htRAj@.7 +r;[N4!!!*'":e%@E,ol7F*)eSB5]($rVup!r;^.)!!O6NEcuPNMU_:trVuoss5URlVjV/rr:]j\ +s82iV7mVu]I>N?*Jr=o*JUgBUb*NQ&_H+QBR]%N^neh +G]N]Xs8;iqrVliplM_7jqY'_\KqFQr-hIB3qu-O"jlu7%mEtt6df0:E#lXf'ro383l2Q8~> +"P'HJm/$Y["oeK!rr<#Srr2p(r:mRnWiaPCrVZTn$2jNaOH,Ge63$WVs5j5]s8N&srr)fQ6'Ia/ +I$'YZQ]%&pPEqPpG?BndQ'IMuPaRe?,@3DOsK;MTUMG""WEIXd$$H\I*,JUp="aSku8qZ$Kn +s7s!/TSQjmg$nq>r;Zfnrr3#T_#D7L_Bm+:_oBU+HussmM3*pbP*(liOKldEa2>m6_SjI+T7;!2 +K8YhRrK)>aNk39J`l5m7]uA+,b)(@jH]4,MOH5NhP)P=<_nj:/_"Rg.rVuoss5UUnUQfu$q>UEor:RS*YcY$qmebH3s8NB'rqG*@T:D\+ +J,~> +%[q-FYJA/ra2Z9Jc,R`Bi5kr]Z.q1`oY9g`l60D +bg"PG."mj[JX2ObQ]%&pPEqPpG?BndQ'IMuPaR_;;cJ]83_t0O^;e(:daFR`SW$pl`6?BK_p$6@bgG=th=0t,qYKpWn*TT8rVuf2]#i+-85F8QO,%jIO#hGs@X1R4P*hB#P`qM:FC&XM +OI2;nSt)+1R$Nb&KofeBeB?+VbKJ&L`P]U4b0':r+i_F;`lQ6@_o'F5eN%uLR?Iutcbd`C`k/?_ +:.IlS:9D@\aT'BgbK%8Z;b9SfWIO~> +!rq!+rr3<%rVZTjrr<#tr8IYSrrE&tr;Zd$f&Z8aqu?Zprseu*r;67MHD-@]Ab,fti;RQmrVc`l +OEHYE7?j7&Q^3o#Q'[o"S8hVBBpXsGR$*_r:JZ>'NDVQf'A +"OF$Eo(r7`rVcfshu3ZTrr2p(r:[.eWj("Lrr)cp#laW[G+OVQB(l*$q>UEonc&Ofrr#[jOE6G= +6^!guQ^3o#Q'[o"S8hVBBpXsGR$!Vo:/-"uMbc-^<+p]5L3YX]Jq/K1Itic-I>hu0?U8AqqZ$Tp +p\XS]U8L_h^X3#\qZ$BjrVnP,^r+(._ns4)^VIY%^:\f;I<:'nMiX$bP*2#jYf"E!_SsF1`5%sk +G^OdcL5_8oOsZ9NN4R'Ia2Z$6_7me0L3J#lKPuR.M3!p`PDtRB`5BR3_=dj.rr2ppo'YX[NhBmc +rqufpq"=&FJ:W6)L4>#0Jqnl3ItWD"A:JT0?U&p"P`1lj7n//oR$!T&L6soC;O!K2Od_JoPE_>u +QXaTiP"YQFrr;oorVl]oo)AUfrr2os#j"RFP`(TAEqfS5$i^2$p:df;Y-",UqpPHNs8Muqpt_a; +S>FP^~> +&!L^?[)0i$a2Z9Hbf.K@b2LGA`rF3\a;Mt1BinGALVM!SaiDB<`Q?LRF/P=c/] +s2OTMs2K?-aii#ZDI.mK7[KU+Q'R]!Q'[o"S8hVBBpXsGR#dDi9MTo!N)DQh=)3>ALNP:QJ:<') +I>!9!H&-,u="d7jbKS)La3rL'RANie]"?eG_TK[8bKSAkeD9WaoD&+Qna5`6pA"O#_8sF:aN2BD +b/qT=^#8JOnM9V,o_ACahnQnC_8=76`l>p5_nNeIp@7_Fp%eFVo_uV6`5K^!_Tfs9a1T'o\b5[g +oO-b+lIR8cXI?(lc~> +!rq',rr2uprqlrsr;Zfrqr.PRrs\o(qu6WqfB2GbrVulrrseu*rVZK">E5>=P#B^*ir8uXC&@i- +rU`IAQt'p!Q'RZ!R$*o!Pad\6G!(_MQC3l)OH)=tPa%Mt6#f_RLOk)0.YiJ0KR\f2IuK)+4)\qU +@CK8*qYpEbV/`AbEPf`Fqu6Eks7uZkq"2FYH$+1NG'\ReI<]a;$NLA.!!!&t!"961F*N%UG]I\7 +4;@tq!!*-'rW!]8%o."]EcQ;JGBdRC#Qb#,!"/c,$OQe49*?&>EHQALNn!Xtr;QZns5<,+7BNOd +p%eLJQSF2EJ;Si/JV8]-K61U(7RT'p2*=<,@WFmsPEqE%MDAS(QBdf&Q]YWf7ZWmsRf8T^Q'do" +Q]aOmQ=$Nhqu6QorVlipli-q^%J][`Q]mo'>-\,'rVQEirs.cNo^hP#s8UOH!r`#prr3,\lgF-; +J,~> +#0Ng?p%n^erVm$"rVc`qhu4/brr<#tr:QtcWj::Rrr)iorsJV2=H&c3O]9g/rr)lnrr)lfrrflPE)f^ +_Sj:0`5]`fJTc3bJUDp!MN3aZP`q':a2>s:_8VGHm]qra;#KRSH.IY*-(Kn=n$F(/-<6Sgb]4[rF]F%86RP`_Ai0OBp,PEhAtO'QZEBSS +qpPHNs8Murpu&!?R\IlR~> +%uk77[_^#&a2Z9Hbf.K@b2LGA`rF3\a;W%2B3J>AMS@6UbK7cDa2lPr:Q_K=N'=DhaSj-YaSj6a +aMu3<`pq,ua2Q-Dd`s4kN*^=gQBm\uR$*o!Pad\6G!(_MQC3l&NJf\jPEV;r6?5qVL4Xr(,_CB! +K78N*HA?uj2JQi9:n0>]c-b(iN+&<,DSa-&f?D%NbJM?EdGNI0nF?&@oC;58n+?MR\\$&*`PfR/ +_8j[;`4ih>naH#9oCI,"s5:c)]#;_._SaC4_nNq$]'&\Pmd9WAq#C02[CX;p^rO7*c,m]7]!Jp< +o^VG3e*PZ*`5]a9e@s5[1nBsoe'cdcHPpjDG_CBnItE3!IVrIh5s[4b0fVHp>AH>UOHkutM(rA$ +Q'@T"QB5Eb7ZWmsRf8ThQ'do"Q]aOiLe_o9^>o#Muo_o'Rs6X_U^+9N"KU_:$C3bK7rL_4=Op6s5h+~> +!rq!4rr3<%rVZTjr;Zfrqr.PRrsA]%qu6Wqe*6;[rr;lps83?!ANZ2KSt;4=^#Wa +EI1)5MhV!$nOo[X7_cp[eboCGrr*Q1qtTg#LO"#bG'81ZG^OF6$Np>-"T\],"9Sc*!"BE8H[C9g +FE;1X(]jd@qu@Z;#6t5/!=VhGGB.qWG'[aV"onf-!!i`.!!*3$!"/i?@rH@3H]?PHrVc`p:]0M= +p5sR*Efg")2aE"HLOY&5Jq\r0H:UA"?"IVd7QlfpP`q5*2NO&gQ',30PF7VtQ^Eo"3]!1fP*qK! +Pa.E!Q][Sa9pUEEq"jmfr;HZ_rXf&.qZ$Elqu6G+Gaat[C$tourr;oqrsJ,Yn*oo#s8W)ts4.)K +rVca"r8R/6mJMJ~> +#/d4Bo)&Idr;Zfq!WN,VrW2usrr36#oV&:eY2f?SrX8c*pLBAOP`nbhq=jmg!W;uprr)lfrrsQB[YtR$Wr$J5)5YB)2<#s8;iprr<#frY,>0s8)cos8N&rBR#r2Nb\+_rVlfr%K65"aL/7U +Z('GBrr2rJrX/W(s8;cOV4O3N_gh~> +%u"M4ZG=N"a2Z3Eb/M9>b2LGA`rF3]a=>3I]4,&K>*cqHb0%rNb/hZKdS>diQ]XPFcbmuM`l#g; +b08)PaMu3<`q%2Kb/qKCbgD;[Cj>p +_nj'u\%BMr_n`mlg$n7mm-XKAfu(2-^:_.r_na.0`kK$oZ/b<;iop%+s7k'L[_g&$ai;69`5]a3 +]XG0>oBc&.ea_D9`PkutaO8/KfQCSt@WpCq+YRt^IsQZuIt3)uG==_i=CPcV6TBs^O,Sre0o_B` +Q',30PF7VtQ^Eo"3]!1fP*qK!Pa.E!Q][Sa99+$bdE';O`lcBB`P][9b43f%&$d`r=$f_6S/]7nHWPQ+k>\ajnH>a3)TG`LTmq7TPIu~> +&-(GCs8W&rr;?Hhr;Zfrqr.PRrsA]#q>UEoe*65Vrr;lps83E+RmWC*SV]o5s7ZKhs8Vu\r_rjq +s7lWhJT.&9Od)&qPa7W#QB7>tQB=-bOIDK!R?O#,/7k33QC*Rg>TNR[RurVc^ilL+35 +;P^B^7k0@FLPUbAJr##)J5/7=FEVPHDf9VXOI),qP_plKPa7Q"LamY4Q'df#QBtiBHBst_R?Nl% +P*M>sQ],fBN)0BOrVl`pmJ[k!s8Drms7uEM7@]a0QS\72rr;uqr;Q^'mcs92o%F'Crr2rJrX/W( +rr<#tipQ='rI=~> +#/Ht@o_\[er;Zfq!WN,VrW2usrr39#nt<"cYN>QXrr*W1rr2rtrgZ<:P+.8!q>^0gqu?]or;QZp +o);\irVuZmq1X"E-6#8EGoH,1/kT.K; +s8N&urqbpOq".FVVaZPa%K# +>X<\%R?O&&Pa@PuQBRYoDDd;Gr;Z`ps8Drrs8VQe&,cJ+rqufnpuFf3Q]mdpp&=sirser)q9-4P +Yd'YHq>UBnec#pSs8Mroih`6:T>5s~> +%t\81Zbji)`l?*Db/M9>b2LGA`rF3]a=>3I\m\lI>*ltIb0%rQbf\#LdX!U(Odq(df@Imd_T0m< +b08)Pb/VE>`q%2KaiDZCda)E-O>2i^PEqArQBdbtPa@\u3&ZkeQBR`#Q'Wq/Q][W&N&Op#:kKR&)M95JZgFE_\4@NRe-D0MObN5bK@lG`Pom: +]XbKEoC2A4f(7\?`5BL8f>u+YbVPNX1GThpGCYh%);bN4daN2KRa9ou0b/_Pr<(08ULO]~> +&-(JEs8W&rrquZjr;Zfrqr.PRrsA]#q>UEoe*6/Srr;io%K,ttGab"_R96cjrVZQls69M]s8Dip +q"n3@13/GCRZEStQ'.SuQBmbf5$sP9P`h>rQ#eUAQBI]$S2#SLFF&IlK8k_L3gNbaiCQ:P0Or;Q`rqu$Hn$Ld +#JHk?pAP$irr)j!r;HWphu3ZTrr2p)qXC&UXg70erVlfqrVuj(s8)Y;R?`o$!cGdYHH[:$dH?sp^GC+X^Its,KP`3`"_Sa%#oD\dh +s8W)piTU=?UhQPfD0gehIt!$)IWSsj3__JEFDGZ6F)Gi#?&k;@PESk*Q'mf#Q!HG:Q&q,oQ]m1X +BoeLBQ2[-MPQ-mZQ^3Va6fmq>rV-86kofrr +%t7o+[):#+aMub2LGA`rF3]a:$#*\R/WG>FE7Mb0'_.)pHCE`R%_@N/a3"Q-R[t_oKd; +b0%rNb/VE>`q%2KbJqHBaj`Oi/8pZ>RZ3GrQ'.SuQBmbf5$sP9P`h>rP&;h3QB[u.ShkqNDfg;U +I#*T0Ll.CSK7@DoA8?.%GBe4PDJ#aiVK8j7WHEr;Y$Q^rFI7^VIk1b0J2M`Q,s4\EN_Xk54uS^rFF9rlS9ec-FSU`l#d7 +_niq[o_%n=f_++E`5BR"iEbCX&p2Igl[LkC;2JTYX)4tT&RCO:)?F`M>6=]pWJR$3e6OHl5s +Q'GW9QBdPoQC!ej056K3PEl-6s-*JJ&snoDKfmW*eAf>Gb0%fE`Q$!AnAlfoaiVNHd+WbPNfAfH +0u_AZ`l?*Bb/:Z9<_Gt[8rLF)aN;uR!QN7\ap"hH77L4*J,~> +&-(MFs8W&rrr2fjr;Zfrqr.PRrsA]#q#(0ldd$)Qrr;fn&,Q*u4B[o9KOXiYrVulmrTX;[s8N&r +s7tnGM3=0iNLZB!QB@Q#Q]d\M/!5mYQ]dZ%PVI*GP*MGuNIR%ZO&S)#K7eu8LkgS4JTF@c8N0CX +Ec6,EF`_Y"Oel."g;D"SeboCGrr*Q1qtTg#LO"#bG'%b;7Ks)"!!*!!rVus%!W2p)$:/TIDe(j< +!s8T&!!EB*"pY5-!"9B(DKKo4?P!2C!rrE$!<3)s!"0&F@;^F:IZN"MrVc`p5QC':nbDoN-\$Q! +It`f6Kml6]O37+Q5C.0mEHl8B9j^/AQ&grmR[9;+PE^um/:a4UQC*quQUOo:QC!f$PQ$jIQN*3T +Q^)-BpA4gdr;Z0`('"11s82Zlp/$j9Dts&$rqucqs82`nrsJ;\nac8-qZ$Qos3prIrVlg"jmV^* +rI=~> +#J$P=q#: +&:%T'[DU5/a2Z-AaMYp@!oGo)$#.bKJ#Pb/_oQbKS&H_Sj@2 +_nQ!@q=Eh&oA%O-_oBpR`PB=3eN"gtGD1R%N.HFs/3%dWgW;*hBln'-@;8).3.d6POI`#3Q'[]! +MjQ&GS!K?JR$E`!79,J9Q]m`!rg!MLs-3tYQ +&-(MGs8Muqrr2fjr;Zfrqr.PRrsA]"q#(0le*H5Rrr2utrqufqrVm?#b_Is=QqcZ8s8Moqqs")/ +s8Dutp\B_ZOHGfqRZnI>Q!!!'#!WE'%!WrK+!r`0-#8UikI!1(#eFWY;rVfRL +m.()H3GF/'K8P>2L-SK&o^_kVs6DXoFE:eX8"[)a?BUeGO-#cpP*VK$Q>/=7R?`l!QBcbIN0fct +PEh>uQB[]#PEh\s8+uoYs8)Wmn,=(%rVulqrV=9+R4aHks8Vomrr<#qqu6U&p#tr;o&99Arr2rH +rWrK%s8V3Lna$+'~> +#IU;=q>UEmrVccrr;lothu3ZTrr2p(qX9fQYdj<%rVlfrs8N&ss8NT&c%[m5P=XX(s8Mrrr;Z]o +s8VWg6N-f]s7uPLAr_tXClNZ(tWfR[8tmR?`r$R$O%Q2Mmie +OHl,sH7Y#APa[f$PF%T"QBdZ!Ru0]0s8N&prVc']!WW&qrsJS;9pI2bo_ngds8N#trseo&qUr<` +ZETn;q>UBnec,UK#lXc&oVe@PS@="~> +&9M/u[)1&-a2Z-AaMYp@=;Fc,e!'aqrCI`1pueMN'L2cI0kPaM>g: +b08#L`l@Pi;Td))b/i.V>`Y/:R$`hpR[B>'QBRK#78/`0QBmc(O)?S2PaRl(S",k/OGnos;DB[f +Edi4aHtPRqhq[=F.nrl,Vlair/ieD9WaoD8Idm((!H`5fsua8X$Y +a2\(rs2P-+rr6`e[D'Q!`lH->a2c0@aN2B=^Vn.-o)/@aosqc[_SX=/`QQ?It'43dEp(beD&7oDIR$I0//9UPu*@*RZiu*Q'.H#QBib1 +2MmieOHl,sH7Y#APa[f$PF%T"QBdZ#TSPGTcH")Gb0%g)`W=0'o#N*!ccF/Haj/A24Gb,&e^Vm\ +aN2?>aND`H[t[mV8l&',]u\::e]@d1#fk*6\l_Bk:Nh6~> +&,tPKs8Diprr2fjr;Zfrqr.PRrsA]"q#(0ldHp)Rrr2utrqufqs8NW,qZ$?^k4G$YOSf"Vqu?]\ +r_rjqpA+ab9obO.QBRH'O-YusPEh;qO=#jLPEM/oQ]EhGOHu9"OI;2pQ^O&&7G7J/X\M)nF+[0[ +s7H<`nGdtoAp/066)12P2Xf\@_o^I,rVulr?2sfsoYQB/F`V_94WG(7%g<:L%LrpT#6kMB#mLJ6 +#7s72:b<4e$jd+>"pkG;#RL_>"9nr.!=BQ2Ec4_g%L<:J%LNOR$NgV>#R_.R$O-tN+C]M?F+0+u +oDSXdr_;VAr;5bCL4P/.J;K+YkO\KKs8Dujs&@]A6Z'g8r:TJISVNf&R$uQ&q87]`7a'qZ$!_s8N`-r;Z8-Jp(;lqYpNpr;Q`rqtpBm$M`fRoCCu5 +s8N#tdJj1G"QSq>lhc2~> +#IC/=qu-QnrVccrr;lothu3ZTrr2p(qX'TNYe'W-rr2oss8N&ss7m'&p[RA7P\?$Nr;Z]os82cq +s7-(hrVQ?es85ZPQC4#%Oe@\uPF.Q!P*D%TMNj*NU^9NiFrcK<-GC+dbG^=^_G^4X_GC+q!Yf+8TG^+@OG^Xm\H@'f5GlMpsGBe^mLqU^D^Vdk[ +s8W&sr_NFVo)J^R4+i'=H@CX*9]kpAs8W&to)2>669f;*p&+OO7@T%%PEhB"Pa%Q&OcqSOP*D)p +RZ`e'MNO*fQC*l&Q'.DuQ'I>m=hXk3r;Zcpm/@Ils8Vrqs6t&YF'&,2rVccrrr~> +&9(ot[DC)0a2Z-?`kfR8bf\qds2GPkaN2B?_73ZV>@OSOcH-"))p$1Gbf7QFa32TcM-+iNd*';I +_SsU=bf\)L`pq,JbfIN;dapO9R%Kh2O.2/pPF.Q!P*D%TMNj/P*2#nM2=./ +gXVBC.oT_g/^i=qf?_[u@j#-$FE0R:TM-ind'17%^Y$a/`_ORrbgG=th=0t+q!6AA^U;(o^:q7o +^;%M#_SX4*]tV;!iq_f\ZF7l,#+i]g=Oh/Ne*aiSuQ^!_)S(,?nbf\/PrlG,^o#N*!bKS&C`mW"1Cgo`a +dEg%YaMu3 +&,tPLs8;`ns8Mokr;Zfrqr.PRrsA]"q#:^AW?'e7Zrr;uns69O] +s%iHc@$6buPa@N!Q'[c%M+N\sPae#&PF=\-PF%Q$PEqT(Ng-,j;tp%dqXWNtA,,]pp](*h +s7uWAIW9=>Br6DYEq\8-bJDa,rVulr?2sfsoYQB/F`V[m0fUU8/ho+<2)I'C/hSY,0JG+1.PO2) +6p!Y#:-_366UO(%69m[s4?Ykk5=/"FG&g>M6V:$G5=&(=6VBa46q9mE8OPKu5\>+\EHmGloDSXe +rY,=pjmW59>_J#aLkUG/E;BP1s7cNm.Ja#8_s8;lor;Q`rqtpBm$N&rSoCD,7s8N#t +eGfLJ#QF]%ipZC&qg\~> +#I'r;qu-QnrVc`urVZZphu3ZTrr2p(q/s7cNm%eotom/$VUs7uZnU10ONrf\.bR$*_rS!&;cQ^X;,P`hDr +QBdetQBda7Pl6pJPQ,h/o)Jahrr)iqs8DKe,PqH?qu?]b7u]=Kr;HKiqZ$Tprr<#qqYT9ZZF$m2 +](l:(s472Jrs/Q$q59mWS@*k~> +&8kcs[):)0aMu6@`kfR8bf\qds2GPkaN2B?_6m@X_Rc,fn')90hDcd'kW`R`Ue8V&\Je'>qF +_SsO;bfn5uaAU70aNVeW:Q1[0Q][\sPEq>tQ'[c%M+N\sPae#&PF+@tP*VH&Q^F5.MN*dF6Jp/< +dEK@:6.3fu_90^=cH=\EDes?(AtaZJB'.4FaLo:?bPoLfa2uQQhpgKOnaZ:k^!+OEd*g4`bg+Vb +f\"Tue'ZFabKA-$dEL+qleUCOhV[/Fhqlu8e(*%&gYCN=naG&ahObfe/UaO8^n9oY'sQBdf(P*M,u +P(8a_R[BA)P*hAtQ'[PtQ'M?5rg!qZS +&,bDKs8;fps8Mrlr;Zfrr8IYSrsA]#q>UEocg0cNrr2utrqufqrr3K's8W#i?qQP:]_qX+r;Z![ +;Z$Lcqbr?8Pa7Z(Occ6!Q'@JtQBm_sQB@VqPa[euR?!l%Pa@PuQ'.Z"O^#Nqs8D]lqYBp]s7H?h +rVcKhqSb(*D*P,!4Dn8Igr7+JeboCGrr-4'qtTg#LO"#`Dfoo.DJ3NlBkqO#BkV'hAnc.#Deiru +<*a-PA8lI*Ec?#;E--/8DJj6(CiFE3E+sTED/40-Ec?)ADK'`9C2.U)EH-#:CM73rF)uGKN7@It +qu6R,qs3JAqOCT?JV8Z+K7uYcr;6Kn3r]'WrVuosqZ$Hmr;-6;:7RQ6R#mMsQ^3i$R#mVuOdD?" +Q'IN#P*V>tQ]mZ$Q^!`"R?F"7eGK1Cq>]s`$ig/(r;-+XHY +#I1#=qYU[ER$2NnNXKT;7r;HWnrt58/rVuTks8;Iq8<,:4qtg?krr;<`2#[:N +p&8!LP`qB!R?k['6:7 +q#:9mf)GXJ#QFYsVk9QM["&~> +%;fo^MJPp\FFRo^;;Jq"Xj_B_M5sqZ$K`p@RhB +hot<4`lZHIb/Up$ce=">GBn^mJq/8$eBPnK^V@S#bKA)Rb/DB=b0%uYZoaG5QC*btPF%T!QC*c$ +P`_;tQ^*i!R?El#Q'[]!R$X&%Q_0J"1TS^?aND`Lb/jS)rl>/\aN"%ss2lS.b/D9=bKqbN:>!_4 +b0A,N`l5s?bfn&;PZ(.G8ka3;aN2KSaSs0ba3)Q?=\).YDh%~> +"Sgp@s8W)r!<2uq!WN,Vrra_UJG]s8N!Ms82T`_MFDl88AScO.DK'W?G^+CQEcQ7#Fq/Y4CN=?1DDPgCF*2YHFT-M3G'.kK +F)cAHGB.eMCi<m.Gl~> +"L+]>q#1'hgA_'N#Q!hQXKB;4r:p9krsJ]'rql;KB05\^rr2rtrW)usrW)uqrq69oqZ#\FPl-jI +Q2d*;Q2d*IPmg1_u@Ri_ns7,_oK^7bK%B:R)d+Z_8F.._8=(g_uIRf`5oa1 +_8aO2d^Pg[`QQ0;_=dj.rVlg7qWIJIpd.9!L44`+J:9uZrqcZpq>^Bhrr2oq!<2ut%fQG%A:K`/ +Q^*i%QBRK"l'2F:OIC?=qY^?srr)`ms8Mfnqu.3+s82irr:NQmK)YWHrr)lrrr2j)rr<#rjL)4t +Z(e`&rVlcrrRh,LrrE&trrr/!St;RYJ,~> +$"R7h\]2e7aMu3MaT'6c_m`ZK<,](]bP]H\b5]Qj_oU'Hb&LH3cHF<.`rF!]`lQtfdFCM%\%C57 +`lQ6Aai`#ddbF9]na5Z0n+$8Gs7@'"o^qSFq=jpeo^MSEnac8Fo'Pc5pAXmto^D21kk4]Eqnhjn +nb)nU3V;q5nalDLqY9pWmcs60m/6"qo(M\=gs#'8aihiH`lGO'bge-rGCb7#M1g(+c-4#DahuEG +a2l@$ar8@@`Q#pHd7eVCS!T>&OctlhR$X/-Q^*i"POatSQ^X)#@9V+,cHFGTb/q]GbfIfD`Q#mq +`rF-[as5-Qa2u3>`lcOV>^"c=bf.WEaN2BBc-=>I]U2\f6qL!f]uU,n!6 +$23Ld%rVulrr0:qYnbW4`rr<#krqZHuqu$Km +qXfq`P*5d.s-DBe,91Err+8EqtTg#LO"#b +H$XXTEGoi;G'J:XG]e%IF*)VLDeEH+Ao_j1Fa!b1(O:1FF*2_QH$4CNC2[ND,$Sr.H?aLSGlE!e +Ft[u[Jp;Q[Dg"l'2g'(TFb5\)oDSO_r;Q``k5Y2B7=T`GLjt567JZu`qu?ZprVl]os8EB'qtL-K +8"5X*P*Q$4!L&ZEPlI$KQLU4JOIVDr9)&&Vqu$Elrr2uqnc&Rg&,ZD(r:NQuJbf6Dr;Zfrrr2p+ +r;6Elr9O"Jo]YW@rVlcrrR_&Jrs/Q's5rY8m.Gl~> +"gOo@p\k*grrN-!huM$(q*o$kVO9kkD)q=B^rF@ErVc`p +!<)lr!T(Wk__]0G`5Ta:`l5j4_84"+`Po^1`Q#d2`/;+C`5]g7_SO+,_o0I0_84")a2Ps>^RgM' +`5;Mi!6"iQ!5nfQ'Z7c*]ZA"+caf7&b/hH;`PM6Is8N#t(B*R_s7PS@I^Kms8N#r +rr2oq!<2ut%fZ;(i'j#oPEM2tR$a/&l'2X;S<&n,q"OX^r;HWorrN#rq>^6h%/^)(s7SlrIJX!D +rVulrs8 +%qAam]>i"7`l?!=aNVfJ`o5#=b5KBh`l#;e=&a1G`QH9CrQ>/])TKh:air#UF&jd8b/_NCaMu3= +aNDZH`lQn]"YP^e]YtTb/_WHceQt'nFQ2;lg*s*rpT^R.IZTgoD/1Tp:KY] +nF,o9o'bl-mIU&Hp@I\8m-=!7o?k/:p%J.Pnac5LmksY,p@RtFo]bo3lhf_YaoD/'l/9eNfuqR] +`Q$-6[ardL/T>ilNeN@aMu3A`QHCUAouJBaMc4!a:?A=c,df:RT2^A84m^3 +a8X-[a4JB5a2S%t"h/#98PNuN~> +$23:3hrr;lqrql`qrXo))s7Yu# +I$p=eQBdVuQBIJsrK[DKnWaWNP`h>IQi$g_rr;rqs8VrcrXSu-rVu]mpI07IpAb0is8N#srs\i& +r;Z]]o_%k>li6q_rW)oKrr2p&rr<#\lgF-4J,~> +"gOo@p\k*grrN-!huUd#s8)cn +pHk:7Qi36MQ2d*5PmQ>)]_rVuoss8N)qnG`Id*rl0 +%qAam]>i"7`l?!=aNVfJ`o5#=b5KBh`l#;e=&j:J`QH9CrQ>/]+2u:Bair&YDc\L5bK%WEa2Z*< +aNDZHaND`Nb/hZD`W!jkaNDZLaM?'If.N)HQBdYrOcu&sQg^1@Q^*j8P7*V@OZUJ3_9C!CaN4>" +s2b,\--XWe/jP2e=_MJ[+5#2[[^is!bJq]Ia2uKMgsXsHoC;;:mI0N;nHnXVo(DqM[CGGjrpU-[ +o'bo0n+6PP&G,;YmdTuBfXeQ[q"XLOlgOf>rpMW.l/1n*m/QqS`k]aoq=3e4cKa*+bK@iEb.#"- +e(7DuJTcj(M0U;sbfRcDc,\#MaN"5%$-:-*`Pg-JdogYgPQ[24Pa@U4QiE?NPOXnLR?`ek=+/cT +c,RcA`6$3Bb4Naeb/hZEa3))[f>n<>`lQ0> +[q&0(9ko1~> +$23Is-Do7#EYL"EW>k7qXOOus7/T^UQJ;jG':aoiP`dQe,91Err*9)qtTg#LO"#` +F`mA#s*-,FEH#Sd&e#RYEHZPNGBe@XH$FLREcQ;JH[9pN3t2N5>]Y19F`DMEG'J7TG5cb>HZs^W +@V$GY&Lpq#H[::*earb9qu$Hnk4/HNr'`bOL4bD>LI'qoqu6QnrVuins8N!%q#: +"gOo@p\k*grrN-!hu,.6UDfg->oA[d2_q+ebrr)orrVm#W^r+(. +o>CUF&')B'b.NXjNjljC`59=,_>_=O_uIXf_nj1&`5S^;NJbHg_SO+-ai23s_uIS,_o97&a1oiX +KTr+G`Q5d+_"Ia-rr<#ts5W_NrV8`@=,)0YJqH8Gq#: +%qAam]>i"7`l?!=aNVfJ`o5#=b5KBh`l#8c=&sCL`Q?3BrQ>/]*Q?(@b0&#Y?iX,T_8sd?aSj-YaSs?^a<8^V +dP=XgND+,$@AMN%]>1hbe]YtTb/_WHceQt'nFQ8AnItZbp\X?h[^kSlo'l,8na>f3n+$DL&G,G_ +nbDn5^:UlAq=X@Km-t#?rTuE,kj7[/s7lW6]#`Clp%Ik5cKa*+bK@iGcaL[9eCGrn8:H/MM1dP" +bfRlDaN;`J`l?+!aq;_7aMQ0RfK1X@PEqN!O-#`orKlo:%?QmAN.R_nV[A6UXL;CV@dfa8j94a9ou-aN2E2A5#QhC4H~> +$23A4RJiBMQ2d07PmE +G5cafG]s(/'6\VGH$FFC<"K-;$q>^Ha3GjD"L4X_Ns8;orr;ZfqrVuiq&cDV)s7b#J +MNX6eQ^ +"gOo@p\k*grrN-!hu(G"(Wprr;urrr2rrrW)uj +rr>3R?dc;s-N3^t/J_rr)or +rVluV^r+(h_u[cmrkSTOr50/a_83n)`km^mQA`;k`P]L,_o)Jjs24lT&AuH"`5/4-NffC>`PK=) +_SuJj1W72K_7m\)_8FN^O-GTscGIH9]tj=@s8N&urq>F>rV6Ein2Y96I"6Wg;ZHars8DutrVlcr +rVc`qrt#,/pXL +%qAam]>i"7`l?!=aNVfJ`o5#=b5KC2`l#8c=B9LM`Q?3@aN2NHaN2B?aN;ZOcUqq1`6HBCb/q`E +`lQ*?:d*9GLoZ7'P*mDl62414(CLSP3 +eCMmN]sG?$bJq]Ia2uKMgsXsHp[e4N&bGblo=MKG[HR8SnF#]7naGrFnc/1lp[\4WftG&/ZKCfN +oCVYGn,;SPm7?dBqZ$Bes22Lc][6TFnF5>ZlIjA(bfIrH\%9c*f\"Uq?@7?^JSdL$bf[rDb0%cE +`lQ`PKC-_oKmAb5KO%c-4DSaN2B@ +`Pos?aiVNLcn0i3eA]SOaNOS(%a*&?`kfBX<^T8H8Sc#[rl>)[f>ms6rl>;a\R\B*9ko1~> +$23lfJ0Hrr;fn&H)P+s8VmrL.^D&s8Doqs8VEa#Q"'g +MjK`tQi30KQ2d07Pm*:uEVTD1mf!i.BWZD]E,.l1oDIXh_oU=)rVulr&c_b'oYQB/F`VSDFa/1X +H?jc6FVAqCG]\".%L*%9$<;.nH@'m^G'&m9-rH?s[RGlE!gG5lgeG:[fP +@NkjR!sg2aDfp>ZOO`t$qYU3irUfIUrVcZlh(Er)Kn4J7mJd(arr;oos8Dp/r;Zfkq=_0qQ^j2% +R?a)$Pa)-3s-E2?&!30DP*D)SK`;#Nr;Zfqs7-(#s8N&tqYohNPX>-=s8W&trr2oss836's8D +"gOo@p\k*grrN-!huZEV]G/s7ZHks8W'1?tH]=LInb)qBai)?Q +rVc`p!<)lr"5^i2_u@RY_ns7*^qmnc_uIRb_90TMO-brk\\lA%^VRq._u@OO_[XE'_l/NLOctgA +`koR0rkeZNs24lR._ig=aiKs8PEh,tbf@Q8_8,aDs8N&urqc0Qs8;fkr8'#JIt2of54/CKrr3#s +s8MutrVc`prsnl$p9dkaRZEZ#Q'df%QMm*KPjt"DP+%`%P`U*Brr3*"s8W#orr2lprr)j,rqufq +rVu1NO$EF9s8Vuprr;p*rr<#tk-hP#Z(e`'rVlcrrR_#Ls8N#t"o#0kSY*-:~> +%qAam]>i"7`l?!=aNVfJ`o5#=b5KC0`l#8d=B9LM`Q-!aN;`Uc8To,]["Xc)F?BO3+0`mi,B^;%;1bJq]Ia2uKMgsXsHoCMVHr:BaS&G,Spo=)EM\%:ksoC),8o^`"OrpLBf +oCW(\iPN+>]X$\koC_hVoD\:Zmn!'Hs8Dlkb.kU'\B"F>o'YP`lIX5&bf\/S`4XC>cI(:Z(i4_X +Jo<6adE0JLa2Z3?`lQ +$23Lj$s8N&tpe$ZAf_b^Ir;Q`drqud$rqCeg +Q'@Q"rfmAHs-Du9s-^0fqtq,o9W+RUE*uW8s8VfR]Z@_6r;HZprY,>/q"2FYH$+1K +F*2_QH$Xd^G]s%."EJ%[;m?R$*>#mf3=^s8W&tnG`If$2sf!j[b\*m/-_[s8W)ts8W!(rr;r` +p%._8l2U_]rW)oKrX/])r;6Hmk3VR(p4*~> +"gOo@p\k*grrN-!huuL7ns7t`i +ah64'rVlcrrVca-hS@%H`5KR2_SO((_8=(,`;[Xf`5]EMNK06nOL`6F^qmn-`5BI0rk]Dd_nsEr +OcGQbOc.#p`5K^8`P]O,_#D.h_oB[3`M/HZP`:or^rXO/_SGjEs8N&urqtO=rr3E&r:lCXL4Onp +6_!rLrr3#ts7lR)qu?QnrV'?CRZro"PF%Q#QMm*KPjt"KQ^Ni&R$*>"mf3=_s8W#rs7cNks8W$, +s8Duqj@,4tmJd%`rVc`ps8 +%qAam]>i"7`l?!=aNVfJ`o5#=b5KBj`l#;e=B9LM`Q6'<`l@tu29*SVbg+V\.Y0^jbf[uIbK.]C +aNDZH`PKC.`lcNMcd0k[aMl'5`mJ71LQ@[gR$j;+Q'7F#Q2d$WOcu-"S=,@*R`rskb/jS%s2b2^ +s2b/Y+iqpP31l^/C/dCDe^;O\Yf*o\e]YtTb/_WHceQt'nFQ8No`+O_nbhkUnI5$]q!>f0]"Z+o +l1OWHmoB#MoCMM@mHsH8p&!df^r*t)^@M'knaZ,>q"t$eqXj=Jp%7\@bIkd2[_'NIqYK^Fcg'-* +bKS,QdBL:-c-FGXdWJ38LPKe+J'uH[`l?'>`l?*BrlbqraMl6?eBuT'GFOk[QBRZ#rf[JMQ^=,, +ns'rRP*:iqRZN%YbgjqP`5T[9`l?*@b5KQaaoTT'rl#;db07uMd)\�#brPrlY5]%a*&?`kfBX +<^]AJ9Q%Parl>)[f>n +$23B3i_T((&rVulr#QO\roYQB/ +FoHFoF`r"SH$O^]G]n.JEGJ\p#7(5.'*M9TH$Xa\F`2>DG'J=ZH#d^]&,lPr!sB9=D/jQ:DfKi@ +G]n4QG]d_0lV,TG']5"oDSO_r;QW^mdpJXr;QKfBP`HVK0M`mqu$Els8;osqYgs( +qY^Bhq8HO"R?j5&Q2[$HQ2d0@Pn91GNgPunR?^:_o)JUes82icrr +"gOo@p\k*grrN-!huQ'dc#Pl$aGPjFYBP`q>c=82aos8;ips8)`mrs8RPI\pjcDF!^jr!35[`kodH +rVc`p!<)lr"Q$r3_o2Plr5'Dh_SX4/`Pom7NJEa_PF%9=_Sa4,_T'I2_o'@j_%ai3X-8O(PEUl_ +_TB^5a2c9=_8!eb_D&pIbeop+Oct`fMjh`'_o0=*p&G'ks8W)olgXWJrquflqIXDnIXaIGnGWCd +s8W#lrXf,.s8Vun`E8+eQ'RPtPa7X6Q2d*>Pn97KPF7VuR?^=`o)JUes8)`ns7uZmrt#)-s8;oo +ha`f!irAlTs8Drrs8 +%qAam]>i"7`l?!=aNVfJ`o5#=b5KBj`l#;e=B9LM`lQ0=`l@tu293\[bK\AV,DSCdb07lHbfIfD +aNDZH`PK=,`Q69Jcd0k[b/VE/] +s2P)[+j/2AEi!PSAi&)jbJVTD\A5qoe]YtTb/_WHceQt'nFQ8NoE"RRqsXRTs76Wlo"MWN_8!dr +lLj`ImniWDoCMM@mHsH9rnYN-_8XC,_=mftlg4$1q"t*iqY'CFmdfSTV6R_C]X>lXo^qh=cg'-* +bKS,QcG732d)sGTc.Gm#KS4k3/C)P^`Q$+#`W=0'rQGepbf7fOb06VRMO'HmOd210PQ.!LR/iTD +Pn]:KQ'7Po33CKVai_`B`l5j7`lQ7"b5]Q_`Y$20a32]Jbg4?iL,-9``6-Slc-42D^7&(l +77p6k]uU,n!6<^4$HpT7aN:u'9LhflJ,~> +$2!6>s8W&rqu6Tnr;ZfUrr;uurr2iprr^[8lKJ3HqYgs&rqucqrUN:>PT0mO1;t^+f +s81`m_o't$rVulr$NL"uoYQB/F`V_LrcJZnG^4^[H$O@ECfD#T#5nN7$WhCmGBn=OEcuVIH$FUV +D*/L1!%e9a'j,BtFa%nJG^OORF)>u?FD=/s/LMbU%1k@7EcZ>NO4Ek#qYU3irorhIrr*K,s7^J" +KS+tCJ9Lefs8W&tp\t3jrVHhlA +"g=i;p\OjcrrN-!hu2D$`Q-*8P^n::N/R$s*5Q2d*>Plm>;QBhB6%S4S8qYp +%q/[h]>Mb4`l?!=aNVfJ`o5#=b5KBj`l#>h<)n+I`Q6'<`l@tu1<%8Sbgb"[,)&LRdE0DObfIfD +aNDZH`PoX4_8jR:aNMKLbKJ2KbrBQuO-Z3#S!f\2m$/6OP+7l*S=G^-WQiH%_TU-B`Q$!tasG*J +`6ZEIdC2'.S3gpp5hXZ/a3;K4_nWn8bJq]Ia2uKMgsXsHr:BsYs6jdknETK7nalM@\A#_p_8g;LS?_nX()\aK+Zkj.R%oD%bEo^(u6o&R*COHQ?9[_:&bo^VG6cg'-* +bKS,Qd(@$6aMu6@d*>QEI"6f-Dd(W7b/qZH`l?*BrQP;as2b5^$-UhU=H&B(QCjMDVBgda5tPb/_68`kfR7qT'5faN)]J_9gK:5_pW4`m;ZLrQ#Gjc-4/A]p_ql5tjso +^!at/b0/&LagtkS7nm`K~> +$2!6>s8W&rqu6Tnr;ZfUrr;uurr2iprr^^9lKA-GqYh!)rVucfqu9

UEls8N&u#lakoo'Pu2 +li5?2s8NB%s8Cg;m-O]NJ,~> +"SK^.Wpp#fs8N#ts8E#sir/uYrr2rrrWrQ#jI**Thtmajo_eahr;ZZfs8N&ti5W^R +ec#IGrW)oqrrLuloYU[GrPK>f`kmdkNK93`_SjF1_8=%+`5TX3rk]8bai1KHLmOWkNlT&N_oKj: +_u@OQ_>h@k_njF6Ru*,jR#dlY`5'1+_=dj.rVlfpqW@;IrVlfr!<2ut&,uV"6@4N>H\Hrp5\?$H +gAV'Lrr<#urqud#r'?L-QC!foQ2d*HPm3J=Q'7S7]`.p?rr;rss7lQms8MuprVl`p$iKu&qSKYL +\>-D+rR(TOrVulqfVG44SAG)Q~> +"l>\P=g?0Lb5]Nc`l5pf#u7X`7Z`%Od,a. +!13YP"dbL6boA73h`6#U(q"F4Hn*TE-mI1#D47i.=rqt0V]Z%e%de`k5mc;^\f?)(S`lcZA^<+O=aMu-8 +`lQ"hRfNTbl~> +%0,kYiW&rUrVlfor;Q]qiVilXrr2d&rr<#ss8UU8kk>&Vq>LBlrVlis"o\K!s7cQlq??nk?^?kA +rKQ-'%[NBGPa?cCp\Opirr)irq#2$,rk;gCO$GooF`2>?Ak)[tq#16jrVlisr@ +EH?;JG]n=WH$FINFCdBE$O6Y3#<$FFI<^1"eF`e>rr2p"kj8*Drr;rrrVR9+o6tD&MM>&,?"dLn +Q!4"]r;HWps8)^#r;ZKe6C*t%R,O>0QMm*KPRX%GOaM6Aqtp +$2_o>V!%aTs8N#ss8W&urS[\Rs8N!'s82>TX0^OUrVc]qs8Muts82d+rVucpq#C9irVunn@[E:F +rKdJKr072Gs-Di5$^R!CIph[~> +&*0Ni<2IUSbK@oG`l5s=b2LG@`XU#3b/M?C`O&K>=-:/8rQ"uYprEuc_8O79^WOaC`5TuJ;3Ht: +Pm`qHR@9P3Q^*bsOd#@$rKI8Hs-H6EQ^3\nDeRhIa3)KDaMu-8`Q$!Abfn5Mc^dk/ME3[RCM@[$ +>W(Ei`lH3BbgFhY`5g0A\A,\ge]YtTb/_WHceQt'nFQ2KmJZMPmJlWSnau[q]#DJ!_6rh*oC2;@ +oCVYEn*fZ2nalM5_7IIs]Y_>Rp[[e;m-a90nal;@n+$)NqprHqa1]=(g&1X;n`A*`f?)(S`lcZA +^<+O=aMu3<`r3sjbgY!S>Bu!V:ek2(=<`J_d`VR/"3S^(`VmgU`).[??d +S!/V?<6NG&_o0L;d)sGK`lui\dE';H`l5sAbf[D&7Qa46GeD#8d`;d;cG[JV=%l:jQ0a#~> +&-)=`k5G>[qYpEir;Q]qs5t8mUF'`n\ +r;-C#s8Domi5NXQeboCGrr*9)qtTg#LO"#bG^974%!d/EH?jdTFDtM!#m:,+&dqucGB\:UEH68K +H?OR^D/Mi_r;[W9$Q!5pH$FLTF``"QG'.tRDfB,F'FG-F!"9/kCj:ARIuVtMs8Drrrr_KOq#:3k +rVlip(B4/j>_\#fItiH/K7#mkZ2".ms8;`nr;Q]q&H;V+q=Kl9QBmf'OI2>qS!*K1"IG:/PaV?4 +rg*SLs-*ePQBIW#OA>HGnbrsqs7Z3erVuodq=3h%rr:CF$NL,)s7YUBm,e9IJ,~> +#l`JKT%s5BrVlcq!ri/si;WZSs8<6$qXgP_Y/gP?rVlZns8N&sqYgd#rr;h;H@_9Hqj%)Drg!ML +#EbF?OI)3!Pk^ODQ2m3KPR3Y?QB7DOq"t*kr;Q`r)#jI5qu$BlrV]H]W((K?>!m+DDd7AZs8Vuq +$NL,*rSYZ4`7=hbrr)orrVloT_#;1M_Z.IP^b!7:_Sj:3aME^lO-,QgPE(s8W#ns8W)ts8W&s&,Z;#oT>E)Q^*r"R$Ni'P4k.DQ]d5jRJE-I +PQ@&8rg*nXPF%Mo:A+Ydrr;rr!<;rq&c_h0rr2ros7`q,[C2UVq#CBBrX/])rqa^3T:DM)qg\~> +&*pZ/;OP8>aNVfG`Q$!Ab2LGAb5]I!b/hQ@`l#;m<`XR]`QH9C`l5s;aN2NHaN4A#r5eo\#Kb?= +<+gK=Qi<3KPlR0KR1,FIPECugR?ir$PaI^2PnB=HOcYWdQC+22R>Zi"d`oqUrlfl5`P]X6b0nJU +_oB^C1m+\)A8+t-Ecc+l;p^Uh&-bJq]Ia2uKMgsXsHoC;56n+68Jp@RkC +mI'?5p@>i2_SX4-_7fI6o^MDAoCV_Io'Gc9mJ>kb^;\+0`4rtYp$V55lg=0;p\4:Ln*Kf?f=eou +`"g2,^tAV[oC(_cm+9A"aMuBL]tVV2rlG,^s2b2Z0ZqeoFA74=M2..HM1Be/:R02F`lZqS!*K1"IG:/PaV61%?lp9OHGlpS +&-)Idm/6n`rqu]kqu6Tps5Ld%rVcTN`Pf^Fr;HZprYkh6q"2FYH$+7QG]n4PG'J:WI!KsS@q$>`"8i-3$X[t!GBe:OF*VtQ +IX5pP7Ml=-!#>_I(L_E2G]n4QH?=FKH?X+IA2Ol)$2ji4#rc[EI!L4$eF`e>rr2p"kj8*Dr;Z`p +s83Z.oLjafK8GD7J:<<. +"omSRT\'&>rr2rtrr;rSrr)j,rVZZmp8G!n^\ds.rVlfmrrE&srVZX$s8Muqq."o$PEbs0rKR2E +#Fgm+EgDiRQMHdZOcl#sR@'>,OHtus2q%[(s8W&rrr**$rqcZlr;Q^.qj_f1FB3]gNM=G.^V[h'p&G'jrr2lmk4&EM +rr2irrqud8qt3U4J:E<,I"$HuKN2b]r;Z`qq>L?nrr2rtrVlfr%/3\bP*V6$Pa.ArP*l'1#F(LA +PEJXlNqnt?PlI$KQO0%GQ'.;pO?rU?nbs'ts8W)up\a9`ZF.*2iqrc'rr +%.:u?aMkf%='0FN`QZKG`l5p:aN2NHaN2B@b0'\($-('0 +dF(O'F-DphP6.#9Qi3?PQN3L_ns:: +c-4YVbfP'=Vfc0ZB5MU6G&Ls3b/VHFbfIcEaN)9Cd`Au6]"?M9`lcHEai_rbdbF9_na5`6o`"Un +o^VJ=o'l)@]Y2+trkrZi^[q0knF?)?oC_YAo^V>Gho*=NcH*o?^V':5nEoQ/n+H8Ln+cM9p[utF +]u7e)`koIBqt9RLj3R)DaiDKBb0[i9ai_]Gb08)P`l?!;aNDa\DL$bnP)G0FK7Hd.3S;]uaMZ'B +`lcHD`Vd^cbgP$*LPh7USWoD%Q'.O1Pm +"TJG[m.C;Ws8;oos8N&uiVj#\rquZgqu$HugZ7h^s8W#prquZorr)lnrWi?!oK\:dPEta(#Eb7* +2ij&fPPLIUR$X)&PEV/mSW9,)Q2gRXrqu`os8No7s82fkrVcqlfh"8i--#rc[EH?ajteF`e>rr2p"kj8*Dr;Z`ps8;oo'Qe#A +N.6M;Ko1D6Cf>MApA"[fr;?Klr;R9%p&+APS!&quPa@])OI,d.#F(F7Q"a-IR/*$IQN*6MPlHmO +Q^*r!BhIOco)AXg$MsSurr;W[q=O%!rVt=F$N0o's6&J1lJV^BJ,~> +#lj:_Uq5`:5Xn +%.DJL=bs`-aiqoH_oVi"ilDhVbf[rFb0%`DaMtr4;cm\8_p$9ErPnlYs2tA_s2P)]s2b2Z#ft-7 +d3O'eQ(%];pm1uGrf[SLP_M,XOI27-Pu=$;PE:iePEhi'P)+;6^W4@:bg"AS`l#[9`QH6Cahu=` +R\N5jG&D#:F*MIr(!=P9bfn/IaN2E@bL4JB^Uh&-bJq]Ia2uKMgsXsHoC;;r'+9b4E[d`ko[;c-=2DO@qW! +:0+^7^W"XB$I$Z4\S"Q2=(fG>J,~> +"TAAXlK\BKs83&tr;?Qos5EtWrt#,,q"asirr<#ih"'D's8;cnrqufqrr;fn#Q=Si1i&2q#C?mr;HWprtkP3p](9ls8I\#WG?-hF*`+LG^"0s`q]B/ +$ig2(qV]?2`7=eas8N!*s82T`_M! +;g!YGJp`0)JU_u +#laOgU;Ha's8Mus!ri/siVrlUrVllrrqluscCM2Ekl(JZ!rr9!rr;cmrr36&s8MRGClahMPm^6is8DutK6sG,@W67/F_Z#>Da_YCrr;us%KHG, +s8N&si5NUPec#IGrW)oqrs.Dr_o'F2`Pqhs#fXa!_SF+2PDB."#a1=2OHGR>`Pqho4i,"T_8aO. +Z&O[,QBRGlPEh,e`l,^3`Pf[0]u.V'^r2FmOI;DrQ'd\rNMFM0_8O:.p&G'jrr2lmk4&ELrr;rs +rr)j7rr;@aFFSplH\$p&IsFMX7I:'So`+sds8W)ss8ET0qu?]qqTqWpQ^F/&Q'Rc$q3;/TOHbke +KTPtos-3PKs-E\O#Eb@1QT7pqqXXUrs8Drss8N&ue[DfmZD.$id/FCPrVuYrTUhXInGe"~> +%.Mq[>(*uuaiqoH_oVi"iQ2&As2P)]s2YYk`P74'>%+/Cbf\)L`r4!Yb5]Q_`rF3]aSs0baNMoS +)IA;hS+W-HPm^<=aBce!&VUhjag +E-68:F`M(JV9I$#bfn/IaN2E@bL4JB^Uh&-bJq]Ia2uKMgsXsHoCMtOrpTmS0(8;uoD$56_SEt( +`5BI,]C>F]m-X6/nal8Cp[7"O\A5tu^qR\%\[_;-p%._Brp:p"me65E_RI7t_S*h+]tV,.q"+%C +iQplBaiDKBb0[i9aiaV(s2tA_1WIDRaN`A\3F[&WH[1$eF`hdnRl&C=`k'1=`l5sBccsPM_njU? +c-j$_Q'[l*Od26!PkgRKSWK(u3/!CePn'.GPECukPFR`'0KH;Wb4E[d`l,g=c-"&@JOqmh9j>9E +_8apE$HgK1[TuO$=_l4LJ,~> +#QFb_kMuUBrquZqrr<#Wrr2p!rqH3brs.!Fjo,5Vr;>s]#QOPi5s1BsP4=eBP`q8^:hlA\q3;)I +Q'R`#rfRPLPre^=qu?]qrqufqs8No6s8Moqr:nr=XIM_"G&h25GALhVp\+X_rX8c)rVGHk`5L4( +rVulr$30ntoYQB/F`VU&EW:(ZFTul)D.+7g$iBu;!t*!#,G6!!X3I +Ecc;@Ecuk_FEMeE,mabP!<3*/$o_sDG]S.heF`e>rr2p"kj8*Dr;ZZn(AIh.<[fV78Prc;KSFh6 +Xc@W%p](9hq#1g(q#1!`qER*"Q&_,qPaIX0Pm +#la[qTt'pqrr2lr!ri/shu3WSrr3?(r;HKikEE'Si;ETSp\t3mrVl`p#5RpX4d2+mrKQu?#F(F8 +Jl$3LP51@DOco^0rfdSKQ8eR:qYUq_o'F2`VmeV`PfU/a2NpkP`(]gOcYWbOH%)q`l#^2^VIt)a2G', +OGf3bQ'@GnOcbXC`PKL3`P]L.]>)D,ML:DJPEM/oOd;,gUW:6g`PfUcs8W&srr)]UnGi=`s8W,u +)>X=4<@KJ47o*?4K7nJ,WJYiqqZ$TjrVuosr;cirrsnl)qu$:U3/a&^Q'RZ%P51@JO-Z,!H\I`N +pltoFQ'M<6#+1IBQ"3/[n,~> +&+\Up>]dNlaNVfG_oBd?b2:;?`W=6+rlPJ`^S4,$?^/D*aoKN]b5KE]`rF3]a9or+`Q$0FcmLK- +Rf&TRQhQaMP`q8^:hlA\qNV8OR$j>-OcTI'#;5&kb/V<"+_ +<\3"Se&]PQbfIcEaN)9Cd`Au6]"?M9`lcHEai_rbdbF9io.'4rlg!d$m.'uK^q\%-_SX70_8*dq +md]`1lgF32p?_\Pj06D2]thFu^Abbr\FfUip@IhAlgEs(qY\4:^Vmt'`507-]t)Sbo'u,-cg99* +a2l9Cd(R08b503Z`\GQWa4&g%%i88+6;D'2F`0%4JRHmCb0%fM`Pp*LccjAG`4a11dG?cAMj9iQ_oC-G$HgH. +Y?4Iq>'AEcJ,~> +s8N;ijPU";rr2in!WN,Xrr)lsp\XsqdIHPts82`lm/@4aq+*o9R$@-+s-EnTP^tuQRJE*XR$X&$ +PEhH"M`X[7qtg?jr;ZcqrtbP4r:g-_Cmh/(Ao1't4$5bf4]qCVqYC*us8Domi5NXQeboCGrr*c7 +qtTg#LO"#`EcQ5DG'J7SFDYP/!WrE)r;[K4"U5NFEH?/FH[C'VIV):a"onc(!##J9!!a9IEcc;@ +EcuqZHuj-[$3Bu'%KZ_@2f!MYEd3Pmo`+mhrr3)am.gSXs82d3rVuKfq1jF7]8flS;K#$hV,l%E +rVcHiq>Lp%s82`d4`sOOOIDK!PE)40Pm#pQFS!<`6s-E\MrKIPNOckc)Ac)AprXAi*s8;co +s6&hDp"e^-s3glHr;Q^#h +#ljh.TWn1hrr2iq!WN&Urr;p+rr<#sr;Z]kooZ8\fDY^Kp\k0mrVl]o#5\-d6BIIrrKQu?s-EnT +P^tuQRJ<$WP`q;rQ^F.u4A8cPr;Z`os8W)ur>GY5s8Mlop1fnf9[\%fQG-rr;f5[^EQA^A%L&dJaLQrr)#WTq@pcpA]X~> +&,,(7?Ym*daNVfG_oBd?b2C>C`l5s?rlbYna1\`J=C-ftpr`QV'#r#8b/hTB`Poj@bLJ4'NguT- +rgai2KM-#61* +OIDK!PE)40Pm#pQFS!<`6s-*;Gs-3bF6qYnLb43O`aihiK^o:j75t+:l_SX72d`;d<`knKO +9i55Qg%0^~> +s8NQ'i7e52rVl`orqu]ns5EtUrrVrfrqud!g[+=ts8;fprr)lsrr;olrWiK$qK>YlR$dE/#EY@; +QCKR"OSP.PR$El$R?rqf6#1L,\b?%&rVZWn(]XI6s7u]2>HNZ]92oW`n%;fOp\+L[s7uX%s8Dom +i5NXQeboCGrr*c7qtTg#LO"#`F*2_TI=6EaCLA4b!rrrr2p"kj8*Drr;fn(]47/ +qYg3cs8MTho0RuOA#uA1o_\Ocq>("#s7lKf;He!_;+G)ZR?O)"q3;2QFARIMR?*R-Q2d0IPm3S? +Q#KTep[J1^rm,[j>s*t~> +#lje?TW.DYrr2iq!WN&TrXSl*s8W)rrVu`gT;Ag9rVccqrr;rsrr)lrrql^"rq^e4M3XA)PkUFL +Pa.>uPEq^k8s&h5s,mbUR?rqf6#:L*\G-""s8N!5s8N&ur;XQ7W.Io<;J=tU]33VXq#1*hs8N!, +s8N#trr;rT`P]XErVc`p!<)lr"Q$r3_o2Pl(W!o&_8F.-MfsPtG`e)NOcYTaOcI8s`r6\@&]=f(f7BdJaLQrVG9ET:V^qp\o[~> +&,>4N@UuRU`QZKD_oBd?b2LDU`l5p:aNVlQc,mW*<`N:I]ZA1:b5TWbb5]Q_`rF-[aqVq:`l5j7 +aj&==5_Y`"R[TY4Q^*j8PQ-pJQO0(HOI;;uS1[EqqNYKUS=,_0Q^*M^5A"FWRa94"aMu3<`lQBJ +ai)';a4R1ZUjbcj4&!Q?SN!Una3;]Q`l?6HaMl6Aa2Q6MaLT's]@G*Eb0%cHb0Skkg[Oh+o^hJ9 +lK[d/q#8II_SEe1a8X%K`597!md]`1mI'K9p&"?e^qdV%aMtm(_83gu](Ysmo^VD;me$)Hd^I!1 +]>Vn7aMu'0]t)SdoC;A3cg99*a2l9Cd(R08b5TK\`r3t3ajSG[eBuaug!AU*,A"AcUm=Nrai2QI +aMuMHHaUUo#UjQ% +Eli8]q/Ct6UsY3`5fjEa9p,1]p;Vk;dl3QoRH~> +s8NQ-h:DW+rVlfrrqu]ns5EtUrsnerrr;lqrr;6HlK\EKrr2iqrr)j"rquZjr:p6oq8Y+=Qgg7F +S;Wc"O-+Tpplu>RQ'db_=[$\EF*:LUs7uWkrVdW3s8Milp.hK^=/.nSp\t*brql]nq#C*arX8c) +rVGHk`5L4(rVulr)?9U/oYQB/F`V\MI"-ZrH#dO1!sS`*"TSZ&!"B#??ZL71G'A%LE_d8NquHa( +.K]_P!!sC9f +rVQNks7l:leb3p>VJ*@6q>^3crX\r&r;1Y1HYmhHBNIHXQ^@K4#CeIWPaR_tRJE-JQMZpPQBdV> +DPdC5nG`=b"Q]+En'q@:!WN,Hrr +#QOVGTqpoMr;Q]prrN,tir0Pirr2ios8W&qq>^BdYbS>9r;HZprr;rsrqufrrqud$rr)]5/:NkI +rfm,A$'^^FMjTcjO`k40Pn0(@Q'db_=[$\DE-+tNrVQNmrr*f6s8Vrpp.M-S;kZ>Nq#Cs +`P]U3_SaEqH?agcrd"s0OcPTbNKK4@`PBCj_umip_3Y(H*-cmUJW#DFP)YN]Ur^Ei`5BFas8W&s +rr)]UnGiIdrVuiq0)tkKs8)corr<#op06)?CkeWoZi'b$qYpKos8MuqrqufqG=d$qEcbtr5`D%r +plu)60n,@TPED:,Q2[$JPmNa$:prr;Ke%fcP-s8))l\[A`;kPG5UdJaLQrV"^7T:Vb&pA]X~> +%Jo4`ARD1G`6?BC_o9^rb2^PC`rN0fn+Pm;dnI[$7aOdZ'2&X/?>Q^O;2Q^*l"NDs)9e]l8.a:QM< +c-+2K[#[jN6qBnJ`lc6Ia9p/1\VsH^<+Vudp4*~> +&,cJ-g>Df-s8N#ts82]ms5EtUrrVuirqucuoAK'!rr3,srqu`orVm$"r;6Bhrr2lr:]17m^/g/B +R$Er$R$3u*QC!o&QC!o#Ng#csQ'@bk="'DtR?`u%Q'IPuP_Cf%DJj`?DJ;<0qXsjfp&G'gs8Dip +rpuXtXF?!"q#C]Fn1Ec?,8:)EtGA +R$!c+P+%GL/V'@TOHQ&rPDY0ZR?j&$R?Ef%QBmf'R$NM\6LXsPnbrReqYgEtrT`eDld5Y2!WN,I +rX/Q#s6Sn3l/;R@s*t~> +#QF\SU8cW:s8N#srrN,tir0eprr2ios8W&rrr<#j^n.^;q#16gs8Mrqrquirr;ZfrsOGoBhR?a#+KNBRgOdVJuQBd`!Q'?tVA8Z4-DJNlKp\OXas7u]p +qZ$Nks8MY[CoMc9p\OpirVc`js8N!,s8N#trr;rT`P]XErVc`p!<)lr!T(Wk_ct!q^VRY"]u>,< +H$Xd^GBn^tPEM#gPE3Gq_o9O1ai1E3E.<:^I!BjaLQ7IYOcYIB`59=.`P]^2R6>C^Z +Ec,f8>Zm8RR$Nf!RZNnrCb:1-PE:rrPEUl[P*qW"PaISrQ2[!IQNib/JjAf)r:'[cqYpL%r8"un +]!.k#rVucArX&;p^n[BiU>#GCJ,~> +%JfCsBk*(6aNDZE_o9Xpb2^PC`rsR$X"tPEqW%Pad>-.$p'aQ'7N!Q^*o%K.^\PD0Ti*==2_N`llK: +b/_NJbK%]Hb^X#pCi@eGc,n/PbfI]:^VS"4`Q$-GaMl6Aa2Q6MaLT's]@G*Eb0%cHb0Skkg[Oh+ +naTfNnaQ2Gs1H7jb/_H9^;7q;c,IQ7]C#4[oCDPRr9M/,aNViRahu!5`l5m8_RfR=o^2)9o)/@< +]u%q5bfRlD`llQF^qRY5p@n4QkgAbKaiDKBb0[i9aiaV(rPn`Us2t;]:rgf+cd'k]coRkk6.!]q +^rjpB_og'Kcd9sM0P,EAE,T]9?! +&,lP.f\lT*s8N#ts82]ms5EtUrrr5nrr;rnrr3&PmcXTL"T/5squ6Nn"8r&nr;Q]ms%Mn]7sKiM +Padu.R#dDrQ'IZ%Q'$rXQ&q/oPa7MfF@DFQPaml&R$X&$1h_5RG&2AFG?nP^?krVu`m +qb*oa9(`/[p%eX_r;$="s8Domi5NXQeboCGrr*Q1qtTg#LO"#bH$X^ZFD5P"#Qal(rr`)t$k-Zd +F`;>B@7_+9!sST&!!3'#rW!?:>]=b1Hu!b31'[d]rW)s"rW!K2!<<**0kklXH$t\%o`+mhrr3)a +m.gSZs7QBir(m=lr;-?jqt?,!VfJ:)qYg?gq>UEkrqQ;/3,sCaH?"1LD08ToQ]dZ'OHc-$OZe5] +PF%i(Pa%&XK8c%[Q'[T)PFdf$R#d2ZIV)1Lqu?3c!WDoorrr8bn+,_rr;Y4Err3,ekj%Kjrr7K~> +#64Y[USZ-/rVlfr!WN&WrW`E%rr)fqs8Dm*s81<6Y-dNns8)cprVlco!<2rsqYpNp%IjBdGD2$> +R?X,*NKKM*PWG(nO,/FROHPlnQBR&F7?iaqS<9/*Q^![jBR=W=C3+E;>?g7ir;Zfns82fos8)Zj +8Z&1OoDedbrUp3irXJo,rr;utrSYZ4`7=hbrr)orrVloT_#D7O_$@]j_7n'5G^B@9#'tQ?I"@09 +rfKU0`kfR,_oofsIs-3iGBS@YGC"anNKTHdON>8K_o]X8^R0:kIX--bGB\@]Jr5SNMN=RT`P0.% +_"Ia-rVlfpqW@;IrVlcqqYp?k:&b(js8MbQGG8uCrVlipq"k$jrr;ciEAh]\BQe69DejDfE0ZNL +R#[>qR#a=ZQ]mc,PE_;hK7f,IQ'I\sSroM#Q'[MiKm[H&r;-H`rX]&+rVlfne@;rqY,h?rqu"k@ +$NKts\YG^eUZ;:OJ,~> +&H;44D-qn,`lcHC_o9X^V@Y,bK7]<^%_0kn+H_RkeF^Hc,e)VbK%N:`5]m=_nl*@m-aK1qY8^Y +_p6HHcH!rC`5]m<`4_oBpDb07uRH#]HfJCDES +`l5d5aiVrYg0#k=HYdnDGAqA?9lZJ]Q'm\sQ^NptJ^* +/Cr%ko#MBbc,n/L_lmuG6:apZ]#i"5dDu[=^UJ/^9ibuHme_M~> +#6+Z&f\Z3!rr2rt"8hons5EtUs8VurrW2rkrr35KmHX?Ds8N&tr;?QsrquZkrr2lr:]((ir/X*F +L51SROd)B"R$O)+QBI2\JVK5OQ'7N%R#->GT9>D+P*:um;`Af6EHQ/BG\_Cg46ZP1s8)Znqu6Wq +om24eEi\j4s8)cprUKjps8Domi5NXQeboCGrr*N0qtTg#LO"#cI!p0]AR#WI!VQL'!<<*0E--,10TM9XR[TP+N'2WSR#R8sPEUuZJUrT9 +P*M?$Q^*SuP)>9VMaarOq"t*]rW2uqrVm5qmIKc)l2Ue]s8UOHrr3,^lKdZgrr7K~> +#QOeiVP:g#rVc`qrrN,tir0\mrr2lqs8W&rrqcZpl^+`[gA_'Ks8N!#rVlcprr)lnrr;mls8@sC +=+l<`R?3W%P*_DuQB[JgK7AW:QBdZ"R@&h]/t\;jR?EbqPuBS^BlA'/EHGi55W\0.o)J[gs82fp +s7LYeUMm.&s8W#ss8DrrrVuoss8W)t%KHG,s8N&si5NUPec#IGrW)oqrrLulrkf/]_o0d;_gH`T +H?jg_rHh._J;]PQOcHig`5][3[G> +N/s$eR$<_j4+*9[NKT]nPDt6HJVT5JQ'Rf'PDb]fL5^qH6a?XaqZ$*b%fcJ*rVlW7[^WTA_>O<. +d/FCPqs^b^S=ZJDrr7K~> +&HD@HF'Ndt`6-6A_o'L:bf\te+NDC>`lQ0@c-=JM`PSiY<`tNt_8FI?c-4>O`l5s;aNFJ&!65#W +HGdhReScd%GCbC5Nff`kQ'IZ%Q'$uXJ;0,NQ'7N%R#->GT9>D+P*:oi;)E9-E-?/BF(B#E-HFKK +bf%NHa3;o\cWtFYArt6_be;':a3)K@_8F79bf7ZHbfIcEaN)9Cd`Au6]"?M9`lcHEai_rbdbF9_ +nalDF$N9i"^;@q5cHOAIrP(tBaj%i@^$,=fqt^0Z\$`ohaN;cVbf[l>_8jaB`P;-Ao(_JAq:!-q +cHOMW`lQ0>_ns=3aMu'Fq=sRTl-\kLaiDKBb0[i9aia7srl4rV"2Vq!b5TFPbgeeKVCp*2aNM`P +bJMHGgauZDEH#i@E,TZ9EFLS\OH>luQ'.%fI?^+YP*qMtNJ)k6LQ.I^P`q8rP+7_gJ9GubQ/0^- +bOW^cb0A)O`PJT_5s\%=H,%A;ajJ0:bJ:5j;GC/,eF`]i~> +&,uV/gu%Gts8N#ts82Wks5EtVs8N!)s8N#or;ZfCm-F!9rr`2rr;QZp!ri,qrVl`p%J]tuo_FPE +JrY;AOI):4Prt:mMhd">I>X5RQBdYsR=T-LR>[PsR[&=rDJsZ?DKg,@HZO+I/C4=is7u]oqtp3I +KF),r8B2=e_N0Tcf5_G/YR?s5&OH+gDKS+r5 +MiNdXrf@MBKSb)'7/-NLs8Vudrr;rqrs@rXp%$]6s8MusdJaCJs8V!Am-*=*s*t~> +#64\sY+N&qrr**"rr<#trS@Gds8Muqs8W&toq&4ibPh>;s8N&uqu6Wqq>O+gq#C*\q2eC(NdZtL +QBd\uPELr_KnFu*MNa +&HDIXI9:0l`QH?B_o'I;bf\qds2P)[)oftCc-FAI_S08V=b+)s`6$6Jc-"&F`Q$!Ab5B?[`c]PC +e&fneEB88tG`.KEPEM/pPE1TUKS+]1PEhH!PEM>bCJU@VRus)%HqS6kFEDGIEH$/?BP%mYe]Q7X +aiMTLe%e28O[RgKaMQ$6`QHEI`l5s=c-42Ibfn/IaN2E@bL4JB^Uh&-bJq]Ia2uKMgsXsHrpq?% +nbN3k^V\%6b/hQ<^V@V#_8XI6_7fU;pA"[I[^X/j^;Blj29*\R^V.M'`l,Las6f@Io#J,Kaj%]C +beM$4^q[Rs^r!t#chdM3p>aQef?)(S`lcZA^<+OiaSs1W`Q#m4`6$->`PTXCR8g&+>h$a0aN)BC +ce3A-D0'Z=DK:,KEb]`4AknS[Mj0Qb5D"rVR?s5&OH+gDKS+r5MiN^PMij6]K7@ZF-.g\jeBZ&' +a:HJ9ai(rh>S!pF5ZinJ,~> +&,cJ-jl,A%s8N#ts82Wks5EqZs8Mrorr3<(r;QNls4c`'kPbD`qt^*drr2p!rqu]mrr)j\q>^?l +s7l,j=akpZL4kG@N/W[MKS+r3Kn+fEc?&AFEhkHE]WRas8)Kh +rsea3PGhd(pAb!erqcZnnbrprrVcTN`Pf^Fr;HZpr_*:gq"2FYH$+7LG]$Uj#QOu/":#/8!WW6+ +#6Fl*"UGcHF)Gqg&J#!s/<"%0m=tBm4fI +I>ubKs8Drrrr_KOq#:9mnbrOerr;rr8,)mgQ`X'6p](3ls8)cJ6$-pbI<9LTDK'QBDg61lH'4h^ +G>3sPQ^F&'R?NM`J:`H-JqAZ0K8"o6M1^>;J4f&*rr;ios7-'irVlfqrs@iXp@QW0s82irdJaCI +s8V*GmHO!:s*t~> +&-)Y+\Xokls8N#rrVuosrS[\Srt,2.rVlisqYg,nVQRi5r;Zcos8Drss8DosrVld[q#C6ks7u2l +>(2$[KnG5;MMd7EJqAW.Kn+fJ8"s]mG9NJ9uTe +G^4XaI!L!aI"I6;NP*3Ib,BH-IXQWjI=HKcIX?HgH[UO*P)mGma1TKUFEi(XH[0j]H[L6jI=6Tt +L67Oka2G^.^VTUCs8DrrrVG[Gs7cNks8N!qs8N&rrr;urrVuonqd#Dd:=f4?r;Zfos4KYYC2nQ= +DKKT1DL-/CD+Af3SrRqdLkq=`Q'Ic$MMH_6KS4r/KS>&3JVSu/I#*5Cl2U_^q>UEarXSu-r;?Q\ +]!f#SRI:(Ss3UcFr +&HDOcN*'Gl`lQ6@`P][=bf\qds2P)[*Q?%Bbfe2F`5$Io=*:p__oBpFc-"&F`Q$!Ab/h['b5]P- +`k]X8d+6OdPt5q>Jq8K.LlICQL4b#-Jq\`-Od_MsR$0[`89a!5eWK/AAp;_S5L9q>]H\]#)7u^Ve%+^rOC4_nj.'_o'+Uo).qSa03(h_8F1, +_8?2es1eTJ'thE!eGAt2n`%m]f?)(S`lcZA^<+OsaT'E]aSj-XaAU%!`l#d9aN)9=`lQb(LS$W9 +bf[iJe'H^V.rK9MJT5USDfK]BDKfthG`eVZG"daLQC*r&R?NM`J:`H-JqAZ0JU`**NJ)Y,Be$sO +dFZL_bjia`ai_`D]pVY]5>4hW^r4R?$HBlL=\qgpK]`13J,~> +&H)S.o&Sp2s8N#ts82Wks8V*X"98Arqu6U%rqZTjs8VZHlfA$E"T/&jqu6Qo!WN#qrql^-qYpBi +qt6IKL4FZ(Ljju0KnTGX"b_Y]Iu'6dQ7nAjPCdjKOd;AL:NCN#D0C#DEH?;>E--5::&";[s7uTf +]OIt!F7oS-q>^HorVZ3b$ig2(qV]?2`7=eas8N!Hs82T`_M#6G,9"p"`-$Q*8oH#=rCrX'5"U>23!!!NbB)ZrX +IuVtMs8Drrrr_KOq#:9mnbrOeq>W,Fa]bO4@fH6(s7cQG4EbUaE,9Q:F*D\IAoMj/EB]JMPW4;P +R[9;$P*:iYK7njRK+NWfK7/B,MHWaHs7Q6grr2fbrX\r+rr<#lkk+W +&HDb-a.T^Hnr:0ass8MrprS>#nZEBtqr;Q`oci+3u +US"$IaSl/ +&HDUfS69!k`lQ6@`P][;bf\qd+i_UGaMl-BbfIuE`l+$9<,SYG^rOUBbf[rE_oBd?bfIm'arSRG +ahl-@bg+Rg3I5jlIYW?)Knb53J:`FDQ)@FDu2ABl.d(=ub3[ +ccj5Jc^d^sG$W_P`P]j;b/qfNc-4E2at:]R`luZJ`Q6-@`QQ]N\\G_fe]YtTb/_WHceQt'nFQ8H +p[>K#[(sJj]t:nirk9#X^V@=g]Y1oGq"iLG[_08a^:XBS)8!Sq^qmdr[^iuZo_e^b]sG#R^:V"k +]=#6K\JVog[^ +&H)S.ro`A3s8N#ts82Wks8V*X"98;oqu6Ttr;$0N.Q\8Kn]MZ2h?U9K7oDTNgYrtP(e*ENg)MIDKBN;DffrGC3OrHEH6A.B`.c-qtp@i +V6!ASpAP$jq>^&5o*+;>(l'+tc`%MB?`$k*LN#n%:X(1Lln1)UT2#S@RT&ebff&eYch'GCub#oGF+GBn1Q +N7@M"rVlfr"6f+Hrr2rlrq$.Hq>:,4Ml13is8MroqH%BMEHZSJF`_SDF)QAHCiX_tCR%53PEh<" +Q'n&$Kn=l,KS+o1L&HZ7K8tY;HpRW,q#CBgs8)ccrW)ios8V +#64\ueY/_hrr*-"rVuosrVb^Srr3N-r;?Qns8Dlmk+&N^iVWKKs8W&urVHNmr=f21q#16kr;Zci +bs$i9KmJW,JV*fP2h?U9K7oDTNgYrtP(e*ENg)MHD/s<7DK'E:Ap&<>Df0`"BDh`.r;6CfU8glM +q#(0lqZ$Hms8;los8N!,s8N#trr;rT`P]XErVc`p!<)lr(#HaD_o0?[F*M_XH#n7UH$FU[H?j`6 +H5:dTL53:WO)fo"E-HSPGBS4YHZs[ZHN&1%H@:Ff\\=#NG^+=YIsZKeG'qt1 +_nl$Gs8DrrrVG[Gs7cNks8W)qr;lotrr+eTr;Q\:M54[^s8Dlmq,M'HE-6AEE,TQ2EG]r@C2e;l +Bp1f+OctloQ'n&$Kn=l,KS+o1L&Q`9K7\r1I]!er@^%D=(s8:7C +$2qc8SXZ2%rr;tJ~> +&HDXfWEW>e`Q6-?`P][;bf\kbs2tA\s3*@?aMl-@X\;`/R('`JbK\/M`l5j8aNVlLaNDZLb0%fM +^Ve13`Qurc].SAXJUp=p$C\am+9A"aMuBL]tVV2rlG&\s2b/Ys2Y2_rle0\aMl3EcHaVPajsOA +T3mr3`5^'U5[.o;E-62CF)>f8DL$;=EHY2oPX0PWP`_B!R@0+iJq8K+K7ei2KS4r+IZ9)5F=Z(m +aNVoHbJMNoa:H83b/Li9:dI3-HG753ai;`L$H9c-;bg+jVX4?_J,~> +!;uir!pAV/r;Qfpq>UERrWE3"q>($is830"s8;oseF)>jrr3?'q=jdcs8W)ts8W&srVmQ.s8Vrn +s7cHkmDhbNKS>#3KS5!VKK"O7L5(/3Q'RT%PF.Yo9MJi!Ciai>F`;AGDg6>;F)c>AED#S9qAeS' +HApAGs8Drrrr_KOq#:9mp\t!gs8N&us8>Ifp\O5LWMrbhr;ZUuB6&!6DK9f=F`h_DEHPu?F)H"] +9l5rXQ^O#(Oc"dCK7\i4Jq8Q-JqAZ/KnP)-:\a_^rr2iqs82Z_rW)oqrrhELo)%8BrrMuri;WcV +rr)oqpAPBulf[^&j8]/Us*t~> +s8NQ%h4gUdqu6Tnrr<#trSRVSrrW2urVca%q>U&WYd2jVq>UBqrr<#srr;rqrr2j1rqcWoqu?]m +rr;B+7tQAHJ:iH*Jc(-YJVJo.L6\'fRZj,)MbHiO?#+J)DKKf7FDGu=BQ7s1Ci)hHpAb0iRW50L +MYd>Or;Zfps8W&qrqufqrXJo,rr;utrSYZ4`7=hbrr)orrVm/[^r+(.a/le[rj;j=[^N[C[f<^+ +[C +#ljea\6i6l^;^)ms2G)\b2CA@arJLA`QH?FaN26B]2;jAM7C:?ai_]D_o9X:b08$-aSj82aMPp> +_8XR7c/-jO20OG#K8,)6Jq8K&It<0*L44lBQBI]#R$NY(9jV%ai_cMb/hTA`P]^>bfIcEaN)9Cd`Au6]"?M9`lcHEai_rbdbF9_o_IM9 +o()DD(A74emdTiBqYKmVo_.qFmI&Tmq=sU\rr36&s8Vuiq"t'is8W'&q"!dos8;ThrrN&prr2rt +!<2fo'D'u=n+#c*cg99*a2l9Cd(R08b5TK\b5]Q]`rF*[b5TU]cH*uDbg"ARb/)?F2kQj2ccXM[ +5?;N:DJ3m-F*);:D/aT1GB.P?4A'FMQBmu'R?\*s*t~> +#5e>us5)r#rr3)uqtg0gs5X+Xrs8Q"r;Q]nqtpEc,u83aRK'Pa.Q#Q&UOtKFriiKnFl-L4t&3LOkA38bW)^rVlcqnbrRh +rr2p#jQZ71h>[ESp&>!Orr)iurquZmr;Zcqs8N!(s6&n7kM-%:r;V9~> +#ljo&m[^,_qu$El!r`,uiVrlUq#:^#p\:_dZ,lY=s8Mios8D`lrVuiq-N3VR8Uu\JIu8i1KS4`0 +Jq8N*JV&`?O-u>tPa$Pl5&tC[FE)>=rG_[S%WZB,F8Gq8p-PUN:&4Sarr)otp\t-krr*B,rr2rs +s8Clp_o1+(rVlcrrVca&hS@%H`59F0`Pqhr!6+lSrl#Aa_ns:3_T0O3aN2B@`5MYms25_m`5K[1 +aN_]I_8*t4^Vde$`l,m:`Podp_]-G3`lH-9]Yqh/b/V9:`PKC`s8W&srr)]UnGhYMrr3]0r/5g# +DjC.pdm.YkDK0Z8E,T]8rc/QhEH5r=CcYpVPE_>tP`q#Yrdk`7KS>&.KS>),L4Xc,Gt7T*s7uWo +s7?6irsnu(qWPZ*\?;=gp&Fsis5EtWrVulns8;j'qY$k\R\Q\DrVunJ~> +&HDY"eRMd)\A#r,dEK\N`l@#Zs2lA'`l#^6aNDZI`kB#s>ZZ!UaN2BDaM5I/bg"DT`q[XR`rF+. +b0AA\Uc*=KIXm?/JV&K"LP(22K7\`5OcZ$!PEhD];*8`.E,olbqXXUZoC)JCp@@hLrpp3`p@eL\*;&t!p\OUWr:os\o^DA?im6uC +aiDKBb0[i9aia.ps2t8\s2m[L`l?*AeT,B&Aq&@]Yq'.%Df'?2F`qhBC2S!.DfT`9C,fCKOd)3$ +R@'"iKnY/3JqJW&ItN8tLPUG4D(sJccH?+,"3/=!`q.:O`XTi+^R8:l4%2T@^s'g?grT<2rQ#>g +]j"-#>].^Vs8%6~> +#5e>us5W5#rr30"q>:0ks5O%WrrE&tqu?WsrVH?hrr_iCmdL/T#lOPus8W)oo_8@bnc&Rg%/dq@ +I#!K'I>`Z3Ljo;U'8(mkMOBirQ^O5(IlDUmDKK]6GPQ7iG&V\G6`KeRH\/8=W:Bocl2D(jrVcTN +`Pf^Fr;HZprWrQ$q"2FYH$+9/GQi52EcQ5DqKF(oZ,G\KKU +EdiFbHusLPF`hqRG'8.OE,orAHus'q;eU5qFaT1uo`+mhrr3)am.gSZs69Lns7lWcq+d`f9'c2^ +8:#,rq/Rj(Df9W9DfSEeK9_ghQBmbqM1^G6KS+r/K8"u/LP^V@MK_trr;HHirr)lerqud(l0@m8 +i;`iTq>L?nj8T#W!WN#rqZcutrr<#trr2p'j7DNlg]. +#ljo'o;A7cn,31brr2rtir0#WrVl]o%K?D-qY81*YIsK)s8W&orrN)rnbs'rrr;i.;0mnMG^G9s +Knk*TJeN]cKSYt\P*MB"OFB*3EG]c4DKYnsrG`?kDfoqbXSM[L9mrVlcr +s7?6irso&+r8,0!\ZVY"rr<#rrSdbUquQcqqZ$Km$N9+YT:V[Uo_ngiJ,~> +&HDY(hK)n6W4U'rf$V^\_oC]W+O&!L`P]U4`llQJaM#3B<`3A3a32TG`k9%*c-akY`Q%Ji&&cZ? +e=o-EItW)pML^G8Hi8R1Jq8W4R$X#&R$Wq_1imMJEG9?7rGhgT&8c<#J9]m+9A"aMuBL]tVV2m`>FJ +8'&iqaMu3=^X:'Q23X[ke(UHPDJ4*0Df^&EF)5T/CMe!1DfS?aJ!$%^R$sA&LkLG8K7ec*JV/Q& +KSFo3L1iU"ccO)GaSspsO%`?8uMH)c$92`4iXAMP^=FJoss7h*~> +s8;os!q5+*rr3,uq>L?niVriV!<2ups8E)uq=spg#g;]"s8N&tq>UBn!rMZbrr;Qgrr3E%rNK4T +KnbJ7JqJ]0rIbi:JqJ`6PEhDtQ^q?jUC]8S`JrS:Ps8Drrrr_KOq#:9mkl)(es8Vht +Dl&58q3supF*7;!-Za9HEcc>/7"^J`PEV?"PD=jDKS>)3KRnf/Ljt,4KR8GHnbE%^r;6Kns7-*e +rs\SdlgsQ/s8Vrpr;ZfWrr)isrr)ckrrrr7K~> +$NL,)qR<,sj88fSrr<#t!<;'X!rW#rrVmE-s8DutqtB?DYHdEhs8W#rrrE&trrN,tnbrajrr;`h +Y>Y@Q"c.SWJUrIPJeN]cJV&]?PEV/pQ%siHCNFK;C2s2krG`?cEb9AgS+to;Wh!&Zs8Dugrr2rs +rXJo,rr;utrSYZ4`7=hbrr)orrVloT_#D7N_>qLQ`;daR_Z.M#_S=41^raEKL;LmK`P]U3_ns:. +_ns=._Rdk*`5]rsPEs/-_nsj4&PEM&nQ'$ZKJ:`E*K7nf/K8=r.JUhoq +8FZHSs8Dcm!<;Tg$30u%d(?iuV69XnrrN#qiVrlUs8Musq>LHneXZW2"M4I/s8RT~> +&H2M&k([,JS@Z_de'Q@Y_T(TV+O&!L`P]U4aNM`Jb.tTi:fUSp_91$C`P0+.c-OYS`Q%Ji&&lZ: +bL_qAG^kF*JqJ`.K`-N:Jq8N+M3=*fPEqMgI5,GUEcl/nEo]5naZ5@ +naGT3mIKuJeA/ukp[mtGna6>E!:g$Z*:iIgoBlARmE2o_nFuMCl-J_JaiDKBb0[i9aia%ms2IRM +b08#L`P]I7e^9RbQ#gKpKfNX\EcH)?F`hbBCi4'+EHQ8-6%FiTOHPosP_OjDK7nl0KRnf/Ljk#/ +Ir]`jdEg%\`5p%!aof`)`Q%Ji%`ZQ,[s(V15Xo4oaN;cId`;j>aN0`E8PN +%fZD+s81X.s8W)qqY^BniVriV!<2uqrr3#qq>L=!c0a`gs8Dlkr;Q]tr:fsbs76-us8;fpr:.3k +I>`T0LOoGYre1<*s+Co8OHu9"Q^*8ZC.i\dDgH9rEY!,6DfSd%mUVK_>/L.5rqu$[$ig2(qV]?2 +`7=eas8N!As82T`_M4G1Ed2tVF)l;BG5c_%F`DSTI +#ljl&qoGG,gA1aH"TJH$rVtmV!rW#rrr<#t&HDb/s8)HaSuo3Mq>C9ls8W)ts8N-!rUKjrs7uQl +qsh-jI>WH*Kn')Srdt0&s+1c4Ng,ioQ'6iSBhEJ_D0TjpE;abeDK]l:<."%COK)Buqu6Wqo)AXg +rr*B,rr2rss8Clp_o1+(rVlcrrVc`shSB-.rke]Q!Q;nT_Z.IP_A0u*`4X18]oA&0`Pfa5_Sa=0 +_SX4.rPB5h^;e".ZB'X/bK%?2_SX71`Pf[n_ApJ1`PfO)aM=U0Mlb"9^;@\%p&G'jrr2lmk4&E= +rr2rqrXf,/s76-gpe[Z`;pTtqEcu6qE;sniE-#o9EbS0\Ko_OcP`q)\JV*lR'7tjhKSOr+KReo4 +LdUh7q>^Ens6BRoqu?Wg_RR(`S]^g-rVucph#@s*t~> +&H2D$lC2-bO1WHVd*9bS`5^fX+O&!L`Poa7aNMcJbe^p'<`N1_]Z/49`PKC3c-OVR`Q%Ji&Br#1 +e'6('3cg+/MMd(AKS9>Ws+LE)%t]k-Q^*l$KS*i,EboW=D#JAOD[pu/Cd_jc:P>E2UX.'-b4n*ff:oCMMB +p?_PEpuB&jaS>Juo(DPArUKmY*V&Odp%.eRo$XeJg%k:/mc)RZf?)(S`lcZA^<+Oda>1cS`Q63G +b/hT@bIl*QgH:Rb6+q82CNF<3F)uJFEGo]1EGoi>Cee`"NKfZmP`CKMKDpQ(K+`lmM1L;7K8P/+ +/CrIpb/DHBb5TTdb/VHia:5k`@nSmM6@ZYda32QGe&Vs?`kI7-8PNNgk4nuQJ,~> +!WW,trsIN:r;ZfpqYgHorSdbUrs8W%r;Q`rrqlNirs7NCmcjcNqYC$frrrDsq#:jM-qu>p[$ig2( +qV]?2`7=eas8N!*s82T`_MnmMM$Y=JZGm8G7F`qqQGlDjr +HZsUP8eD@;->%.:H[Bf4E)$$qD0u)!'QA89EcY,_Lk_(^RZrhfJ;/i6KnTGX%tTFcLkLP1KKSPqs8;osqW[tZ +r;Q^(kO\<6g&M*IrVc`qec,RGs8N5fkO%Wsrr3#us*t~> +&HDb/qq%L7cM7D/lJq/N/IY39)JqJ`/Jq\i3Pa7GrP(&+$6ud-`ErBtVE=d))EHcA3A[Z'HN,8Lnq"t*^rr2rs +rXJo,rr;utrSYZ4`7=hbrr)orrVloT_#D4Q_SO+g_u@OP_>V4`]tqh5O,9*Y`l,g4_8F71`5BLj +_DB0I_8ia9PEhQV`50C2^r442_o'=,_Sa@1]YMV(UPtM'YJe/k`P97^s8W&srr)]UnGhqUrr;oq +&HD\.s8;lkb>kI+=G'LfBm9Jorc'-#G&M>>:g&O+OI;K"NJ;q7K8"u2JV/T*KRSZ*Jp2c,lMCJZ +!W2o^rX\o+rU%2*\$MRhrr)lqr;GURs8Vimrr39&p:R,iStk'9rr7K~> +$N'YqmB'o$K>#@NrlYA``5^fX+O&!L`Poa7aND`LbJV*+EDK__\AZS1_o'I9bg";L`Q%Jis26A1 +_8t-KIRes?M2-qEJV8]/K7nr3K8,&7Q'[Z!PC/""7<3?fF`VVDEW0kgEH$#@AlDX- +s8N9$s8Ud.mf*4hrr)lsrSd_Us8Drrrr`5tqYpKo&Gk,Lj8]/VqY:'is8Mfgrr;Ke-3*o@qu-Ei +MI)0gKnP&2KnY24K7no1KRnZ2O-5rtLOaP7DffmlEY!A5E,eO(9q\@ZhtZsEs7tpY$ig2(qV]?2 +`7=eas8N!)s82T`_M +%fZM,rT'fI^%hX-rr)lsi;OJirr2rtrr)iprr2rrrq4m4YIaH,rVlisr;cirrrN-!o):!7s8;in +s8DqR>`!f[Jq/H*K7\Z*JqAQ*It!35OI28eIrmqmEbtYmrc&KgFDc&<9S?$/UJB_CqZ$Tjo)AXg +rr*B,rr2rss8Clp_o1+(rVlcrrVc`shSB-.s2+cQrk]Dd^qde'_Sa:-b/KF(PDkaG_o'=+_u@UW +`59@,rk]kia2PTKN0''fa2c-9^qn(1`P]R._84"+_SO:0V2L=uOK&5qs2PM`p&G'jrr2lmk4&E= +rr)lprX]&-r;ZflrGd^kAXYMnIVA\!s)J'X!H3)ZD^%)cJqArFOG\dGJqAW.Jq8N+K8+l-H@^m+ +7Gn.Fp](9es8MutrU'Rpr;ZfS\[JuMUA+Wcs8N#riVrlWp\t0l#lO1qSt2FHjSf)XJ,~> +&,Q2#n\AsAEP9TAbfRuI`Snol0a32K>`Q$'Ebf[oD`q%1t`Q#^0 +_TC*VFAR[?LkgV:KS4u0JqJ]-K7JH.Nff`pKRIu/DKBaqF8p7ZD\6u9EG/lFGX\!g3P!8Laj\>R +o>i&o`Q$-GaMl6Aa2Q6MaLT's]@G*Eb0%cHb0Skkg[Oh+r:L$\!:fpW&bPhto!lEL\&%\+nF,u@ +nEo]Dnbr%jlM1,=^V%J!`q9'#p@IeIoDS2$n+#l;naubPe[rE,]A!#Ro((tim+9A"aMuBL]tVV2 +n&PXO`PojFdQL#0kYcEF*2PDD/F94E,oo:E]JW)Ko_O^LkL;-JV&K(It36, +MhQq9Jqnk7^!Y-@a2PX2aND`Obf]Fr&B`;@[=V%<6UsqY^W"I?bKAebs2OrWqT92^X(+t!94Gs5 +qu?PD~> +s8N9#s8Um3j8Ju[rVuinh>[BQ"9&/pqu-O#f^.Yus8;`hrr2rtq#L?^r[.[Cs8;omp&=f!ArMY" +LP:A7KnG#2KS"f3LPCVIR?2rOMD00gFSKkeGAM>:5F5ucJ<'I)s8Moql2D(jrVcTN`Pf^Fr;HZp +rXf,,q"2FYH$+7QG'.qNG'J<3H3ee>F`VP@?6TgW!!3O;FoH\0H#mtDEd2nXH?sg`B2fEB!X&WX +Ble<:F`MYGF*;hTHMr+&F'L@(!!WE16>h$iD0_)io`+mhrr3)am.gSZs69LnrVlilr;Zc[9W>$^ +4B-0?q/R^$EcH,<2M-C9OHPTXKS4u3L4k/2KnP)0J;B2;J4[9Hq>^?js69LkrVuo]mIgA=k5YJU +rr2rVrr;orrr)lsrqu]orrh?Clg*a5s8W(K~> +%fZM-r:@(bY4;Dhrr)lsh>S&err)fprVlirs8Di^SZ9*[rVZZp!r`&qr;Z?e-NEuCrr;ojs8->; +M2-P3J:W?*JUrE)J:NB.KSGPSNdcP8/ot`IqJ[$dE-,i9D*=+mX*fp's8W)qs760gs8N!,s8N#t +rr;rT`P]XErVc`p!<)lr!T(Wk_uIUR_ZRcn_SZ;f:r1#d`5Tj8Q&greQ]fD,_nj.+`l?'<_SEq% +^r"72^lOD>Q&;-Tahkj0_oTg8_ns7+_SX(*ahk'=MNj6cXM_uj^;Rk\s8W&srr)]UnGhqUrVuco +s8P(Sr;Z`Z8uJOR3)OF1E,T]7EH,r:EH,o9D)?cjM2m[UKnG#0JV&N*JqSf0It!-1Isjbop\Fje +rVulr!<)or!<;Qf$2skL]!Sf@]Cu4%rrE#Vrr<#srr;rsrqud%qr+BCSt)eKrr2qJ~> +&,H,$o@84j@^X8$bK%`Ha5P,>aoKN]`Y-A7bfn2M`PATQqh7HW,Qb%b/hZGdE;(% +,0%UAbfn/IaN2E@bL4JB^Uh&-bJq]Ia2uKMgsXsHoCMVHrq$0[!:TmVrppirrq=4>]Y25!p$qP? +oCVP>mI0T8rUUQsna='#`lc'>p%\.LoCDYTnc/&"mI0N5o_S.0]Xbnl^u5%`n*/lYm+9A"aMuBL +]tVV2n&PXO`Poj +s8N9#s8V$:hYmHVrVlcmhZ!HQ&H;V*q>^?ls4cf#pAb*er;Q]q!rMfknGXg5s8W&qs8Viglq)k] +M2$h;KSG/5KnY27KRni/JrG_IIt9RtF)u?sEXm&0E`7BSWMiG]qYg?krTO4gs8Domi5NXQeboCG +rr*H.qtTg#LO"#`F`qnMG'J<2H3eb1LdEI!Mmo`+mhrr3)am.gSZs69LnrVu`os8DroOF*[I +N$`C_q/Rj(E,p&;6u[p3KS>)3KS>)7LkUJ7L4t56KSP&5:VZf*s8Dups8)c[rX\u-s5WS?qV_;O +s8;]js8V*X"T8/or;QWo!<2uqs8N&u#3P7:l0nZNrr.E~> +%fHA+rqj1"V!7UPrr)irh#@1-krr)cnr;ZBf.0'2Es8Muss7uKX8V;JG +JV/H(KS"f.K7\c.ItN9%MN!@>Il;.`EGt_n&92Z+EH5$)6)CDKo_eaerr)Bdrr;us%KHG,s8N&s +i5NUPec#IGrW)oqrs%>q_o'F1_Z%IQ_Z%A*^r""-`lH)eNKBa5`PTR2_Z%@l_ns:/`NYJbP`C`dWkHBk`5KLbs8W&srr)]UnGhqUr;Z`p5Q1WZ +s8W&sr/Z,oG).>WDfBW6E,fl:DfK]7Ebe!ZKmnW+Jq8Q-K7ei0JqJc2JqS`1IYLV2qu$Kns8;oo +s82fqs7-(!s8;f6]<\W5h"UjKrr;rqir8oVrr2oq!<2uts8E<#c_@/*S[\Iorr7K~> +&,?&#p#Cm6>-,Q\aN)BDa5P,>aoKN^`rF*kb08)Mb/20/?!(6Z^;\"0aSj6`aMl-iE$_T0U3 +_oU6HdE=1_Fb5=(JqJc0K7no1KnFo/JUr`=M10t6E--2BF*%;"&T;Z2FD+-`4Jnc-d)sDKaN;Tq +a)0 +JV/Z0LP:D,H9A(XbfduF_T'I7b5TTeb/hTBo#M<`d))V\5r_G,PIJZU`lS/%ilM/>rQ+cV#ekmd +;c6MBjno&WJ,~> +s8NQ,s8ViHj8&`Trr;orrSd_Wr;Q]nrrE&sr;Za&s75"1lMpn\q>UBn!rVrnl2CtdrV,hQKS5/: +nUq-tL4_F%G&DL!EY!/1Fusl&UM["#s7u]nrr;6^$ig2(qV]?2`7=eas8N!.s82T`_Mrr2p"kj8*Drr;!W$N0WfX/\X6;0?o"rc.sY#]Xd/D0'neE/"+@ +s+^E(#DIb`K5tZ!r8IV[qu?TRn+ZP/rr3-#qu-Qpir0)Yqtp +$iU)(s7k*6Vq:_CrVlfsrSd_Us8N#trVlcss8Mus"o?'-ZbZ>7rr`9!rVl`poD\[frVd$$q=-t- +JV3HD#ChGZ2J[DNFSTkdDfBbXF+^*#W:Kubs8Drso)AXgrr*B,rr2rss8Clp_o1+(rVlcrrVca( +hS@%H`5BL0_ns=.rkT)^_8FC4bE\0LOctoiNlM%7/]5TKahbs:]Y2)$\sSGBP`q>hRE3@b_nj:0 +^W=.)`kT@.`PnsBP*_;iNf0n9`!s]'_=dj.rVlfpqW@;Imf*.ar;QTn)#j4tW2;n':2t5kDfBZ8 +E,KN;CiX\aDhIt>rdt`5JV&K+K7\]-JV&GkBD2<'rr2rnrr<#grX\o+jh8=8Wg`_]rr2rtrr(jU +rVllsrV[6*qu?Tns8Vi,US46R`Vf]7J,~> +&,Q2'qW4e]?CJaO`Q?3Da5P,>b5]Q_`rF-[aqi4DaMu66ISs<`\]M_+aNDZHb/hQ@`q.7``l5p: +`lQ +^Tb>c`QZ0=oCMMEnFH&5o]tr;m-X?9p"?me_SX()^Z#%_oC1edm+9A"aMuBL]tVV2n&PXO`P]^8 +rQ5,]qo1G:f/'+s5B086F`qeCCMe3:E-Z2;F?XPpKnkG;K7A?"K8"r1JUrH+KR\Dm>YEMmbfIm) +`WF6(b5TTeb/hTBo#MEcahrp-4[)J2ZG=K$`lZEHb2UMB`rF-YapuV8c,[oIb.):`:JtMam/?s7~> +s8N<%s8W#MhsgXF!WDrRrW2rrr;Qitr;?Qns8;os#Lr#&s8W&nrr2rtr9!t^qteT+LkG2M#D[hF +6$%$gD>A2_F)XI8;l?9io)Jahs8;ool2D(jrVcTN`Pf^Fr;HZprYkh6q"2FYH$+1NF`qqQG^4UX +H$44EBOiD!!rW*3!Y8pcG'8"LEH5uFD/t/IF&rr2p"kj8*Drr;!Ws83)%B;^])5]cpFs)SNfEH68;Ecu.tA:=9,s+^E(#D7_bL +I'esrSd_br;ZNWmITl0s8)cqqu$Hnj8K/YqtpBirrVuprr)j(m-EltjT#8Xr;Q_H~> +%K-5)s8:cHUWE3/rr<#UrW)utrr;rsrr<#trr +%/Kf#qsD()>Dg,5`l[)Ys2tA_s2P)[r6#&\&](2==Ce_N_SsU=aN2NF`Pojga99N%`Q64$bl>`h +a31D2H%(I$o7I="JpV)nE,]o:rH%pW&9MhMO%Z+26Hmp$aO//N_T)2g*lc1=bfn/IaN2E@bL4JB +^Uh&-bJq]Ia2uKMgsXsHrq$0[!:TmR<9`oCoCVbN_ns.!]u.e,^\70cn+6/>mcjB.o)/(SjLqt5 +_nNn,`5hK>nac,;n+5c6mI0i)_"Nng*`PqAg&&ku$8NK%.@_'FsaN2KGb2LGA`rF-YapuY8bfIrHagbbN9NG`*nbrK<~> +s8N<#rr<#RhraqGEd)_EDGqDRqZ%rK/Sf?KH$=IUGBS=ZEcZA11_9Wf +!!!-*!t.G4F*;bUNR[V#rVlfr"6f+Hrr2rWrYYS*r`>Yh;*f/AEH-&@EH-)FE--8E5"C\0LA6E+ +K7Zb4q>^0_rqlWarr2p*m-X01irB&Prr;lmrr;$X"8r&nrql]rq#1-j$K^X;k2ZLAr;?QoJ,~> +!W)fprrqkiU:0aorr2uri;Nr\rr2lprr2os'`S(0rr;rkT<,N2U""s8McmrV6BloD\ahrr*B,rr2rss8Clp_o1+( +rVlcrrVca!hS@%H`;@IP_@af&_og!@RYm2hP*;#gNf1oprPUk<`l>d4a1fX-O,B*eQ&q)jNg[u- +_o'=/_o0I2`4j48_l/ZTPa%>nNfT1"`5BL2_SPpFs8DrrrVG[Gs6]gas8)`mru:Y2 +&,?,'rU\BS?Z`]na2l?A`n&60aq2J$=&iIp[)U5/aMu6Bb/VHla:QA1`QHHOccjJL^r=Mh>^qS# +K*m0YJ6YQ]Ciji?EW'tYE=cu)1ff=&9;^.X_9C0Dahu-ia<8LA`luZJ`Q6-@`QQ]N\\G_fe]YtT +b/_WHceQt'nFQ8NnQG>Jlg*s-mIL)Gq9-Ca]t;"t_o0@anF,i;oC),9lh1&Er9V,(\%TMi_oBO5 +oC;A?mdBK1n+$#@HrQ%%BaN2B@`PopA +ha*9m8NLX.Ec>u8DKL,KDf9N-1-LDDK8##3JU`6)LP:A3It<0'IWdWDd*KGIb0'S)"j4p-`l5pf +a9or(Z~> +s8W,rs8NDehr"J6r;$3es5EqXr;QZp"8r&nr;Q^/rqZToi829O=ZSEVT;(rr;rms8;-\$ig2(qV]?2`7=eas8N!'s82T`_MpU-kr;[!%!X03LEr9tcFE;\KEbS;b"U,#-!%7pL"tOJ/Fa81TG&qtPGB.J-3=#Zk +"98E*!!!9iCNXlIIuVtMs8Drrrr_KOq#:9mir0ems7OlCUR"\]FE)8?EcH,AEccJKA5,75q1e]u +s+U\p>ke0os7cNir:9jdrsRoXo'b'-s7ZHlqtpBmir8uU!<2rs"TJAprVlcq$K^[@j6$CArVZZp +J,~> +!WE#srrr>.UoEeZrr2usi;No[rr2lprr)j,rr)fps8Mc'V68&7s8N#ss6fmcrrE&tr<`K's8Dur +p,R\Qnq-mo#(KB0E-6#9q/?mcC.VX+LMutarVlipq>^EnoD\ahrr*B,rr2rss8Clp_o1+(rVlcr +rVca!hS@%H`;@Fc_SaC0`Q-#cMiNsbPa.AjN/P]nr5:_;_Sj7-_mbYbPaISmP*M2jR`NIc_ns@1 +^rO4-_ns9gMiX-gO-,N_OcQQ^_ns=-_=dj.rVlfpqW@;ImJd(aq#:6l)#)Q]Tp&,PEc5l9EGof: +D/XH:@S8h,K7a)Rqh"`u#_[nI>PJ-qs8W)trr)iqqu?3cs8387\\#>=nGWCdrVuoTrr<#urr2j, +rr;rss8MlhgnUL5TX4:hrr2utJ,~> +&,Z>*rq>?)@UlIRaND]E`n8?4`r='Zaq2S(FAl@nZ-(20`l5s?bfIfpa:QA1`lu]RccjDH^;nII +,^On0K*m0YG=FK+EGKN5EW'tYE=d2*6r]\$JbYnaPSbm+9A"aMuBL]tVV2m`>@HrQ,#]rl?)! +`Q$?P[R$.<8m6:TD/X?3F*D\HEG\`?0Pu8ire2,?JV/`5Kn4`'JUrN-@Rntp_og!Abfp(2s3(Pe +aN4>"o#M6Y];\Li4\/)I^raaAh8oW9s2b/]&'W#5`Q#d2SlJBV>(kZ&rr2qJ~> +s8NQ,qu-Q_gu8;5qY9scs5EqXr;QZp"8hoir;HX*qu$KnfC8&)s8N#qq>'ses760hr;HWors&2o>Vs8N#srrDu]rX8c)rVGHk`5L4(rVulr$NL"uoYQB/ +F`V_Lr-/KjE-5r%+qP+`qu@c="VG?dF)uJIG'A%NF_at`"T\T,"9JZ*!!k)hDfbo%*d2jKFE)D2 +/.`!d!!*0%!!WE-5&5:dH$t\%o`+mhrr3)am.gSZs5O"cr;-6YG*&V58p#)nrGi0bEHlPH9:]:5 +q1e]u$&+$pB_VE(p&G'frqlWarr +$N0o&s8W)9U8-WArr2usi;No[rr)cnrVca+rr)cns8VuRS?KNqr;?QnrrE&trUKmes8W&urVld% +s8)cqoR:8'KCa[#K8+Y>?2oAXs8N#srrN&toD\ahrr*B,rr2rss8Clp_o1+( +rVlcrrVca#hS@%H`5BLl_@si(_nO43_NB\6Mj'EjP`Lc`NQ1t95Jt@U`59F/^q,)VOc5KcNg5fi +OI=2/`5KO2`5fR,_99]^LlRUXNg#N_P`q62`P]O0_SPpFs8DrrrVG[Gs6K[_s8)`os8W$(nop.j +@7>6WE,kkr%<63*DfJ@<=a5RNJq8LMK)L<-Jq/A1Ab>j!p\4[as7-'tqW58u^9"L2rVuoss8V!U +s8W,us8;rsrVmE+rr)N1US"6N_tj91s8W(K~> +s8S$aMYp7bg"<"a:QA4bfn>UaMu04 +aj\[P=b;4rK*m0YD`Up$Dej30EVjegFA4l`QpSU5b0%fLccsJF`q.7q`l5sAbfIcEaN)9Cd`Au6 +]"?M9`lcHEai_rbdbF9_oC;AMmoSoFn*^&LprL7i_8F+(^Vdt-_=mBen+6/>nF6)Fp[Yc/_nO+- +_8aC-^rlHEoCD89lga<3pAas7]thP$_oK^3`kK+Aq=O4Hj3R)DaiDKBb0[i9ai`tkrPnlYrlY8^ +rPff"b#j(_@S(QXCi=<5F*)GHChPqi8o&g(K8#$YK+j#oK7SN%JV/W*0iZ]Ia10.1b0'_-s2tA_ +rPnBK$,Np*8j>d3@(4)Qb1ar8b5]Nla2>j6]STW^9j+(OpAFpiJ,~> +s8NT-qu$Kig#`86q"FUas8V*X!W;rrrri>sq"Xabs8VurrrU^;k5G;^r:p'brVuHfs8;ips8N?# +s7Z-clTp*lKEm3S;8#/6Ed@J$%sUQcYBklsrVZQfqZ$Tns69Ljs8Domi5NXQeboCGrr*9)qtTg# +LO"#bG'F)uACF)c>GEc+2# +VG?rAq1Js+LIniKs82fqq>U9ho)A[h$0LjQo[Ws@s7uQkr;ZfUrW2rrrVm'#qtKsbrVlg#i8j1i +hu!EQs8E#uJ,~> +!W;rrrrrDNTqTX(rVllriVj#\rr)cnrr)j!rr)cnrr35pR]UBuq"k$jqu?]frr2rsrXJo,rr;utrSYZ4`7=hb +rr)orrVloT_#D4P_Sc8f&BD]-O*Q>:OHG]iOcGH`NlV.;rke]Q3l\tNG'o@1N/rsYP`_#iR`NIc +`59I1`PTL5_k)1%Mi`gTP`UcfPEDrc_SO1-_=dj.rVlfpqW@;IlMge_q#:6l(Zn/cU-iU3EcQ,= +EGof9DJMJnV+cPpK)C3!K)L<-Jq/JAYkJ(os7lTis7-'os2_p^[@c1JrqufRrW)utrr;osrr)j- +rVlfe\"]IcT\'&>rr<#tJ,~> +&H2Y.s82,`Ala>maihrK`5^`VrQ5/^`r3jh`lQBH`j8KE?_#']`kfL1bg";uaSsnFc\RqQ[KM`5Ka8_oKU- +_T_lMo^_G +s8NK*qu$Koi9Bb2q"+=\s5O"Yr;Q]q"TJ2jq"t$i#Q4Dqs6Jb0oD\alqtKj^rVuHfs8;ips8W,s +"TSJpCM*$g#$m#T6?$j]q/QdC8Z8FXqYL0jrr2p!rVQWZrX8c)rVGHk`5L4(rVulr$30ntoYQB/ +F`hm*GQi85F(?[1rW)ouqu@07@W6C3G^4U^GApas"TAE$"oJ?6#:aA(EcuSLF`DbIB0H_)!!!'* +qZ%$05](XgG'o;!o`+mhrr3)am.gSZs5O"pr;ZWgp5XNpHVn9nF)c8BEHc;EC.J'i6huK4KEm*b +M(u(,s8)]oqu6Kjo)A[h"Q/bDnCdg@#5nAqr;Q`rhu +!W;oqrrr;`WM$ZprVllriVj#\rr)cnrr)j,rquZkrr<#lZCeGKp\b$hrrW,qqsj[crrE&rs8Vs# +s8;P(A:]C)JdI'_?AJ)Wcai)-8_Z%A5^;%7,I!L6qO,o6YOHPcfRE3Cd`59I2 +^WXI,TR(s7I#3oGOctoiP`Vuc_nsC1_=dj.rVlfpqW@;IlMgb^q>UBn)#a8jH(B+T@W?F,EH,o; +BldrWp[LS'K7a#Ps+:3%s+1N)K7bbRp](*iqu6Bjnbrpn^UghSU\Fcdrr<#Qrr)fmrsJ`$lDgPL +S>tiOrr2otrdX~> +rr*B,qru(>;e):ar/:?`P]U4`lQBH`jfe]>(OZ@`kT:+c-OVVn]29aaN2B@`P]U1 +`R35f<*+Bso7I3rGYibN3rWjpDfP\r%s;#GTT_"Zc-+/Ib0A8S`::ni`l5sAbfIcEaN)9Cd`Au6 +]"?M9`lcHEai_rbdbF9hoD\8\oCVkO`jiFgaj%c?]tM>&`P;-9n*fl>n+-,Fo!Q!;_8OXDa2Gp6 +_S=Cnq"F@Jmd9E>q"W[U]"P\mai23<`4s!sg\(1.nDhj]f?)(S`lcZA^<+Oca9g#1aMu6=`lQCiFE:Ec?8?E*P6Zgb*euJVAmXKbB#hIsum"KnP;8+cO^g^;Iq0`r='Z +`rF$X`WF6(b4 +!WW/ur;Zd$kNh@3rUosbs5O"Yr;Q]q"T8)jq>C3k%fH5#s8Us@jo>>[r;$-crVuHfs8;iprr<#s +"T.jeK8&iK#Cf5+pLRQMF89he:3s'YVY^5fs82fnqZ$Tnl2D(jrVcTN`Pf^Fr;HZprY>J1q"2FY +H$+7SH$FLUF)l24/e/$crW)s%!!<*!%0p9XEclVPH?jI8'*&78!!33(!WE'5"Xn#$F*W"SF`VD7 +57n&!!!*'%"T/6.#=*-KG]nCmeF`e>rr2p"kj8*Drr:sV)#F7/s78saV`e+"F)Z/AEH-8B@;#0, +oS<:-j +"P;c&kO8BLs82lsJ,~> +$N9o%s8Vul[@aAmrVllriVilXrr2lorr`8ur;HWp#5QoAZ+Ki,rVllsrquB4c[E">N_X(nL.rr2rqrquWms82Bdrr;us%KHG,s8N&si5NUPec#IGrW)oq +rrLulrkecR_>_=O_c4RlNd,_pJV&K5Nf]?bP*:pJ`l5m5_8*t)\p\XOIPnC!rr;usoDejh!<2utq>LW\_6LSQYP%nns8W&ss5EtT +rVQTrqt&kIU'.!op&4gfrrE%K~> +&HD_-s8DEAJl,O\ccaJN`Q$oYr6#&\!64uV'#r#8b/q>_;HRYK`l#R-`m)iPm)TUT`P]U7ajAYn +4*>n$o7IC"EAQ&&<_mROEH1qu&TMkfH)+JqdEKYO_og'?c-+)sa<8LA`luZJ`Q6-@`QQ]N\\G_f +e]YtTb/_WHceQt'nFQ8Nnc/2^o_A=]bI4pq`5TU6`5'7.`P]=]nF,i;oC;JKos_0B^q.S,bKe2J +_nj(4qtfsWmdT];pA*IS]>;A#]Z&"3`lPm-]%ZiKnaPSbm+9A"aMuBL]tVV2m`>FNs2b2Zs2Y2_ +rlkDb)p#k7`7!-tU80\FC2e06EH#rCBMqXUgY@"%KD^K+KnFsUIgq$cNIc^n7FfT'`P][6rPniU +!QN4[b5]Nbb/hTBrQ,#Y'ZeG<\qj +s8NK)rql`ql0IO1s8;fos5^KAmd'`Krr3&trVu3_r;Quur;KcBKnB,R ++,59!JqJK/Ir/urpk)IaEHH)@E-?5@<(sEdrr2p"kj8*Drr:sV+8c*rq#:3jrs\o,s7PUJq:GZHqtKjarrW2tqr.PQs8N!!r;$9h +rs\;UlfdI%s8Dlmr;Q_H~> +!ri5trr32u_kNpcrr2osg]%0Or;I!%rquAUXgdHirr30"s8W&ss7?6hs8W'!rVlfr#5e8q<-EkE +q19oIIt`K,I>WJfC&%IV;/gGoCiaK9DfAWFVjgWXr;Z]ps8N#rrUBgcs8N!,s8N#trr;rT`P]XE +rVc`p!<)lr!T(Wk_csmn_S=(7_Sq:YG^4aeI=@!2OHPchPEa1_8=:3S9K+&GC"O]IYa)MP)trkY/J5o_oBOcs8W&srr)]UnGh>D*rH!;qYdX.V1_0R +F)H);Ec?)9<."H@_a]-.J:36%rdt'#$A*qZJ:g&Kp](6lnc&[gqu-Kn%K$2%rr2rpe@3-*Ra^m- +rr`8ur;HWpir8rUrVlfr$iKeuqSB#'SY +s8O_T)2grl,Gjbfn/G_oTmGhEbn' +I=qBM-&-o%J:i9/HsTe'e7CdYE-6)BEHuSB;+mgK4O;3m_SsO;bfn6!aF2(I`luZJ`Q6-@`QQ]N +\\G_fe]YtTb/_WHceQt'nFQ29mI0NCrl?h7 +ccF)TP#S*,6Z@$]Eboc=G%jBMdG(HXIuB2:NJ*.DK7SN%JVAl;KS4_)aj&)M`W!mV`W!mWaSs=) +`lQBH`P]d>b/;38bKS,5CHasT6&iM!`P][;dEfeJ`oG,Cb08#J`r=$h`l#a8a2Yc<<_l=iNp-E= +s*t~> +s8NT-rr)lsoAo$$s8N&ss8UOH#5nN$jQG[qr;Zfp!W2i_rr)j%q#C*?5DY#Fq1Ki@Mh?P3M1("2 +g&L]3:iUMtF),r?DK&.0Wa*mFrqZTmrr)l]rX8c)rVGHk`5L4(rVulr'*%k(oYQB/F`r4XH$jXD +?m?*aq#CBqrW!B.%VKHmH#@eD>7U]7"82^+""7o&Ed`=XFCd?@"pY,.!VcX'"?UOHH?OIjeF`e> +rr2p"kj8*Drr:sVs8O,:s8DRuT;PXsG&V;:FE2/=A:"%GrB,+MM2?b9JGY$"K*Ha^Jr4;eqtpEk +rquZqqtU'frr`8qp\t0l%K?>+r93\BmFVCC6lJ,~> +%fQG+s8N&se"<5Wr;-Hjd/FUVr:dXqWn7:MrVZWns8W)uoD\ah!<2ur$3'u#s7k/.KnP$QJf]K$ +K7JQ1I=a'$s7MgVE,]i;Bln?2Db#b_=jHjCqYpNprr;rdrr2rsrXJo,rr;utrSYZ4`7=hbrr)or +rVloT_#D5U`P9:4aMPlVIX63aH[0mbIY[HQ*W5s8nm@l]:LA6aDJsW9 +BlIO-q=j_DC3YDbIY3.LK)L<0JpN6$LM(c,s8Drro)AdgqYgBm%K6>(r;6N]^q@4aT^;UUrr`8t +r;Q]qiVrlU!ri6!rVm6'r;HN!U7\$MdJWnAJ,~> +&Gl;&s8Vu`XAhlWcH=>I_oC-G&B;]*H"^;7b&_SX4i +`@f!?bPUr!naQ&DqU`?m`l?*F`PB=,`lH'8\[U,ap@Ie3cg99*a2l9Cd(R08b2:8tb08#Gc-4C& +OJ5Z]F)5Q/G'.V<;cq;df-Y0\Mhlq@Knk;5JU`6)LOkM;Jll:0d`TPKa2e+trQ$2*aMu6Db/;-< +b0%`D_op- +!r`/trr2rt!pAY&rr3#rs3^iFrsShXlfA*GrVuooqYfUV"o\=AC41m3KH>MoM1^D4F^4eCrJFBp +DJsZDBle6*=KRX"nc/Ldp\Xsis69Ljs8Domi5NXQeboCGrr*c7qtTg#LO"#bH?jdUBkJq'#R(;4 +!<<*&qZ%oC%V9j5H#dY,$NgD9!rrB)!!*'"!<`N*"!_>oH[g6ZAMXbr"nhp+!< +!rW)srr3?)k,4i_qtU3fs8URIs8NK'r7[R?_"dp,r;QWms76-hs8;lqrs&DuFD,uTKDpK%KDpHE +H@^j&JV%rHp](5N:3LGjF*M>;DIlnJMeV\\rr;cnli-n`rr*B,rr2rss8Clp_o1+(rVlcrrVc`s +hSB*->/J1o^Vl1SG^"F\H['jbG'\^rNffKa_8+(+^V@L(EclkZG^b'dH$FXdMij0^QGp_]`k]O1 +S:,L+G^+X^I!^-cJV]>MQBnGg]=u>)_=dj.rVlfpqW@;Ih>SMnr;ZfmrQS3gk +r;ZN<6@F]BIYigYK)UE&Jd@*`IXui*oD/@arW)urrqZQqqtg9jrso&.s8MuqrntH%[&:V3qYpKs +rr)fprr:pU!<)lqrr3E+s8);`TUhdQl21M[rVqB~> +&GZ/$s8Vrc`*cGia2Hp4gALF-o&S-`f?)(S`lcZA^<+ORaT'@: +bfSG@5bX?1D/F67EHcS@@GB5Zd(22^Ljsf4N/*% +!ri5urr2rt!q>@+rr2updJj1G"o\K$eFVSsrrrE%q>('hl2Cqfo@Q9[NJ)lYKHGu&Kn=u1Kf\r" +qY$"HEH?5>EI)YC7%U5Siq`KDrVucos8V?_$ig2(qV]?2`7=eas8N!0s82T`_Mrr2p"kj8*Drr:sVs8O,:s8N&lHA/P0@rld/FE2AB>#4ejr;WBkK7eo4IJJO& +Jprr9LMK9LrVu]mr;6Wnq>C6l"oeDpq>U +&,lP,rr;uroX(ErnbE.]s3^fUs7lW`S$]EZrr2iqrr2r`rr)j&o%-!SM1^;0K)C6EJqS],J:`E. +3VDt@pQ83iEH5l6G]7IdSY8p=q>L'es8VEarr;us%KHG,s8N&si5NUPec#IGrW)oqrrLulrPMgU +`5KNJFF87VH$js`G^=X[I"m`JP)7#q]>)1dGBSCZFa&.XGBnIWG'\h"O,]IKaM>@,`0bakI=HEc +IW^'dH?jpiLl[UUWQ)`i`koRbs8W&srr)]UnGh;C*rl-;rVcD6KrC9lrVqB~> +%f-##rr;ogeok55[_g8*dE)@-%_fuj=D(Zn\]2b9aMu?@nAmf9bfn/I_o'^<\LVr[JUiH.JU`,t +JVB#>MhQe1GU1nbc-()hDK^5BE-ZMC6(!s'a48JN`6?6Bb08#L`l@SjIE'7Gbfn/IaN2E@bL4JB +^Uh&-bJq]Ia2uKMgsXsHoC;;=o_81![(X&b_8F4+^Vn15`lQ`PTI.`PoO%h#-d6n_qg\f?)(S`lcZA^<+ORa<\pIbK\Pf +@&Zrl-,#`l?0B`Pp!D +a2l3>aMY&q76*e"Qb:2X_nj@8ccsJI`o>)>b5]K[a:-,-a2u)e +"TJH"rVlfr#k[!-s8W)nq>A_@s8NT-s8VHFm.:8WrVcNds8D-["\Xi(L4P3WKHGqsJVer38C%88 +r:2RDF)Q/ADf'Q-D6A;FqZ$Hmr;>OQ$ig2(qV]?2`7=eas8N!1s82T`_Mp=B6C!;c]prqt^R!rVrnrr3`4r;6Eip](9] +kj7]umJm4bs8W#prdX~> +s8W,t%0-;)qSAl%ht[0Ks8UOH$N9l%qR`N+`q]T4rVllqmJd%`0E+P"H@g[&JqAW-KS4u0K6i!% +J;$VAs8W)i0i`40EcGr3EF`jeHAVLurVuips8;lrs760gs8N!,s8N#trr;rT`P]XErVc`p!<)lr +,i6>S_o0F/ai/XHHusRWG^=[]H$FUYH%:^3OGgfd^;74$H[Ym<3.67"G("X\H$XpmN/j:F_oKg. +Sp5[0H$jsbH@:-fH?jd`J;'5EXhhur`50:_s8W&srr)]UnGh;C+ohK?qZ$6L8$ALPDffu9DKKi9 +7aUr1q"KGIL4k87JUrFPKE$N0JVSQ.K76&[r;HHjq>^Km!r`,trr3Z1s8;cY^Uq%]SFZUXs8Doq +s8N#ss5X+Xs8Mrsrqud(rqOL0St2Urr;HQmrVqB~> +%f6)$s8W)kif%2NS%Ze[dDum=`PBO.J6Z,jZH()3a2Q9Bn]3r;bfn5L_nj79fJ7o"K78W1Kn4]# +I"-j-K85D5Gr2bXe'5oJ89/cqFDbo:B5FM\E7iaccbmZ?aND`LaMu6ja=YEN`luZJ`Q6-@`QQ]N +\\G_fe]YtTb/_WHceQt'nFQ;Ep](9(YJ7Za_SjF4rkpV7`l#[2_nNYMp%J7Q\A,qt_8=14^qe%2 +`59=(^r*t*mdU&Po\@!n^r+14_nj70`l>pq^_OW)]&35Tp$^kcm+9A"aMuBL]tVV2h8h=m`llNS +cc%Z6R724CFDu5BDdtB#bgFhhBL[#cJV])8K7SN%K7ei7H\?QW,Lk#\b5KQ`b5]N_`rc-42H`o>&Bb/hTBaSs?^`s]i)]n'6V86_PDo_SLcJ,~> +s8N0"rVlfr#l<63q>^Kkp\`M>&HDb0q#CBW`7JG=d=IZ/o4>3u7b +rV4o0E,9l=DgHJH3MNHJo(`.Yq#1-LrX8c)rVGHk`5L4(rVulr)ZT^0oYQB/F`D5:>S7DU"U,)4 +!s&K-"U##4r;]%g)f0bA;\'6 +s8NK+rVuloqTkk1anYc4s3glRr;ZfqkE3$Ukl:Y\rrDuarr)jMqtNU+L4G&/JqAW-KS+o/JU<9+ +KO"!Gr;HKC6>gO_CMe<:E]9Yf:%J,Zr;6Klqu6Wqo)AXgrr*B,rr2rss8Clp_o1+(rVlcrrVca- +hS@%H`5^'7G&r%QGC"OZH2`.aH?XU\H[^[.O2A`OWH!96F`i(XI"$BgH?OLYH$OdkMNu<'`4hFQ +H@1-aG'\@SI!Bp]G^"@TH%q0W_Sa7,`kh?Js8DrrrVG[Gs4mSoqu?]kqZ$SYD6%\^C2S03Dfoh_ +[JBk#s7oYWF+sUEs+:9's+1Q*Lk0u1J33W*rV?HhrrE&rr;ciqrso#(g:Ou+UV6O's8W#prr)lU +rr2p"rVZTlqu7#oZ)*q[UYPeHq>U?mJ,~> +s8)fqrr3<"kG;cpJ%=eu!`Pom:`Pf[3_7mIk]tX15r8Olk_Rd@s^qmt)_SX"$ +^q@1e^:h;Pp%\4+Y.VQf^V@\"\A,bo^V@In\[T2`hXg@.mHDm_f?)(S`lcZA^<+OSacHaMT +f[k1IU2XOZF*M\DFCHkgd*p:jd5)nPI=hHP%tfFaK7e]4JpDic-JQhfb5BK`b5]N_`r +s8N0"rVc]p#MeA,s8W&lqpGBOs7cHkg%"\+s8M`fp\t3Zrr;m#qYip2Ne)cVKH?&/G]-!Cp\k-f +s3NiSE,]oCBQ%"$[qDW5s8Muss5 +rr;us#Q4G\WhZrjrVliFrX&Q's8DnoVQJ5Br;Qcpmf*.a$2j`hG_pp#K7\^QK,oPuL3.6!U&"`g +qZ"o'G]%ASnI'[C]pAb-jrr;lps8VZhrr;us%KHG,s8N&si5NUPec#IGrW)oqrsdi#_o'F5 +VL*`3FFn^]rceBe!dAm7rd$/CI>*Eg`h.I&Ed`RdI`4s.(p&G'jrr2lmk4&E-rrr;6Nms76-b:na[MEHH&:Ec61mWVcPi +rr)lb:O@]mJHCFUKDgB-JVSZ.7IC$OrV6Bjs8N#trVucqrVca+r:J1E]X=3hrVZNlr;?Nms5 +s82cp$Ms,/F]i:Y_9C-Gd`;s1`6--jaT'Hqb/V94^;J4P2Kb"2IY!3,Jc:-C +JV/i8EFUYRda?4Qe#F^6EHHGMASP.gYZZb(e',kSa2Z4!ao]Z(`U_(u`l5sAbfIcEaN)9Cd`Au6 +]"?M9`lcHEai_rbdbF9_pAX!Y](kd]"5Vc\\#Mb +\$icS\+9+3\@ATP\&61u]XkP\]">Ve]=GDYZGY&bp@%;+cg99*a2l9Cd(R08b2:8_`lQ' +"TSH"rqlZo#N"P(s8W&kqpPKDr!WAss8([Lm/R+Yq=agfm/I%_#6+6\JU`?.pk0`?Lh:Dnq>1!c +s7WrjEHZ;BDfTqfWgd#^s82iprVufSrX8c)rVGHk`5L4(rVulr&c_b'oYQB/F`D+f6:+.-3^>k- +56!eG5".Ur3^,ko2)Jr\<_uRf8k;QJ8lAP\<)HCb91qlK8P;*IDe2jQ=&Vpj:Jt.m<`N:';,'b^ +>Zjm$0Ond=H\%:.o`+mhrr3)am.gSZs5Er!qu?Wnr;Q`lql+G2KiDo3E--&=:i5t*rql`frU:3ll1+?9d/X.ErVQTorqlfrs5O"Zr;$6grsel$s7uQls5`M9 +lfA*DrVlruqu2*~> +%KHG-s8;lpr;*aoV<7XRs3goGrs&>se<[2LpAY*lrW)obrql^"o07YOJqepSJfo\oKk+oiqu-No +s7j)kDKK`8D/jVbWgHcXs82iqrr;rprr2rgrr2rsrXJo,rr;utrSYZ4`7=hbrr)orrVm>`^r+(/ +_l09&VPBf]V#I4gU_fc!U84W\S\MkGZF%'GYdCgB[^*!AZa0S;0=.sgYb&AF\>HU@]"5D\[^ +!W2]krsJ_rj+^Z7T=i=ib0e<1`sTW#Q:kCsWkld!_sb\Haq)S4_8!b(e(6C)Jqo#/K8'5U+G"if +=&d`"d)j/Ga-JGoGBA"IDf.\3O[@@BaihZBai+/#"3AL%`U_)!`l5sAbfIcEaN)9Cd`Au6]"?M9 +`lcHEai_rbdbF9_o_74qgZ%/Jk2u[+s5kckinrSIhVmACc118aj6H.(n*of6o'bi5nF#c5md09) +k0N2cmbI0uo`"P-o^VJ@m-jK6n*oi:nF6"ko^MG>hp:Z@aiDKBb0[i9ai`M^,K@UCbKS/Rcc==b +>+U=GDK^#@D/2F;e'Q7S`k^'N2-PQbKDpE&K8';X$%d\TFZ7Uba2l@"b5]Q]`Z*(A`Pp!Ecd0eT +begTF5qt&^:qF]laMPp9b08V]'un>;b0A2Z_o&pD +s8Mlps5jLKrr;rmr6tZFr!`8ts8DuUo'HDQrV63fs7-(@s8MoqqY^6cp.KmiJq8]/KRno2Lk(>; +@Y"=NrVQNhs7ol8Cj'o=F_XU;V-q6prX8c)rVGHk`5L4(rVulr&-)P%oYQB/F`VPBE,]]3D>e8L +D&$r*EFs<8D/`uuE,fc2Ci464D/4$.rc/0^G%GN.AH$rVHHkrr)Y:BodjtM1L;3 +Jr+o.Knt:Eq#:L?k +s6K"@mcO9DpAFpi!ri,sJ,~> +rVuos$N9o"_PaqH]C2IkfEW^8e +oD\[f)#a5OArD+eLk(&-J;8K&JV8G8p](9mrquNis8Dut"9&/prVca0q9?RZ[%ke>rVQWprr2lp +s8W)sir8rW(&e(.rVuors8N&-U7\!MbPqP@s8;lqs*t~> +s82`o$23bX>uQ]l`QcQRa:HP6`PfX(>?P6r[`ut7`lIPh29*MQ^VRt=gcp1CK7&6"KnP>;M1'r# +9k2c$d*'MHbK`4UATr?=G%F(#QVsp4aN2B@`Q#praof`)`l7Mi-cX-Fbfn/IaN2E@bL4JB^Uh&- +bJq]Ia2uKMgsXsHoCMPDoCMYLq>C*fq%ESorr;lqqtp3^o_/(VrVcZjr;?Bos76*epAXt@p&=[_ +q"OX[qtp?jqtg*_oDAC[r;?Bgs7?0]o'bu)cg99*a2l9Cd(R08b2:8u`l5p@c-4>L`QlIkO.T6d +EGoo;DJ)Ynd`9&9`R!/k@pt7CI>33&KS>A8KS"i&/Bc&S`PojkaT'E_`>ctEccs\S`l"ci4tnuX +JAKLRb/;6?bfn/JaNEJ_s2P)[s2tAd&'N,/\8G;@:0sO^rqlQ`q#:;D~> +s8Mlp#P[-:kPtSWr6tWQr;$*es82iljmVsDrqccsrr;Qg0DtkMq>(!brqBH8MgpV>IYWK1K8"o= +IVs]1q>L!dqYp*]q/pq!EclD:?)Eg"pt>WSs8Domi5NXQeboCGrr*-%qtTg#LO&c>s)e3[rH/'^% +s*/BD0Ki&4E5I_EH?;KH2_pdGPubUEos8Mus$2aDks8V3MrqOtArs&H%s8;fps5*_Vr;6Ejs8Vrrr;Q^%l0Rd&h +uE`Lr;HWrr;M3~> +rVuos$N9o$g7k7Lo_ngie,KCI%fZM-s89U$ZI&LKs8Mupnbt6@s8Vros8;om4ItiK' +I>iMiJb]-Bq#Cr;Zfms7uX0r;HZqrVZQks8DusmC]j=[@Za`r;6Kms8W&t +s5 +!W2inrsAPo]O=TQZGk#0e&W-Dc,doC`4pn*=`q0maN)<<`q%2)`5^!9_T'L>e3Ii-Jr>5*KS#,; +M1^_,=CKS#c,7fGa2-3^B5qa/G]RCqQ[i&gc2P`_`<+'#aSs?^aT'9Na`RN#J]#)S7fB?LPH\$KsKS>)=L5L\;I6jpSb/VTI_o_br+NhpN`Pp!Dc-=JN +_6oJ'2*"&iVT6s)`P]^!m:s8)Harr7K~> +s8Mio"lA=ms8W#FrXSl#r;Zfns8UjBl2U_]rr2rgrYkh4rVuomrV. ++>.R2FTH`4rd=`lrHU#AH$"CTAnOm`1LtKJG'\I[G^4[bI!U$^I!:$eD/XN*1*0),H$4C_NR[V# +rVlfr"6f+Hrr2rNrr;aFpN)=q@Ua@kF*VXuRJHLPrqHHmqt0bEFbGL#JVSc0K7ec2MKqY[s8DZk +q>^9is8W,srVllqrr39&nFQG:jT#2Xrr<#o!ri6"hYm]Vrr2rtrr2iprs@cPlfm:+s7Q +rVuoss8<3%nu/:WkPY8+rr2p,rr)lsjdWHbqYgHns8DNfs8Nu5rr<#ps895oMM6>3J;/`*KR\<& +HrO8CrVQU*rn]eWCOL&;B0i5o9]Q-Or;Z`pqu?6drr;us%KHG,s8N&si5NUPec#IGrW)oqrrLul +pqujC%Dg$$_90WRQGgVW`5BIk^]D9erPCD-^q.M'^;m[?R)R.c^qmq,_ns4(^qmn*`4s:2^;\=5 +UQ;Ora2#R0_=dj.rVlfpqW@;Ih>[HSrr2os0)PM>EJ^Su?>sLsG\Tm=qY'shr;Zfpo_C=bJqef+ +KmnZ)JUiH.G!tm#s7lWirr;lp#Q4Q#s8MuprVlg)rntW$[\pV7rVZWjrr3*"r;Q`RrW)urrX8c* +qZ$BJV4=!JWqZMms8E,us8W(K~> +s8Dlq#Q+AOCfXZ5`6lX?>.7^W+@8`5_Di0uhAO`QQNIcdA"8L5:;:I"?m"MM?\> +G="C9bKIrJbK\>\_CfP;HZ!h<4dVZqa4ec:`W*sAame>bQbkh;5n*TK.lg*s: +miCf^kOJ02pAX^1]A3&QmHir\m+9A"aMuBL]tVV2hT5`:rlR.=_o'@5do_$l>[MJ`E-,e^L>'V` +_7\"BdaI,"A9NELIY`W4LPCM=M/=QXeBZ(]_og!=aN4A's3)J*`Pop@bfn8Q^oi&Z2`sE;_8jdE +_SjO\obfRr9PZUXO=aJirs8Voiq>UDE~> +s8Mio#3b.(r;Zcre,B^PpAY*lq>L?QjQQUC!ri6"nbt3ArVZTnq#BkLLOXu4Ljk,.N/3.BLNZkt +qu?Kfs8;ifr1HW'GB%MA@8n&%CNXfFrcoE.I!g3aG'8+UH$sjSB2B9T2J[;RH@1!_H@5^A(jpURG^4gcE,T)E%2)Zb +G]n@]NR[V#rVlfr"6f+Hrr2rNr\"6Jq<1JMW((lUCi4B<3Ka7=r;$Berr;ch9m)DKJqJl7Kn+]+ +L5BGsp%SL`s7uZns8W#qrrDoprs7r]o^L6-s7lTmrrW#kr;Q]srqtaSs8W&ss83?*s8VTKlg3`r +s8Volr;?SF~> +r;R-'rVc`o\Yu:Oqtnh@rr3B(qu?]lYGSVequ6Qns7?6is"FBLqu?Qnnj8.]Jq\r-L4+r3ItE;p +>5J3trVHQlr:Tu[7W_1j^r"") +_8X=/b/'s2M7(C>^;e..p&G'jrr2lmk4&E'rr)jNrqbeKUn^7qH>@A;EAisXs8W&tq>^Kipe>jf +Kn4f0KnFi'JUrPbM"UQCrVulss8;lqrVum1rr)fprVufl`P/^aRcsbLr;ZTlrrrE#rr2rth>[BQ% +fHA"`1WZlTtC4$s8W)urVh<~> +s8Dors8<3#l@iVKMS[<\aT'Bnc,[c?^qt%f=I?Vi`-`59O;eBlmq.XHJrMMZV0H]akD_=D/sN0FCK3\?u+B:b/M@!`otJe`l5sAbfIcEaN)9Cd`Au6]"?M9`lcHE +ai_rbdbF9_naHDCqsXLN&+&KOq")2.bkL_tmHa'&li$5MlP/aQoC;DEkHqSDs8;HWmHNftlKdd8 +m2bNVkO7p1q=)n>ZfV&SmdfJcm+9A"aMuBL]tVV2fuP5S_o'^?d)\)4T0[IAB5)C*/U>gd_naR< +cd^P$2.6E3*.j$)LkC>7LOi*1cd:7ecH+#FaN2NHbfn>U`l7ks'$%GU=@>,56D2-7bKn&EaND`N +rl3g9s2tA]&]i,:ag?Cl928lZk4eoUqtg0gs*t~> +q>UWss7k=0mJd.7rW2rnrr3?$qu?KBl086JrVuBd1&1eFs8DleoLOacL4t;=K8P/2MM4crp&"R[ +s8)]ooZ,lLGAqGC9mrqtdTrr3)uqu$Bkrr70OrqZHjrVqB~> +rVuiq#lXbOUo1d"qu,"Crr3B'rVuoqe"!V[pAXpfs7?4Gs8)clrr)fep.:$dKS"f2Iti9!L4W'g +p&4gas82cpo>f]FF)Gf3F`'@>UI4\Rs8N#tr;QNlo)AXgrr*B,rr2rss8Clp_o1+(rVlcrrVca! +hS@%H`:Uq^`5BL2_TKu^LmFUP_SX4.`5T^5_SlGms2=rV'?%f-N/WdjaMP^1_na7/_8*k$_#D4i +_S^KfJpX&-:$grr<#trr2irrr2rsrXSu)qWkVs[]lq6p](0kqY^Bnrr<#Wrr;os +rr)j)rqu`^Y+_J\T@s/ArVuos!WW.L~> +s8Dors8*$!n#']$DSa]@&BW2;`l5g2P"f/%Xhhop`Q%Mj1WI&IaOSu"fA8H7I#3]6L4"c*J;&D8 +K@.u`]ue+2e&S6(/p)#KC2n5YV3`XUeBu@W`r*pV`rF-[b4As6Vo:mHs6*mHsH7oC2=\[^soao^D>:kNqL"lg=00 +mHs6*m-soFe@)p!k4S35o\e'^f?)(S`lcZA^<+OSaT'BaaMu7!`B1lWe'2K>QYf]@D/sN89.A%J +^<=O8cH",Vf+_5'Jq/K,L4k;8It;_cg"b-*bfe)I`lQ +q>UWps8UU.ir8u+rW)orrs&E"s8Um9hu3QTr:0b@s82fon[1Q%Ljt26J:`Q2LP^k>:g*emp](9m +rVuLs9QYSpG^+7K<._2hT(i*Vs8Vooqu#^X$ig2(qV]?2`7=eas8N!'s82T`_Mrr2p"kj8*Drr:XMs7n5GnO'OZ6uHsaGAh\B3`5qNs8Vrqs7u6`EGg8XJUrE*LPLM: +N/Ct+WW3"pqu6Tp#6+Msrr<#trr3/mmIg8&rr2otrVlg%qtTs`s8W)rir8rW%K-&!r;Q`qs8V3H +lKI%%rrVrjrVcbH~> +rVufp#QO(aU8[SXrR1ZUrVufps8VuhVQ-rdr;?Tpo)A[h(]=71n?bH&Ljk&1I=Qs%Jq\o.:0ISl +qYpL.rr;Op95o&bG'7kE;Lk]^TDJK_q>U9knG`Ferr*B,rr2rss8Clp_o1+(rVlcrrVca!hS@%H +`:UtH`(S7jb*\*LQ_2=8_8=(-`l5m6`5KX6`5KX5_o7:fQAqKU_o'I0^WOL4_SEt(_o0L4`5oou +O,9'\Z,+8l`kfLas8W&srr)]UnGh5Aqu?]o/cY?,XJA*uDfKo:F)!d9s7u]pqu?]noDETFIt<-" +J:WE.JV8f4>0AUS+6To(r@_s8W(K~> +s8Dors8*'"o>YKH?F@f#a:H>6aiMN@]s1mK?^ndJa2[\k0ZM2Nc.CI;.qF$QL5:M:KRnl4KQo^I +gs*g^`l5p@_DX&XGADPLChlt%VdP&$b/3tqrPeu]b08)Po>hum`Q$-GaMl6Aa2Q6MaLT's]@G*E +b0%cHb0Skkg[P15!qc*Vqt'dY$hjMjq8^"Y^rc?BnbqqUnc/(XmJlW3o(2ABYdqW_cM$l"o'>H- +mI0N6naGr6nF?/Hp=m`lQ +q>U`us8V*:i;EWTe,K=G#lFZ$r;ZcNk4S`R!W2fcrr;gHs5B,>DM*OpKS,&8IY`T/LMa:-jqu6Kjl2D(jrVcTN`Pf^Fr;HZprWrQ$q"2FYH$+9'G5l^b +EtiV/@OM`p!rr2p"kj8*Drr:[N0E:qJrqMCaWJ+SlF)Z,?BgD=ir;-Hnqu-?jp[WE3KS>29 +KS"f/K7&Q1F[q5_qYgEkruV1:qu6Wpr;-HniUcp4df9:Gs8Drqs8;Zdq#CBnr8R_Trr`/nq>:-j +"nq3HlKIa9rrW#kqu-PF~> +s8Vuqs8N;s\"9>Hq#/Y?"8r,trr)j#c(D;fo_ndhoDUKEr;6NPOATnJJqS`+KS4W(I=QudZ1n7s +r;QHjqu$,<1gFj=E,TW1:8F4IoDeagrr2lprql`brr2rsrXJo,rr;utrSYZ4`7=hbrr)orrVloT +_#D4S_Sa7._YM+K`"U&-Q]72lP+p"4^VIY%`5T[4_nuDh$Gsg%`J]bMj'*ZZc9u!_8a=as8W&srr)]UnGh>D!r`,trr<#t$ig,(s7hF\VL_rcErKr"AjH(i +rqZTorVufqq"&Q5KSG89K7JN)JTZWrEC>QUq>UBn!<2rq!<2ut&cVh1rVl,l]"5#:q==Rcrr;ln +rr`9!s8V'W!W2fmrsSi+p:d/lTV8pnrVl`prr.E~> +r;R0'qYU*6'IpSJld`fVI +]u\:=b$H/iF+ACMAn4]8Q93RH_nsLmb5]Ng`l5p:aND`Nb4E[q`l5sAbfIcEaN)9Cd`Au6]"?M9 +`lcHEai_rbdbF9jo)nOSnalYJrUU`squ4mV`P9.2p[e"HoCM\Lo^qbHrp^Tlp@e0k^VRq+ch$_t +na>c8oDS;'oC;ABpAO[=^q7In]&rqena5Jbm+9A"aMuBL]tVV2hoGoBc-49.`W+"5bJhTLdVr9] +ITL&tF*2S5.+-MgaihW8_SaaGdp>juJVA`-JU`<(I#*2_0ketXb5TC'aND`Nc-48K`Po[2`kn3B +2`*We]u84;`5BI4b08)PaSs3;h=0/pA"Xes8DutJ,~> +q>Ud!s8ViOiTgICqu6WprW3&urqt[QrVm?%s8;iqs30rls8W&or:9h>r;ZIM7=C&DJVo)0KnY;< +L5BJarqcHis7u]kr;Z]iAl!AgDfTtmOf2ECq"Oabs82fmr9=1gs8Domi5NXQeboCGrr*<*qtTg# +LO"#bG^4Q3G5c_YG'.kGCh#%3#Qk&0+DQ+JI!U$XEH6,DH$Xd^F)4kp"pk>2#rld@F*MnLDJjB5 +F`qnKEcZ&*5nX2%!!!F"Bm4fGIZ;kLs8Drrrr_KOq#:9mg&1'i +pAb-jqu?Qmpm4j$J:rZ0Jr"u9KnYD6N'I7>qu7*&qtp +s8N0"s8W)ts8N?"d\3DLp&4pirW)usrr2rSrr;p,s82ios8;GQ\$bJsrr<#srr)lnr\4nCN"'/F\7anpiF!5\ZQ3lAhN^qde( +`kILiP*1Z`Tu=d__oB[7`l5j4_Sa=2`Pfa#OGT*]PEiMh_na++_=dj.rVlfpqW@;Ig]%0Os8Ec0 +s7u]jeO,Y!9Q5)mDf%bgqY^9kpAFq*rVlVZ;KdPBKn=l1ItiK(KmS\>r;Q]qs8N&s!W;onrsnu* +f=JT!Ut>G@qt^9lr;?Tprr<#WrW<&rrr)j,rVuMiT:DUOd/*eAqu?Zps8W(K~> +qu79*rVucSVHd,N]ZA=:`Q#p=b1XiTaiM`Ha2#R(Zr1@?U;"Rb`lQLk^V7IoBS*ccO>P^s0grPK;YI7uLM4,:Ytb/2-;_oU!Cbf\$+`o4uP`lQBH`P]U4b.;:d +7S@'^li$VPrVlrts8RT~> +s8E6&rqucqs8Drs#MJ2!s8;Qis8N#srrr5nqu6Wqhu4,_r;Zfps8V]@lMCP\qu6NnrVl]l')/_6 +J;f,4JqJo3L4b>5Lg*i.rqc`rq>(!tpionWF`V;"W/O6frqlcqqu>RQ$ig2(qV]?2`7=eas8N!A +s82T`_M +qu?]p&cVh+ntW(do`"mjrVZ]ns8Drrs4mVRs8W$&s8DQ3T=!5Irr2utrquirrr;usrVm`.YZ,t* +JUrH+M1:2-KmJAQ;LJE=qu?Kkrr<#ts8Xc/7(OHYol[DTr!_nj.]s8W&srr)]UnGh>Dqu7<. +rVQWns8V^U:8F"HDJsDdSc&EbrVm?+s7lWfXY=gMKS>&/K`6Q&K*-^Rr;HTo"TJAtq>L9l%K6;* +pVF;I[& +rr)]n(%'F^?"^LfdE');`Q?EJaMu6Bb/hT[a>V2\_o'R8\qip]L9nY6_oBsGb/M-1`QHHMb/hZH +aGiW4M1U/)K8YJ@JqeZ'906G-b/_cEaSs0oaNDiVbum_UG]mXrU4bG+e',hQb0'_-$d-Q1`l5j7 +`lQBJb4E]&`l5sAbfIcEaN)9Cd`Au6]"?M9`lcHEai_rbdbF9_naGl8oCV\Hna,Z/kj\E9a2Gs=]?^!9mHa*+nauVJqY9IHnFle:]=c%t_SEqNq=X1D +iQplBaiDKBb0[i9ai`J]rPnlV.`TWScc=,Ubq2M`P"'"tBL.F(bfIlLb/M31^rOa?e'o;4\Mrqb/h['`r=$Zb2(,Q`l,[:cH!lAa1\9M +846 +s8E6&rqlWns8N#t"mXk&s8DKes8W)trr`)lqu6Tpir8rT"9/?#rr2p%cKX6ds8N#qs8Drqr;ZX1 +s/f7PL4Y#4It`B'Mh7"&=4I*Hrr;foq>Lp'pKMB)YFa8IteF`e>rr2p"kj8*Drr:sVrVZ]pr;R3!nD#d$ +M6'oqGu]UprVuiq(]+..s8;iqo_(n*JqJf/K7o#4K854uMu*GOrr`,lq>C6l#3b[Lh!+k=dJa7H +s8MrprrqiPm-Wlqp&BO~> +qu7?/rVlfppq*3!gAV!Hr;Zfps8;lqs5*bRs8W$-rr)fWS$B9^rr<#trVc`os8N#qrt25,IY!$$ +LOY#+ItrK0@oC_Lrr3)rs8N#srtGD1rVu^tBQ%QLUnpRFk4S]Qs8Mrp!<2lqn,E=drr*B,rr2rs +s8Clp_o1+(rVlcrrVc`shSB-.s1n`Q_u@Re_o0F,_T0U7Ss,:tNf]iOc?-W`PTF+_8F:+aM,C2_o9cuNfB3`OHu,n[)9i!`507^s8W&srr)]UnGhAEqYpTqrVlg+ +q=EDr +s8)fpqYq<)i.kKAPKD(d]t_P0d)sGL`lcHF`nnd2`l#^:b/U2K>$[N,`Poj@c,df;^W"@?c-",K +bhA"EI>*<(Kn"l0Jr"i3>!B1:cc3uBc,duG`Q$!Dc-+@#?ug*JTV+@tb0A/V`lS/%$d?c7`l?!8 +`Q$!Ab4n*TN1nb);Ho'cGCo)%+S]>DD"`PB7Oq=X1B +iQplBaiDKBb0[i9ai`M^s2G)YaSs1OaNDcSbf%H3+]4,t;dF-*CXp\N`lQBJaMkp5`QZ9@eBHZ, +EI*(fL5:S>KS4r*=DQ7%^V@\-cd0hV`5':1aMY6:5r^kdFh>Q?a:ZD+^s13Jai;<7Ki(3) +s8W,u"T89!s8Dor$.Rkrs82fqs8;forr`)lqu$Hnir8uU!<2ip"lJ>(qu?Wfrr;orq\8k)1PgX= +L4=l,NJ;q=LcP/*p\OphrV?F'qt]>DDe3m_H;cMfq>Bsbg&;BZrVcTN`Pf^Fr;HZprYbb5q"2FY +H$+7QG'.kIF`r"WG'7tA@P/?!qu@?4,\hIHG^4OVF`_hMI;s(0*!cBA-Njc5BQ80?H$+7NFE`"O +EH>_T('OjB!!3-##Yo>\FE`.meF`e>rr2p"kj8*Drr:sVs8;ips8NT,rVZ]i@6efuWi/3nA+KQt +rVdW2s7Z?ipAP$bFDZVeJV8Q#L4Y/8DI*,9rX\r+s8W)qr;6Bks6T%Ao[ +qu?]q&cDY,qU_=6]`.s/rr<#rrVZZns53hUs8W&urVld$qY@P%[FYBs^L44l3 +ItW?1KR8B)2=^M9qZ$QnrVlfps8N!.rT6Od@rJ)e;_FR7rVHQoqu6Kmn,E=drr*B,rr2rss8Clp +_o1+(rVlcrrVc`shSB$+s25>`_SX:-_oTgM3Ds8W&s(&e.1rr<#n +?pAQoW2)Ua@IO6prr2lprrPeJeA&c_h0s8;osrV?,fTq%aJbk_84s*t~> +s8Dcn&bk@c?p-CKk5$[D9l+aN2C#`<"!"rQ6n:bg(q_H@(F% +ItW?4Km\N&.G3+j`lZ0>ai_cG`Poj@bf@oI2JQc>SodOJLYC.j_SuMq"Nng,`lA"s"3/F)b4o_?VD]tVG&`5'+3p[[e= +mHsH6nFQ)An+-(b]"H#&_SX7(d/NY:nL!bqnaQ2EnacJJg;(87^VRn)^q](.p$hD.cg99*a2l9C +d(R08b1OcLb/hN?aO/R33cIEdUH)7HcIg@^aSj79aMu?:`5f[;g"'A4G_L[&JpiB+K7@8ufZhFP +_8XL?ccjJJ_8XI:^Rnpr2+0O!]Z&@@&]VT)c-=GQa2,/q:IRcTV!7[Ks*t~> +s8W&ss8N)trVlrQguA;4$3'l$rVuopq>:*hs5X+Xr;6Ktr;ZZ9nEg/Ss8E#rq#230qtp0p\OpirquG*Lk:>7M1^S:LOaeBk5,)Wr;Za(qu-Qos8VrZme,`,rr:FG%K6>(qYgn*&^%s*t~> +qu?]q#lO`%qs1;UY4;Gi"9&,qrr)itrr(jUrr;rq$i^2'rn*UH]^Yt"rr)lqrr;uurr2p8qu*!a +L4+],Jq8W-JV-^lqY^Bnrr<#qrr2lrs8EQ/pAXA44K,)BDeq;Ws8W)ts8;lps6fmcs8N!,s8N#t +rr;rT`P]XErVc`p!<)lr"5^i2_u.Ff_ns7+^V\"0ad\6GNf]HeOcGQt`khVjrPSZP2oi[lLQn$a +NK9 +!<)]m&c(Y0F')q[aMc!;aNM`KaN2BWaT'9Zb"]mp_ns9V9O_\2^WOd>_o9Xb/hNSeg`_XTfHau.D"3T]>MY.r5],_`Q#p=aNDZr +a<8LA`luZJ`Q6-@`QQ]N\\G_fe]YtTb/_WHceQt'nFQ8InJ1Naq"FR/^:_4t`l,j1^cdH;nF`Lu,VJThud`KYQqoUV7bJ:s;b0A>[9lkE,KSP2:L5(%p1We:o +`l#^5aNVoRb/2$:ai_)@82i[tA@B4le&W-@_8sgCai265Qr[$W:1h<-qY1#?~> +s8W,urVuosrr3PrgZ//1s7lHfs8N&ur;6Bhrr;$Xrqu]o%0$/(s4u]%p&G'ir;6Kks8N&us8Er7 +qtpEmnm-?mMM$_:I=R'$/bJi:s8Dutqu?Kk&,cD)mX9bjBOQ+)E(Tcss4RAZs8Domi5NXQeboCG +rr+SNqtTg#LO"#_EH-#AG^4U]G'.J'-4^@c!s&B%!oF*`epo`+mhrr3)am.gSZs53hSs8W'/q>^$E5B^m_ +D*tD$8aZQUq>M3-rr<#trr<#pYuc($IXZp)HB*qkLA^lErr)`pqY^?rkjRp1e,B +!<2`m#laP8T;^Bs)3 +F,,R%LkC))JpJs&7J^;7_)`P]U/ +^WQ6=mHa0.nalACqYBj"]tM5!_SF.+^:hndrqIl7oC;26n*'ZKf"o;B_Sa=1_SNptjnJH;nDhj] +f?)(S`lcZA^<+OTaAL+(bf\)Qc-48A`l#VIF)c895cpb@cHaALaNDZHaNVoScGdW;`mW>^NB_$) +IKCY6qV`l5p:aND`PbJD-@c,ZVZ4"`cbU:SFce&W-@`Q60Fahu-2EDoS->(YQ$qtL,@~> +s8W,urVmW3qu6WqqqU3!s8VifqZ$Norr2imr;Q`UrW2rrrr3H,s8;Zjs8UIUBH;Q6!ODK9cB<-7j.r7_,Xs8Domi5NXQ +eboCGrr*c7qtTg#LO"#_DfKiAH$Xd^G&1V2"U"u2qu@<2#:+)/H$Xd^G]e%RBOqYR&GuMW!<<*, +2JR/NF`heKFa/1TCdSd9#mC;/!<<*(!";YBEc?)GN7@M"rVlfr"6f+Hrr2rTrr)lsrr2p+8Rup[ +F`1AhY).<$s7uX1s8)`pqY:!cqXmR'LkChAJ;8Dp=o&*tqu-`rqtU'arVm&tjmr''i;Wc'rXAi$ +rqu]os5EY@l/DF0s*t~> +!<2fo$2so(q:hO7^[qL)rWN9!rVlfqs8W&Wrr2rsquH`qrs/N%lBSlto_eXe!r`&qrVlcq)>jF1 +5(\$7K78N4K6'QWr;Q`rqZ$TfrVc`nrt#&-s4C,EPX9;/DKAWqqYg\h@%KHA+s8DufUS=3GU! +rVmi8qu-Qpr;H?OZsH^UYf4],bg+5Nb/hZD`lcH`aAKsr`QZTQb/(j4a3(JR@WJNhb/(g0b1"nc +aMl->b/h`NbKS?ZG'f7'I"6j"DEHk.b/q]9b0.`Lc,dlA_oB^8cI]]oT8bj>BQ&)h?H9asrl+oW +kf=gb`Q$-GaMl6Aa2Q6MaLT's]@G*Eb0%cHb0Skkg[P150CS;olgOHDrVt'O^W"42_o'=-_7dt` +nEoQ/n+62As7QAuZd-G+_8*k'^:h>/p&=_6p@@b?mI'WBhnHYAai2*:`4j.&]B/e[o((kfm+9A" +aMuBL]tVV2hoIq&bfn5Pc-OVT`6$Ra>]k%.APQL0H!&D8aN2NHaN2TMe]YkI]t__6c-J7WKn>83 +JV\>\5hX`6b/hTBrl>Ync-F2HbfRAK4u"oV@_TRsd)Za>`m)WC`N<6?8l&ulk4\]Hs*t~> +s8W,urr3c5s82`ns8V6@kl:\Yq=jpfrr2rsqu$Hni;WcV$NBu&qtg-gs4lW"rr<#rs8)iprr)j9 +rquZjr;Z`oQLp$s7TuiWFKU7G]InL7]Z.Zr8%>[s8Domi5NXQ +eboCGrr*`6qtTg#LO"#_EH62HH?sjZC1J[s!!!)t!##D=,\qULH?jaYF`hG.%KQP0!oFF9(to`+mhrr3)am.gSZs5EtSs8W'/r:5nAF*N"% +6h=QOXAIL@q#1 +!WE#ors8T'rr)2aSZh>XrsJ`)r;Q]orr<#srSmhUs8VutrVlfr#lOAiWi3Jaqu6Qo!WDrqrVulr +ru1n:rVr3dLOO`.KRnPA2Tbptp\k-lr;Z`orqud,qZ$.rPGM]#8p5)jE^[&)qZ$Eks8VZhrr;us% +KHG,s8N&si5NUPec#IGrW)oqrtF8)_o'F3`5KR0_8=((`05RoK8okk$':1/OHQ6U_8=%d_E,HKH +?aphNKfQdOctrjO.sY4_ns7,`59R1_k_L$IXR6@OH5NaO-5U2_o9O4_SPpFs8DrrrVG[Gs5!\Or +Vuj-s7D=CDf^1p6Ln3CW(tt>qYpNn!VlTirsR^EIX6]uJ:`B09t('!qu6Qo%Jp)&rU\7I\[eHlp +\k$:rW3&urr3<(fqFn)R],?Jrq?AB~> +rVmi6qYU6ir;Z]cc>QTiUVb:&c-!uGb0%fF`lcH`a;i4=`QZTQbeq9Z#:o'cD5\@o_mbK\,J`kf@%]B/e] +o((kfm+9A"aMuBL]tVV2hT5Z>3c94e4tYVgP+=P8_u%Llb0A>T_Sa@6aj/AI,^"?f +IX[$(JiT4JbkT6ibKe,NbeKfY1b(aUSA<+]dDumCbJq`I`P%@38kN!)`:*I!p]#a~> +s8W,urr3c5s8Mljs8VWPiW&rRr:pRf^rVcTN`Pf^F +r;HZprY,>/q"2FYH$+1LFa/1XH$41@/IVLWrW*W5!!*-*,\qULH$FLTEcP\(!!E3#rW2rt%L"b* +Ecu_RGBIq@Ce#0Cq>_'-"T\T0:2Y,nGC>J#o`+mhrr3)am.gSZs5EtTs8N!.q8u$HG&1qWo(pR4 +Wg[Ajq>M-+s8Vros7uWfL0]LkKR\i8JpUGgqYC+"r;63bqu?Zqs6&qFkMZ@>d/FUSs82chs8VET +meQ23s8Voop]#a~> +!WE#krt+t*ZBO-,WhPEE5j`P]U0_"Ia-rVlfpqW@;Ig]%3Nrr*K* +b!C&gBPTj(qoMg7QXKUfrr;rr(]+13qu?]os7_4OJr,)-LP:2&;tU"es82forsef%rVPrn]"4oH +q>('jd/FIRrr;oqpqihhStNOfo`'F~> +rVm#sq"t$grr3T-ieUf9P.Socbf[uIb0%fF`r=-@a;r:>`lcQNbf.Q@a325s9j2/;_84%/bg4V[ +b/hTBrl@%?`5g'J.sHZ%K7J>u?r;9t`Pfa3_9L-Kc,dlA_o9X@b+VLtKhgBW0P>EF?6n%FaMu6= +`Q#pqao]Z(`q%3!`l5sAbfIcEaN)9Cd`Au6]"?M9`lcHEai_rbdbF9_oChhHmHa02qZ#-`^;.S% +b/VHB`5TO)a7JffmI0Z?qYp?&[)g8*a2l<@`50=0^X;`Jo^hV@n+6#Fi4Ze@_nj:6bK.]B^V.8@ +p\47NjO*>GaiDKBb0[i9ai`D[s2tAc'$AGDW%Vq+AR?n6fsj\GLdKD2rl4rY)Tp:NahPX1`QlkF +9RDYNJ;Ar,Ccg"gb/VNDrlPkp`l?*Faiq`1MF&jJ5@;4JaMuZL&B`8D`Q-!;Z?&u2:L0"@o_nL` +J,~> +s8N,trr2rsrY,&%qu?]Uk3E!ErVZKkrr2rtqtpBmir8uXs8E3"qt^'dq>UBrgu[f"rr3)urr;ln +rsSi(qu?ZqqYQ^gK7NrU.tNA"9`"\^s8W&rqu6Tms82`krVuIhX/8\SqYH"9G'ItC:Aaqbs8Vij +rSd_`s8Domi5NXQeboCGrr*T2qtTg#LO"#bH?X[\F`qS7-4p=W!##J:"9AN+$7';)H[0dREc>A( +!Wi3!rri9#%L+\(F*DqZH#[Y95S!r"qZ$Tsr;[618T&TqG(>Y'o`+mhrr3)am.gSZs5EtVs"s`Q +s8Mlb2gB=cBM\F#q###hY%t<:q>^KlrVu`oq>^Hkr;-Bc8:?5LK7AW'I6RB$s8;oorWE)qr;-Bl +$1@ +!WE#nrs8Q&s81`GTsadsrs/B"rr2lqs8W)sj8K&Yr;QZp'DhY(qmNT4jS8cRr;ZfrrVZWmrr)jT +rVu]kRpCMmK8#&2KR$2dr;$Bmrr)]lrVZ]nrVZWnn5,pT9]Z'HLI-')D/E#ErVZ]qqYpNlrr<#h +rr2rsrXJo,rr;utrSYZ4`7=hbrr)orrVloT_#D5U`kK=,^Vn"/N.H5$IslZnO,oE`Nf]Hr_nO+0 +`5':0\UJUIH[gmeZn]s8UOH%K6>,qu?TfWLfQJS^dT0s*t~> +rVm#sq"jmarVmGrZs?XF]#`4C_o^!Bb/hTBrlX9B*6,t;b08)Pb/hZ?`PfN0Pb.tj2aj/-oR[6+Uf%=aoC2dir3Ru?rcca/D +a3)QKb0%fF`q%2E`l5sAbfIcEaN)9Cd`Au6]"?M9`lcHEai_rbdbF9_oD8%Kn*KW:r5upm`lQ-8 +^rj^>`5BF+a7/Hdo(;JKrq3q3^rFU?aMZ-@_u@Rn_pS/Ln*BH3nG*%A]"5o$`Po[1aNDQ?^Uq); +rq?olj3d5FaiDKBb0[i9ai`M^3l].ZaiVQFb0A +s8W,srVuos#P\#hs8V0Ej8T&ar;ZZnrr;upr;Q`Urr('grVlrMmc=?H"9%ukrr)j3 +rqu]is8)clqK=lSKnP50LP/+@rVulos8O2>qZ$Kls8N&uQ?TKYNVi\Nqti'gEbe:Np%eIUrr;cP +rX8c)rVGHk`5L4(rVulr#64SqoYQB/rcJKlI!TpPB.+5kq#D!/"9S`1+_uLEFDc/=>T3\J!rN$6 +!X/]/!!!3YC34fOHZX.>3YDZ%!!!&t!"]56!!!3oCiFKFL6'jVs8Drrrr_KOq#:9miVioSs8)`p +0`(Q:DJsi/=TA-pqYobMWh`GhrqQNnqYpNjs8N&nqZ$5s8!/OZL5CLI;#9e^s8)Tks83E+qu6Wq +qW@GMg@bLDq>^EArWE)squ$Hn$N&fNna#B +!WE#krs&GbWM-9Squ6U$r;Q]orr<#srSIPNrtG;0s7YuT[D_h7rVlips8MuprVc`q#Q"Jss82Y7 +2u\F7L4+i06HoB-rqufr+8l!8rVlirs8J1$Vg"'rrqlTh06(oE8$DX_r:^0jqtpBms760gs8N!, +s8N#trr;rT`P]XErVc`p!<)lr!T(Wk_csdg_Sj@0`K,@iFaAI^G^Y-sNfT3[O.+,4]u@e,`jpS: +H[^0eH[C!gLQ.L^P`h]Y`PfU._8OEgIUCLr:risCNa#ds82ips62nIT1o4`q>^Kks8Vrqrr;oqs7WlhNIcV3LO_N`q"+F`qu?]o +rr2rtqu?[(kJ"XESAk*ps8W&te,KCIs8Dut$Mr>MSsl(Gli$k_pA]X~> +rVm#tq"jmarVmN&_J`R^V8:BuaMZ*Ab/hTBb0&\as2P#[s3+Qc_TBj5Y=r/?XhDj"bJM9Cc-=DN +`Q#p5bf8#Uem0eZLk1+sH\"2Zbg4/A_TpEMaNMN?c,eAaF_.7_G1G$gfA7HWBPKerbg+AIaN_TE +b08)PaMu6jaF2(I`luZJ`Q6-@`QQ]N\\G_fe]YtTb/_WHceQt'nFQ8@oC_eJo_HVA]YMP._ns7* +_9'd;^:VAXqs3nDqZ$9&YJA/rcHOJN`l5m;a2l6Io^q\?lL"3BgqgbFa2Z?B`5T^:aMu'.]]/YV +n+5_gm+9A"aMuBL]tVV2hoJ[7]?.t9ccO>W1h1?C:H[@Qa2HNQ3hi$&f?V1Rai)0@_9U-F^VS4> +Si_mbJV/o62EfsDcHj>G`lQJ`6#`J:JFP[En:*e +rV$8A~> +s8N-!r;HZqrWr;pqu?]bjPfP*rrDrprW2oorr:gR$iKeqrr<#rqu?$;gALsPpAFpgs#U/Wr;ZWm +s8Vuc3J<0:J<,/6?AA@Qs7ZKiqu6Wns8)`pp$YhUX\n`cs8VikP#HLJG +s8Moq!<2rs$NBS1T;0[0qu6WnrVliss8CaPrr3]2s8Vups1P28k4ncLs8;orrVZTlrVnDDrr2rq +ml5lNI=I<,KNj-*q#C*gqtpBmr;ZZnrq#SUTr:WRp](9hr/jnVEreGar;QcqlMge_rr*B,rr2rs +s8Clp_o1+(rVlcrrVca$hS@%H_T'4$rl,5)H$O^`I=6Khrd7:kMiWm[S]A@\aMu!+GB\1[HZjg^ +GBnL^JW,VMOe0P1^qR\(`h@g"H$OX_H?X^aI=[9:OHu0?aMbd0]YF.>s8DrrrVG[Gs5*bTrsei) +qXsdbX$plFAQJ['rr<#o,*?f@G*2P!s8Dorqu$KjrVuolr+fnrIt3B$J:9]Ts7u]pqYpNp&H)D) +p\N4N]=4-qqYL6lr;Y7Fs8NN,r;ZZe[%Wt\S&P7&o`'F~> +rVm$!qYL*erVmN(f8j0?MSR-LbeqNEb/hTBb0&M\>0>19`59I1bJnHo>aN+Ca3;9;b0A2T`l5s; +aMH*IeC)\gI"ZimK62fJCXU;K]u\17bgFGN_T:$FbrV5d4k7a!d)sp[4_K%^.Dsied)a8Gb0%rP +b/hTBo#Q7!`Q$-GaMl6Aa2Q6MaLT's]@G*Eb0%cHb0Skkg[Oh+qXF%Oq>A@C]YVV1b/VE;^:_D) +`koL3p?_DFqtKim\%9]&`QcKE`l#R-`l>s7cLg_qlgXNAgUt#8`l6$=_o0L-^rFC2^qf(.p$VM4 +cg99*a2l9Cd(R08b2:9;`lPg?aMH$HN&kfs?TliXa25a5eq._8D0*nBb/_QF^Ve46a3)N?eQc[A +Kn5&.Ir?X^g<%LX`Q$!Abf\#H`P]U@\Y<@d/O)8!aiquO_oC0H%a*)@^r+!s@nTKq>FFjWo`'F~> +s8N0"r;Q]qs8N&o!<2ut!o;nor;ZcqrqufriVrlTrr3]3q"asirVcZis3Tils8W)uqtpCPirVulrru_(:p](9ls8RS&WbAI1s7uKjs7<0aDfT_^p\+R\oDe^e +s8V?_$ig2(qV]?2`7=eas8N!/s82T`_ML*frVccioMq]c7e-NTq#16lrr<#rrV6E`22-[8MLUX_7H4(?s8Dur +s8W&s!W;rprsIuWo&93?s8Dfls3^fIqu6TorsIcOlKR%&s8D]hrqHGC~> +s8Moq&,uV.s8W)pgmk.JqY1!hs8N#ss53hUs8Drsrs\o,qu-Qel_(c-p@e1Xrr`8ur;QZps8q>^6is8DurI=e>,VYL2fq>^KeVbLrDCdIjks82NilMge_rr*B, +rr2rss8Clp_o1+(rVlcrrVca'hS@%H_SX%$^r294H2i1eG]n7SGC=^`KT(nOR`WLgaMYZ:G(4a\ +Fa/7WI=$9aHA.@(_o9O3]>+%=s8DrrrVG[Gs5*_V +s82ZlrsAJ87<3?]4o,-TrVn/9nkl!R7.L +rVm$!qtg3hrVm6"l*t2-FLo$#aN=G(g;s95>0>19`PTF2d_2GM?ZNNe^<=F5aNVoQ`l5s;aiDTN +`m`;eG'\[gH$k!j2:U($aN)HK`R2HDbfeMiA9"/CP1A'u_p-QPM*#Nn?nC!Yd)NiFaND`Nb/hTA +o#Q7!`Q$-GaMl6Aa2Q6MaLT's]@G*Eb0%cHb0Skkg[Oh+p%%\IrQ)LZ^;Iq-_SX.'_n3Y"a25R2 +q=aX\qY%G.`4s.*aNMTGa2G[*`5]d6dJ3D(p%dq-]=Gi%`Q66=`PfX,^V[q,]=-_lp@@h5cg99* +a2l9Cd(R08b2:9:`kB+0d*U"+/o5K=0%\Cc_7dS(akK@XT.eI;`kod<`Pfj@`lH$Kdj]s*KnXc, +67fq!d*0JK_oBd?b/hZD`PfU5Z%#7u2*,`kcHaVQa3r$>b0/#G]tC%<92/](_sm^)p&BO~> +s8N3#r;?QnrrW,or;HWshr<;frrE#srrr>tr;?Tpir0#Wq>:-js7m!$rr2rlr;Z*:hYmHVqtU'd +rr2utrqnbUqu?Tns6*!CML^D8M1(%_qYU6kp&+^crql`pqV+uGTiq0gs8Duns8Vql9QYAn;#1(i +s7H6erVlisl2D(jrVcTN`Pf^Fr;HZpr_!4fq"2FYH$+7KIW]-I%Kut9!Wi?("pP26!!*3.#mqe] +HY@%q$k*FB!!3?2"98W,"9\].#QtMjAo2X8C-)q4!s/Q."9JW0"o\K5!T#QH$Fn$eF`e> +rr2p"kj8*Drr:sVs8W,p#ljNnAoD]Zc1q;<-2dcBs8/j+YEEl"r;ZfqrVulls8Vig<)&%&JVo1i +K_kHCs8)Zls8Dotr;QZps6U-Wm+VL>r;Z]ks3^iGr;Q^"o]GW0hq7r.!WMrhs*t~> +!WW/ss8W'0rVlisrVc>aTr$ZBr;?TnrVccSrr2p"rr)cnrr3B(rVu`jq8]S@h>-dBs8W#trr2pR +r;ZTmr;Q`\7n&2]KS>81J6;RBrVuThqu6Wns8D`E;59acp\k-ks8)cqqP-T"EGJAHrr3&os8VHb +rr;us%KHG,s8N&si5NUPec#IGrW)oqrrLulrkhsOb/hAXI!U3fI!U'`I!g6hH@CO)N/j=?cG7<. +Ed2nUGBS4VF``+WH?OF\MNEmi_8+"1^RKLqG]n:VH@(!aGBnL[H\%$;N3g:9`kT=)p&G'jrr2lm +k4&E-rr2p)q>:3`:h4UEorVccpq#CBiqG^.?L44l0?A/1Ms8Vrn +s8Vuqrr3K-pAW.I\@AI%o_eahqZ"tBrr3E'qoGD$U7eLZqu6WppA]X~> +!<)lr!r`#nrVm?*p[m0\?XoS7]Yhk6b1aoP`l5pEcP-a2Gsrb!a=j`lQ6@ +ai;QGb0[mP0n+@tK8Fhp6Ia<&aM#U4`Q#gAcdB);&`Q#d4`l,a5^;7e0 +`4s=]s75pVYdsbKs1oSl_7n",`4rq"`5TO8nFHDTpsld`]t_G&`;[XR_#M+b]YD;$\``YVo^1\b +m+9A"aMuBL]tVV2hoIjua2uBGf[Ml^Ana*-c-FDI^Ve1>f9B=4JQC+@c,n#HaMl$AcH+Hs7u*.Z +KmHU4f$)Rf`l?*B`l@qt&B;f.Z):Vb00W(-^rjpC`m)oPs2kej]Xr_l9Mo#@hY7'MrV$8A~> +rr3&tqu$Hns82lrrVlumiS<\srs8T's82Wirr2rWrW;ukq#16mqZ?]qrquctdH0Tkrr`)nqu$Hn +s8Ef3s7lWorr)fqppU,(Og8-1q>:0irVuQjp\.[(Ec=7oo_J@` +r;?Nms8V?_$ig2(qV]?2`7=eas8N!-s82T`_M1)bs8;orqYC$c?C%3ge+`kApAP",rVHHB8;)VIL51=GpA=Ua +rr;`mqZ$TorXf#,rVuookO@rpn,NFarr;rDrX]&'rr<#YkO@Zgn,NFcs8DZkJ,~> +qu?Zp%K6>,rVHNk_P*[:s8DusrVlftrr:sVrr2usrqufr%fQG+s8W&qnY*(up\t-hrr;oqs8Nf- +rr;urs8Vo$9n7kLIu&S?kkb>Wrr4#9s8;]koP'>h=NL@;rr)irpAap];J9rb4kB?*rVli`rr2rs +rXJo,rr;utrSYZ4`7=hbrr)orrVp9^^r+(.a2,TLFa&+UH?a[YH$ag[G]nCZI"RHOai)9-HZs[U +H@L?hH?j^[G'\FYI"@$/TZG9r\!qniG^O[UG^+CYH?OLYG]e:_M2np]_oB@,_=dj.rVlfpqW@;I +h>TA2rr;fgM,nV8:8@Uap](-js82Zkqd#DU>LWEIs7ZKls8W)srROJkJU`?*ImNl.qZ$Qpp]('d +rXo20r;Z`S]=PMOaS>f3s8W&rdJa[VqYpKcXdP`PU! +s8Dors82cp'D_:um?_*(ai_cIaNDZF`o+oW`l#d:bfn/E`5TsGb/18V=Dk7S_o9gAaMu3= +rl@=L_8spCa2u]UPs0PJLOXu&1X"7m_o^$C_9'U8dEtjGW'mW5bf[uEah#a@f0:iT`luZJ`Q6-@`QQ]N\\G_fe]YtTb/_WHceQt'nFQ8Enb0r/]Y_S*`59=,_83k$ +^;9f]7_d7HqYTQd[Ca,jbJqB6^V.Cs_ns.%]tM)'o_nd\gVUD2_7dIr^V%D!^V@S!]=bhk^utUd +oBGA_m+9A"aMuBL]tVV2hT.^saOADdF@p@a6Aj4>_8jF5`kfL:fh73d8[6FZb/2KGa2u2ZH+_;<`Q$*Gb0n?AbJVE;Y%(4$9jXaaqYpNnpA]X~> +rr3&tq>C6ls8N#t"oA9!kMk7qrrrE%s7uKghu3]So_8=a;uZ[hs8;fpr;Q`ejPg%:s8;]gqu6Wq +rr2iqqu?Hjs82Wgq2Ag=Iu&]3>*Aj_s8)ZnrVuops2/'DMJ2hbo_nges8VfmrUE$jE-42.pAad_ +r;HWps69Ljs8Domi5NXQeboCGrr-7(qtTg#LO"#_EF1%,#6=o,!s8`4#Qk&,#6k>3!!NZNA8P'X% +1PeBqs8;oqs7lTlF_?;ZJ;8_WX8;epr +;ZZos8N&urVlip#Q+Q$iU-4+h#@!<)QiJ,~> +qu?Zp&H2V-r;HZpj//TOo)JahrVc`q!ri6"ir8rW'`In*r;Q`ps8;`nq>^H.VR#%Rrr2iqrr2rt +3;NUKs8W#nr;)=ZKm\]-Jl@[*r;ZZms8N&uqu=H6WekEds7cKlqZ$Tis82:HD/OARn+m"Xs8VHb +rr;us%KHG,s8N&si5NUPec#IGrW)oqs'X^!_o'F3\qb`\H@C3fG^+L[H?a[]H@'j_K8Z,9`2n9- +H[^<`GBJ%RI=69`H$XgaGC>@8`kng:G]\4QI=6<`G^4X^H$Xd]G'A:`P+fb3`l,m5p&G'jrr2lm +k4&E,rXJo'rqUeCCN!`[q>U9hrr51Ys8W)ne5`W:>5S?rs82ios7u]nF_-,UI>*/NXSr2!rVu]n +s8Drrs8W)ts8VuqpqXSQ[ArTgs82iqs814C%KH>*rmdd3S=QnSrr2fes*t~> +s8)fprr3#tqu-QppBU`l]PLn\VRcZJs2b5_s2O$==j"b%_og3E`P'@4a3V]K^iN@VMR1.Cd*0MI +_8aR=b0S)P^;\73_912L=c.idI"+e5f?DFT_oBX7b/r;07Akf6e'GnQcG@?:^!bEX-Z!^8,Ke0V +^;e7nao]Z(`q%2X`l5sAbfIcEaN)9Cd`Au6]"?M9`lcHEai_rbdbF9_oC;"T\@of'aiVN<^qd[t +^Vn%-]Xtkja8#JgZ*M![`PTU.\@K8e]>2;#^q@%`^Vo^:kK^cP^UV.t]=YbhqnO#^]Y(hrg%b4/ +m,ZLZf?)(S`lcZA^<+OTa>h>ha3;k9:MOiQ3S)R"`lQ?LaiM]FagFU:NBYb3^sC3M`l5^ +rr3-!qu$KorVlis&c)D*jQ,4os8W)urqZ?drr<#XrW;uiq#14+rqufms8W&tpAb0>ki)O@s7c?d +qYgWtrVufmrr3,rnO/(]re2bX6gO^Nr;Zcrs7u]h:8Xp`l1b2Ss82irqu?Zks-HH*Dg"8Ao)JXc +rVlisl2Gc(rVcTN`Pf^Cq>L?mrr)lrqt.[ZH$+.G4YJH8+s\E]-n$Po-71/i-RL#_,8_G:C.13I +1bggG3&`fU2*3cY2)-sD/O!H&E+hm+5Jb5X7=h91a2HE--DbeFEM:s8W)s +k3W!Es5ukje?$mf37brrW)rr7_,Mr;HWps8W'.rVliskj.g-kNr6Hs8Vofs*t~> +qYq3-r;HZnr;Q`oq66Wif_bXHrVlfrhubV.[.4r;ZQhrr)lkks'cNLk1##<;6Cl +rVulsqYg?jrr<#urX]&*g:t;3UW!0.rVccns8C[N!<;op&HDb0s7NaoS=64frVZ]pp\smdJ,~> +rr)irs8Dor'DMA'pYC:*=GsT)aNDZHaNVfH`Snm>b/hK>b07rG`l#sC_9U3?X@ZT&S%[.kb/V96 +`lQqX`1_g"G6CjlGC[gXk36iniPOh:(3M`:W]Zg#_M]kht@XiS`\WiTB:_ioT4V +hs/kgp$M2+cg9?,a2lEId(@$8mE#7GrlG&\rl@(=b0AGbasgh?D'WN8_TU-Bbg"JV^rOC>>'uo@ +Oj(hnaNDT?bfIgVI=I**H>GXgai;0:`QcKGrl4uZs2G\oaN(H(5r(D\EkoT4_SO"4dELIgrlY&X +&&uT.c+@[e:Jk5IjRi +rr;usq>V!)s8VlMlK8-Hrr;okp\Fgfs5X([r;6Ejs82fqrrDurrs[*?q#CBmrV6?grr;oos8;j) +qY@*iLOk26IT*BAqu6U*r;QZgT5^i[MYd;Orqu`nrr2j-qk6],G[ODCrVcZfrVulps69M&s8Dom +i5NXPcLq/9rqlQls8Mc/KQqZ\F)u>>D>\5MDZ4JNC]A)WBP2'NDK]i8E+j0,DuOVODuFMcDfB`= +FE2A?DJj<.DfBZ6Df'FlD$FotE,TW3ErC+lI#H>@q>C9mq=s";rr<#tqs"(rpAb0js7uQlrUo27 +Ecu@\h=(@CrVuorrVlis)#3PlWi&efp]('hr;HZcF)$DcK8"_Lp@nRd$i^)$qu$BlrVuosrVlg& +qWdPAmFD7:rr)iur;6EOrX/T&rr;usr;6Eirr)itrqucq"Qo=Jm+Lk-J,~> +qYpQprVllsrr39%f:AbDo`"ggrVlrurVbaTrr3-#rVlcprr;rr$ig4uk*^Hn +rVulrruV.8W)84rJqSSMXnVbnrr;iprqE%&WI0R"r;ZcorVccrrVud"T1X0I?;UFrrrr5us8Moq +qYpEmrVlisrr+#>rr2rss8Clp_o:=/rVlisrr;lns5Cc2`PfX0_8=(g_u7FO_>qFN_Z.L\_Sa6Z +^W=41aMPsr_Z.OM_Z.OR_>qLP_Z%FX_Sa7,_8=(,r5/KM"N&*q_SQ5g&AZ3Us8Mios8;l\n,3+^ +rr;Qgqu?Zp(\Rh.r;ZWks8VoT4`GIZ2r*p%s8N&ur;QX8rr<#nmmj4L;"XS`r;Z`ps71/BJ:rB' +HqsG3s8Mio&cVb.rVlisro:o/\[&.+qu?ZprVliVrWE3"rr)iqrVm!!rVc`prse_[US"$GSDX2C +s82cmpA]X~> +p&G'j&c_at[TusKZ,"`lZ??`59[@`6-1"au[V` +`PojBM*QE6JU_c0PK(J]aNhiF`Qa$DSS)]dcHseVa2Z*;`lH3Fe:p&"Cf3CKe]u:RbK\2KbP]N_ +aT'71`Q#p=b08#J`luZJ`Q6-@`QQ]N\\G_fe]YtM`QQNHbhgq'lg3p(n+-,GrqQQhrqHHcqtCcs +o^_LomeH8FqY'g^q"XRVo_/"Wq#C$lo^_SBoC_kWq#L3cpAF^cp)!JroCDD?mc2a^h8mO\dEg+K +^<=U;aNF(p!64uV<6!#%bf/)VaNhW=bLbCg.:d1%,JD%:`l-$Fbg4V[b/D6r.aSj*Y +aSs0e_kg=&8Q033lMC/Go`'F~> +n,EdVjk]n8s8Mihq"t'is3goHrrV!;lMgedrql]kr;-HlrZD%;q#=eXLPgh9IT-%?r;HQnr;HF] +Vl!)\s8D]gs8Dlprr!B!9O;Xa>^c5>p](*gqu?KVrYY\6rVGHk`5Bmprr;upq#16lo=fj!EcQBq +H2MmdG5l[jDL?YHG'J(RGk-1VH1?4WGmf(\cggo4s8;]dipZmH!rVrnlMaNXq"t$err;iflT&m' +68rTLr;Zfprr;rqrr)fqs8;VRI&D"Sr;$9is8DlM8V2\KM2$MFrVuirs8Momq>:*is8;iqrqZTo +nE003f)G[Mr;Q]qs8)foir8uU#lal'rquZjr;QZp$3'o$s6T(Eo'"p,rrE#ks*t~> +nG`a_XIZ!1p\b$hrrW2trR(TKrV!Cq\Erkdrr`9!s8;lqs8Duss8O2=s82h)/qSh3J::Mos8;cj +s8Dun7]3%Rp&Fs`qZ$NmrtGD1rVZ7Z>\nXgI.[@7s8Dons7lHir;Zfr"9&6!s8N!-s8N#trr;rT +`P][Js8;iors8T"rnkN-`5BOb_uI[S`W*mV_[*ot`P'40_8aIb`;.=@`;7CO_@62=s82`ks8VBP +qu$Els7?6es8N!Es7cEhqu?]pp[("\DEp*no`"mjrVlipr;HQls8W#iO*IC:Rf3-_rt#)*gIH^D +I"d6'Q2gm]rVlWm'DhY,rqZEccF:9oU"]GCs8W)rrVliVrr +o)B:"qU]^a,Gcc4)IbehI!askEO`l5p:aNVoQ`l?6H +aMl6Aa2Q6MaLT's]@P3G_8FIAb0/Yoh!OIrnFHSMs763[#O;*Ulgj]:o^VPKo'u>Jndt!4mbc7/ +bKnY_\%^&*`lQccF2H_o9dBb/h]Ha2Z$6`P86Y/LNS>PJY;Y`lQBJ`l5sWa90H'b0'_- +( +nc''ss7kO5mJm4aq>:'err2rGrr'dW6%"'5K7?/*q>C6k +rs\LFJu!Ffo`+sfs8Mrorr2j,rq%gdDJh[dq"sjdrr;ipl2D"hrVcTN`Pf^Cr;Q^(r;-Blr9h+J +FE)ACqK2=Os)\HiGB._KG&kuQ$[dr\med%aqYU!Gn,E=gr;6EWrY>2,s8;iqq>#ItEd;cOeare> +s8Dlor;IZ6rr<#sohDHi7.BpMs82imQ;rZrMhP6$s82KgrrE&tq[`W)rr;ros8W&troWV;iT:(= +!W2lqrrW)nr8R_Ur;HWnr;Zcrs8N#trrq-@mI0'+rr3*"rVuljs*t~> +nc&gmr5bM)\bZ.&!ri/sci++#S[-<1r;Q]urVHQoq>UEo%f6+uo07JFK7n\>`;9B1rr3E!G^uT/ +X7Q>jq>^HlrVmQ1rVcZoo.4otCd*7CrqcZos7u]krqufprr<#trX]&.rr;utrSYZ4`7Fk^qYpKo +#QFW"i53=I`;.7@_Z.FV`PfO+`P]I<_@FAto`+shr;$BmlL+QIrVlifrr2rqrX\c&s8;iqqYPb% +F*_lMeF`b?!ri/sr;H]prr3o7oLl'^61=RJs82ikPu<3eL4N +o)Asnq<4/LW%QeB-.]r5KDka2uQWe/)_+?n>^7ccF;NaM?%!aSs<^aSa%'aND`L`Q$-GaMl6A +a2Q6MaLT's^"CQK_8FC>b0Ao$jn8BDnbV_GnGhq]oCD;9oC;4qn.=X+kh=2#bKJA[\%^&*`lQfi +>c$WUqY0gXs*t~> +nc&sqs8Us;huE`Uq>C6ks8ULGrr38XjQljGqt9mdrr3&srr;io*rGp9qohNfK8##'=S`!os8;ZM +=0.ukp\ascs8;lrq>LcuogqtiG]6MEr;ZKirrDfXrWrQ&rVGHk`5L4)s![mCr;Z`a\qYHPF`qqP +G'A+RG'A+RG'A+RG'J4UGBe:TG'.hKH?sc6G<'hjG'A+RG'A+RG'A+RG'J4UGBe=VGB\4SG'A+R +G'A+RG'A+RG'J4UGBe=VJ!&7Lq>^KmqtK+=rr3&tqu#aYs8N]/qu??ho/CH(E+3SOs82WhrVlfp +rrN,trr*u8Zsg4nGlIL9s7u+JMhcn;HVXD1rVufqs8Mrnqu6Qo!W;oqrrh]Wp$^!$rs8Q"rr<#t +qt^0Lrr;oorqcO!rr;utnDEEnlJqjC!ri&qp&BO~> +nG`^kk,+oNhY-mI!ri/sd/FFLpU7'-iVNQQs8Vuqs8)`pruM%;s80r+M1UG6FB85;qu6Keh,C;C +;>9tbq>^Blr;Z`p$iKZg:18<`:dG$[q>UBpp]('gs8W#rs8W)t&-)Y.s8N&si5NUPebf+:rVlg% +rVliU_Sj=1n\k@Erko&Y^r+76`5KXb`;.CB`;7F_`5D6Irr2rps8VBPqu-Kms6BU`rser)s7QEa +2JdAQ?to-1rqllts8N#rru_7=rr2lnZX0bbFoM77s7kqDLP(#*GYIr.s8Vrnr;Q]q&cV_(rr;us +s8Vi0[_BGFp@\@_rrW2trr2rWrr<#rrqcZprXAW&a/,H!Su1cKq>L?mrq?AB~> +o)B3uqX)%5=')!,`lQ0=`lS/%d`=Ae_745SqhW4D*q"4.Jr:BpY#Oq0Op@\"IoC;DIoC;DJo+:$-jjqGl +bfeDX\%^&*`lQ_p-$HbTU;&@o64ThpBE]`5]m@bK%WCaN;TIc-_bOUip5$bKJ>X +aX2CgK7Rque]H(aao9Eb`kfR8rm)A#]tD2!X&(T86oh'P`5T^8aNVfH`Q$iWr5o#\rPnlY&'Du0 +_hD;k9N,fSoC_nWrUg,?~> +nc&pps8V?UBqrqu]Arr)j$dIHB!s7lHirrN)rq>MK5s8)`op1\;qJVo.MUAXids8?Jk +V-M"3s82`mq>^KjrX]&.q.Ma2DK\Rhqu?Hcs8V?_#ljl%qV]?2`n(+gAcDK&rVuDsL4+>kF`MSE +FE2JDFE2JDFE2JDFE2MFF`MSEF`DPFEH$)BF`V\HF`V\HF`V\HF`V\HFE2JDFE2JDF`V\HF`V\H +F`V\HF`V\HFE2JDFE2JNOkBC+s8W)qpuCiBrrW,qr9=1squ?]ms7qUDF`VY1]_qU'qu6WqrVc`p +rr3r:rVc`iqd>bn:tG@=qYOfFM1:A2<5A>0qZ$NnrrW,qr;?R0rqZTop%%hMe`d&4rVQKks8W)q +qu#=Mrr3'!r;?Nj!;uir"l/>'ki)(2rr`8uqu-3fJ,~> +nG`pppUZih^\Ig.rr2rtcMe%Es6Q;b\+BRu"oeH"s8N&rrZ;+;s8Dutp1J,nIt`G>T_\B_s8-/^ +Tj#G.s8)Zlq>:3jrrL:!ds8W)ks*t~> +nc'1!n_]:&;gc6s`l5p7`QZTLdE"5`a/MR2?`1lea2Pp4c-FDMaND`LaMu*@`m32c:0;moMh3pH +f#YhQe4GUn9rZCeb0%iDaiFA"s2b5_&C8bQ9-\(YB/?F7d_NN8a2S%touJ5o`luZJ`Q6-@`QQ]N +\\G_hf$).O`Q?BHf&Y<1ki_4!mf)\SmKW.In*fT/nF5oBmd]cBmL\8"eS_c,muHaN)?@_ns@4bfe2Pa3mP8V*VZs2b2Zs2b5_%Euo0Y?t='<*tdO +qY0mXs*t~> +nc&Ufrr3/Wm,%dBr;Q]urquZkdf04F!pJ7uqu6csqu-HhrZD":qtL!dq*p-lKRo+WqYguqu$Eds*t~> +nG`dmqr+6LWUB`brr*$"rr2rErW`<"_kaKaq>LB>%:Mg];brVHQoqu-EhrVlg-r;6Hfs8W)tlG:*B +U:gErr;Zcqs8UdOqu7-)r;P!:St)OPh"UaFrUTu=~> +nc'0up[,\#>@XkO`Q#m5_p$HLd)\)b_KALLICRJ@`l5g8:=>&+Fb0%94`Poj:rQ+u\rQ$/%`Q$-GaMl6A +a2Q6MaLT's]@G*E`Q$*GaO/\[`n8hoh"on%h>Q41h$;lphVHuEi7l]:%ahl=e&fJIb0A/>^rsa: +rlXfQs2nBdc-=JT`l5R0a33,[88;sT5(1r.ccX>Na2l9@`k]=+a3DiOaN;Z+8=br;hUU4B@WHjW +HsL14aiV`Fb08)P`P975cdTqQ_S +nc&Ugrr3)cj4mZ(B=@3qV]?2`7"P^s8;]eqY^6^d_*3#rk2.B]tV1o +]tV1o]tV1o]tV1o]tV1o]tM(m^V[e$]>)(p^;%Cs^;%Cs^;%Cs^;%Cs^;%Cs^;%Cs^;%Cs^;%Cs +^;%Cs^;%Cs^;%Cs^qJ\"r;R#ujmW3Ks8;cYrY52.qtg-T4)o1_75s]8o`"acrr)forZ(k7rr;us +s7uQjFH!/3Xn\eJMMd+FE+&D5r;HEhrs8W%r;Q`rrqQ3drri#]p&*)6rr_ulqu#.H!W;rqrrrE" +q=sa^rr3/alg!cmi;NZYrqlWkrV$8A~> +nc&mos8Mi'URK'rrVlcqdf'aTrr2WbYHkUmr;Z]pr;HWpqu.W9q>^Enq#A0,IY<6!L+APcqb3]` +9^29Js8Vopq>:3jrr2p)rR*uHEG]MhfD>LBs7uZms8N!-s8N#trr;rT`P]XGrqu`ns(D9.r8l\l +gu7,Hh;6uEh;6uEh;6uEh;6uEh;6uEh;6rCh;I8NhV?uFhV[2IhV[2IhV[2IhV[2IhV[2IhV[2I +hV[2IhV[2IhV[2IhV[2IhV[2IhVQoVrr;cirr;u]mem%_s8VKc)?'R6s8Dutr;,_>D/4/W2"(D; +s8N&trVc`ps8Dp;rVliqrr$knUh-V$VCdOUItVTCp%SLcq>^Hlr;Q]qs83B+rVlisnC,"V]U\Wj +s8)Tlh>RESr;QTn%K6.mZ)!q^TY'ahr;HWcs*t~> +nc'1"rV5?#=]'OW_oB^4_p$BJd`<*BaNM?*@8g3g]>V_3`lZED`lQ7#are^J^rOLAb1N=KG)1p+ +K,/ORd5.&c6d +n,ERfs8:^A\?!WW)rrs6s;7BRf`DG,fkE +q>UEls8W#ss82Wk%fHA"s7o3!GBRsr_Yj3/kl)G!rVcTN`Pf[Ap\Xmaq"OXbrr2HSm-!m.o(2\I +rUL'\mI'Vpo)nONo(N%^#lFJ[n,E@er;>dX')DP'q419'F*:Pbo(i=^r;Q`qrr42>s8Mrqs8N#t +s7cBV9<+sgnOS:cLP^U^[J]t&r;QZls8W)trr3`0r:^-iq +n,EXhrRRaBXS;Ylrr)lFrXo2,s8N&dS[H<(rVQWprVlisqu.]9s8W#ss7lD4Bn1_eI:Rd!Quf-J +L&(cKr;Zfqs8W#qs8Dp-rVuQjpdJ_6EG\1_rVHQnqu?Nlr;Zcq%KHG,s8N&si5NUQf`1sKrs8W& +s8;osrVuo_rr)j!rqcNld/FOTrVucos82fZmem%_s8VWg!<;rqs8NT-s82S\8oSZg;-!2as8N#t +!<2ut,5hK@rVliqrr<#pr9R"QV+pDJIt*$%Hr.0Irr;rsrVQNkrr3'!rVc]p$N/Vi]=+Z_q"jsh +rVlfrrr(dSrr3*"rVlforseo$eXrA*US?? +n,Emon#9f%?a[YgaMGd:bf\AT'?S;?aMt/HOb/D?A_u7UTa:cY:_oogBfed2aG\f43dE'SR`rg1`l5p7`luZJ +`Q6-@`QQ]N\\GY`dE0JPbfn>Qa3`5gin<5GlJVX""5qtjj3ccoiSDVu`5p$?bdkU7`l7r"o#LmP +aNFM+>0+_(_T^?ZL-KNsF%ARAbg42HaN;NFb/_B=_o0^Bc-4;Q_p-7ZR$p=p0m%brLO;OZf$)=T +`Q-$Cbf7Q>`lcHGbK.N+I6/u40m:(KdF$.]b/hTA`lcNJilD8Hb0%`Drl"oXaSs?^a9obM=%>eX +?aK.nrVlg!rV66fp]#a~> +nGa$uqu?]]j5]k5s7lWoqY^9>rW3&srVluicL1N-s8VrrqXXUsr:EO4LP:J=4QK,9VG@!drUKje +rVlg/pPhslDg-5Dq>:$bs7ZKgq>]p_&-)V,qV]?2_pA)Qr;-3_r;Q^!r:p$]q=4@Pq9o-Bq=O@W +rVm0&qr[AGs8W#pn,=(%q>^Khs8W#mp+U!$EcZG1FSPk/s7-($s7cQgNdRmQEE%dHN.bncqtp?e +rr2p)r:fp^s8VK\qsDo4rs8K#r;ZfpqtpBls5O%Xs8Vusrr2p#rr;ugp\t0rk3D1&kNhsDs8M`l +J,~> +nc'1!rr;of\tPkDpAb$is8DutdJa^Rs8W&kd@dqjqYgHorr)lsqYpHnrr*K&8:Z;BJV?/E8>r:W +qY^Xs7uZorVlfrqYh--qZ$DP +HDYe&;MKXSIUmBnr;HTorr2rtrr2iq%K",U\@&:.qYU3jr;HTmrrW,qrSmhVrVlg0rr)fms8W&t +s7cAnTpMOI\b?%%rVulgs*t~> +nc&sqs8VcEM,dB.\]Xoi!QN=1a:lb;bg+;>L.GBb[DU21ai;?>aN4A's2Z"r`Q#p=aN`$bH%q*. +IP'#/S<>\\d)jB(`r3srb/hK>a3N.W89f3!AmF!;ccaMGc,%3;aN4A)s2lS+`P]U5bfn/IaN2E@ +bL4JB^UUc%bfe8TbfduEaNVuVc-=S@d14^\f#ktN`l?*D]>W"7`W"$Oa:?5/`Q5sDbJVTJb089M +<;pqIED9T`cG7H=`lQd)X,C`Q$'Eb/M9;`l?*A +aih5e;_BR_8$0YOc-FJVbfIc@`QZTLilD8Hb0%`Drl#GebL"5Jahbc/<'EoPIbXPmrVlusq"js` +s*t~> +n,EUfr;ZHIk26.;s8;uor6YEJs82cpr8-E.rr3#os7$!urN&nLKnP)-:O]?aVXXW`nbs@)r;HHk +Jm32>FE;7GV>gDms7lWoqZ$E^rX]&-rVGHk`5C!tqtpgUps/m=!ri/rrr3)uqtpBls5X+XrrW,qr;HWtr;--crr3/alLO9"q"Xjh +rq?AB~> +o)B=$rVuosps5S,Xml;hs82iqs3U`Qs8)ZnmCfL8h"C[DrVu`nqu79-s/f+IJ:E&q97*RSVXjcb +rqQNjrYPV3r;$AC=)2nrE,$btrr)lsq#CBks8)Zmr;Zcq$ig5*s8N&si5NUQf_YUPrr)fprr<#e +rpp*:rr2p+rr)fps8;'Ir;Q`qs760hrtP>0s7lKkqtL*_5=B:"FDa`OFo21:s8Drss8W&urVZX- +q>^Knr^TS"?1EbkqA +nc'.!s8Vu\W`W&/ZGXl*_TBgFa:le?ai)3AXDgd@NjH^EccX8IaN4A's2b5[!6+rW&^&IY6%"E@ +IWe8*Sm-fabf7a!`r3ssb/hZ@`5gPD:2bK%CKh7&g==Nq_T^- +n,EUgr;ZfTjOO5/!ri5sdf0:E$2X]%rr<#te*$&rrrMojqu$3f&,-#pKntJ=Gu1?+L?no`+lg2/75VI;N5F<8.BOs82?c&cD\(r;Ea/Y_c/3N.Q@[nbrFarWE)qqu6Tp% +fH5%q>1$Kk5Xu%s8;opr;QlsqtpBls5X+XrrW,qr;HWsqY0jcrrqfNmdfMprr<#rnGe"~> +o)B7"rVlisr9:2TRbn#Cs82iDrXf,/r;Zcos8D;\YIX?'s82fms8N#ort5&+7tH>EKm-:eQW*b\ +q#C?mqu?Kk*<5m8s7hh'5?_oDG%O.YoCi4aqZ$Tks8N#rrr2iqrr*E-rr2rss8Clp_o1+'r;HWo +!rr9!qu-*bnGgo8rr3?)rVc`qr9*JFs8VTf)ZTg:qYpNppAb,j2/$uMG\C01:tP^Gs8;osrVcit +rr2lrrW)utrXf,-s8T3/WeO9%LO=5HnGN7ZrrE&srsSf"mD?3AYHduqs8;lr!W2fnrrW,qrSmeW +r;HWp"TJAtrr)ir$2jA-St2OHVsO<[rr`8urVlHhJ,~> +nc&Uerr3B%ctQHYS\`=d`6$'Ja:QM=b/VB;ai:DY;IHE9bf%NraSs3Y`W*phfe.H%LPC%PVNM`P]U5bfn/IaN2E@bL4JB^Uh,2 +d*9k[`PfU1b5TTdc-488`X^)7bf7WA`l5sA]>i4;_u@gMa@!ta_nO(6bJ_iYL)X91F+8()E]:AP +aN;6:`lQ +n,Ejnqu?]ch;7Q!rr<#tqpPKDr!rT's8Dcls7Fk,qu?ZlrqlWfrX]&#JR=1\LJX&5>fZ[>s7u3a +'`\13s8;Tja[B3PEcQLg;#L+grV6BlnbrmqrVcTN`PfdIrr2os!<2`m%fZM-s8N&ts8N&ts8N&t +nbiFd0`V.Prr;utrr;utrr;utrr;utrr;utrr;utrr;utrr;utrr;utrr;utrr;uqr;QZp"TJA^ +n,;VPs8Nf1pAb-lo07D8Ec-&AJ+<=6r;HEjnGX!ls8)Ef8X-5TI"%#dJGT6Cr;Hfrq>($hs8W!+ +p](9Pmd9Yts8W)ur;QZp"9/2prr2rXrr2p"qt^-grVlonrVlg#j6,n!h=CRF!VuW^s*t~> +o)J[f$ig5&_P*^:p%A=as3^iGrseo+s82Wls4!gIbl.A7qZ$NnqYq-"J6[_OKhdT,=i^=;s7lTm +qu?Kk'`\(/s82Hg`^*OBDJse\;#L(erV?Hms8E)urr2iqrr*B,rr2rss8Clp_o("$qu-KnrrE&s +rVud*rr;utrr;utrr;utrr;ucs"+0Lrr;utrr;utrr;utrr;utrr;utrr;utrr;utrr;utrr;ut +rr;utrr;urs8Dp$s8;'Ir;Zfdrr)itr:g3j%eDi*C2Ra*Dg^sFrqlZirr;rr!WW/ur>GV7s8N#t +p](6fr^;gE<-NVB@Xe1Prr<#tqu?Zps8NH"s8U-\\[.mrp\b!i"8_omrr2rtr8IVUr;HTo!rVrn +rr3T-q;83CStVarqY^?ms8Mrprq-5@~> +nc&UfrVm5iNa#b6[CXB!`l6BI&B`AB`PoX1cH1o:;09=__TV\prl4uW'#r,7@7u-"Lf9M>;Qn0Z +`kTU9qo8HQ5K_!dbfe8mZT(d"CM7N637uF"ahZ$=a3)WNbf[rE_o'L +n,E[lr;Q`rg#:W_rr3#srR1ZRr;6Kns8DutrVtg7mJd+crqlWhrXo20s8A-bM264RTp7q=s8)cq +nbrObrr<#t$i9GcEHc5@Eckr"qu6TspAb*]rX/](rVGHk`5^I0rqufkrXJf)r;QWnr;QWnr;QWn +meeU7r;QWnr;QWnr;QWnr;QWnr;QWnr;QWnr;QWnr;QWnr;QWnr;QWnr;QWgrWiDcmJHn^s8VWg +!WVuort+hs8o/KcFEDP3Fo24;s82icrXer%rquZU:KXTV:5*,2oDeddrW<#pqu6Trrqucq"S:L; +o[3X;"8Dclqu6Ttrqu]nrr;$Xrr3)tq>'serrDoprrr,Tm-*WarVlonq>C6mrq?AB~> +o)J[f$ig2'j/AiMf_54BrR(TSs8N&tqYU?,q>U_DPGrVuiqrVuiqrr3T)d^R!.Sal^Zs82irqu?Tlrr2rTrW2rqrVlrsqtpBm$iKk? +Tq@gLU[e-WrVlirrUg,?~> +nc&UfrVm5r^2@CaP./BO`5U0G'["bI`4Wt1d)EY<8m%Ik`66"r5elW&(&U?6%Ol7=/(j: +i7PoebONY<`6ZWNb0A>\h.(J>EG]Jh5LS$/ah>d6aND`Nb/hTA_oBpEaMl6Aa2Q6MaLT'u^Y7&Y +c-!o?`Q6BQbfnDZ:aNDZGaN2ZH6<0h>:P)VfdFQFZaN2NF`l?*@`lA#! +(rO"Z?S3lk2kn47e&]VJ]>M_6bf[rE`Q$lX( +mJdOls8V06gueY:qZ"qA!W)forsAW%s8CO?kl:\Wqu$9hs8NW*p[C.iIRp0678p;`qu?WbrY,>2 +s8N&uqu;OSE-,o?B6?61rUg*h!;l^#ab]rVd'% +rr2imr;Zfrrr2p#m/-,*g]%6QpB(6fqYgHorW)lSrr2p"qtU!brVllmrr3/ejm20bn,E=grqQ +nG`sss8W&rqS&Ys[e]k"s3U`Ir;QZorr3?&qW+B_]D):%s8Moqq>Lp#p@1+fHq'd078U&Zqu?Ta +rY,2*s8Dutrr7mVDfTT8Aofp)qt'ge!W;uqrr)lrrXSu-rr;utrSYZ4_pnV^rr2lqrs&H$rr;rr +rpp*Zrm(QCrr +nc&UfrVm2sifRPICpq7&ajJ0Db0/#J_8aR=aMX`G?=p=Q`6-4aW,>N8Y2hC8!_NB +bJ`ql)9^1Ia2l?DfPbfIcEaN)9Cd`Au6]"?P=bg";K +`5]sIccF,Kbf[95&]V`+`lcNJ`Q$3`Q63Bcb\#VGtgqdX'oa942JqV`QHELb/VF"`Z<.Ec-=))Il0)73FB&)bf7`F_7db+ +b0A2R`l#^6ilM/Bs2YhmaND`Nc,IT3W+o7*;-fIIpAP!js8) +lMh.fgZ75ks7uZmd/F4Irr<#pqYpKun_W4!s82]kqu6Km%Iu)^C1aPo?#FWQs8DufrYPV2s8Dlq +r:K\OBQn?7J8AG?qZ$EerVuEe'EA%0qV]?2`7"GUqu6WprquZkrr)lerpg$9rr;uurr2iqrWiDc +mJHh\s8VWg(]XI4r;$Ba88iTmDfB`68,)iZrqcZprUKmequ?R$oK7h_8>DnFG$ak?rr<#trr;or +s8N#trrhWXr:S>5rs&B#qtKp^rr2p"rquZjir8rW"8hlgqu-O#qu?]fj6?!ph>[EWqYU*cr;Q]t +rql]hs*t~> +nGa!trVlfpr84HNV;V@Ps8ULG&cMY,s8Vums8Vc8XJsMCs8N#ps8DrqrVum,o.FKcB!$DdCiAhC +r;ZBf)ZTg:pAb'hs8Vih7;-gZDglD'IJEj=q>UEor;QZprr*E-rr2rss8Clp_o1+)s8W)srr<#s +r;X,&r;Qcrrr36$k3`*Hs8MuerYbb7rr)]no1!S9E,'90D+=?ts8Mlps8DrrrVuiqrVuos&c_k1 +rUW%#KL_f15]U0&rr<#srr)itrr)fn$LPU;]Xaa1qu?Wlqu6ctrVZZns5O"Yqu$Em&H2P)rVlfi +c_[2'T<.GTqtU*h!W;ogs*t~> +nc&Ufrr3B(p[Q"2<`YU*aNVcRa;N%b/hTA`Q$!?r5oVqeC2MM>AIiK +DbaYDcdBbQaS!`j<_u`l#^6b08#I`m2?@d`02Gb0':r)9p:B`50mH/RWL?EH?,2 +1!7>SaMuHIaiMR%`rF+$bf[i>_oBd=`5TmJf-kci7AHP=AjOMZ`Pp!FcHO<-`?*%Ae(*!`M*2k+ +5)>b5]Np`lQ +mf*:drVm2bj5]P,r;$;>L.gs8Vrq +qXOOsqYpHdZ;ZF/?%&Sm@8Qpurr2oss8N&r!<2lq)!^_iqUGQFqu-Qpp%\CXqu$Hns8Mrnqu#LR +rr3-!q>'pequ6iQipH6hnGW@eqYpBrrr<#tr;QHjJ,~> +nc/Uf%fQG,r:mb!U:L1!r;Z`prVuiqs8UpSrr2uprr36#rqQ<#Y.1s8W)sq>U>* +AO3;i5_OYRi;EWGrYkh9qu?Qks8W#sqc%'FDJj32F?THms8W#ss8;lps8W)ts![mHrVufR`P][F +rr2rrr;?Nlrqu`ps8N#rrr)iprr)iprr)iprr)fcr%7mHrVlcprVlcprVlcprVlcprVlcprVlcp +rVlcprVlcprVlcprVlcprVlcpq>L?m#Q*ZNr;ZfrrUKk)rVu]ns7uLNCMe'/EcYehqu-Nos8)cl +rr2lrrVlcqrr2rt%/$i#J:LmOUi]s@rqu]os8N#ts8Doo$cg&o\Xr!ar;-?kqY^?qrr)fprVlls +rr;oqrVu?c!W;oprt58/rquflkc12FURT@$qt^3grVuor!<2ZkJ,~> +o)B=#qtpEnrVQBMJ6l,j]uJ+;b5KB]`n&3EbK.T?b0%]F^:%cS?(JaObfIfDrl#esb0%fHb08,R +da^us6Dp8-FE]R\e]c"Rb0'_)r6##[6H7*fcH*rD`4X491feL7FDbr&*n&*V`ko[6aND`LaMu3< +`luZH`Q$!@a3E&P\@T/ZeBlOje'?4ce(3!f_o9^AZd03-b0%uVe'ZC`d_NuQaMl6Co#M`lbL"/P +b/r-Q?YX_(EGel9ahu*>b/V]GaN4>"s2[LKaMYj5`lQ6=b0\P*6[*fXI&Cp[1=F.]`Q? +n,EIdq>U?m!nuc%rr33#s8Voiqu6Tp"8r&nrr2rPrr2ips!u4Ok5YJXqZ$NoqYg?ks8Vlhr;Zfp +p0LsPV/:+%LO2tps8N&ms8MrrrVuWk!<;ur&-$9IA9)d;F*^"Js7u]pq!n@`r=&`'i5ijZg"+^# +cHsr9chu,ucd:"bcd:"bcd:"bcgK.4cd:"bcd:"bcd:"bcd:"bcd:"bcd:"bcd:"bcd:"bcd:"b +cd:"bcd:"bcd:&7dKIq?nGi:_s7$!ts7u]ns7q1DF_l/3Ff'$GrZ_C?s8W&trVuWlqr^A&LPURI +VQW0VoD/=`s8W)tr;?Nlr;HX'o'>r@kOANMo_eagr;?Hks83-$s82ipqZ$KYrr2p%r;6Ehp\4L_ +rrqrOm-UNqr;Q +o)AdjrVlfrs8EB'hP6[BhY.!Kr;HTos8;rsrr3#urSIPLs"=?H>2B3J_]Df]i?48]'Qs8Vops8)]rs8N#r +s8W$,s7tQl_8k3]e^2Ufcd)I7!77pqqp,5f"O5$UrV-^EnpO,eVAT)$uUMTo/oDTX- +qu?Zns8N&ps8D.KKS+r.4/&c@:A4Ycrr2rsrXJo,q>1-hs4b?$[]dmKrr3#trql`nrr[PsS"HP:-hrqufqrq?AB~> +o)Adgo_A@a'(`YK:0dmHK#AScF">8>T:aNhoC`l@kr ++3)CC`l5p:aii/^_6L5Q_nN[gZEpmAZa7'GYcb47Z*Ue8Z2_-;Z*(72V9%0,bfe-!a:cY>cGduK +da2;]ARB.Q@usF0`q.8.ai2EB`5op>_pR#V0n"Y+GrVb,0/(MlbfIZ<`lucTbJh?_o9jDb4E[h`l#d>bf7WEbfRuMZZf/;9NZ)E +jS/ZR!rMimnc++~> +n,EIbp\t-k'BS05li7"\s8Voiq>UEorquZjrr2rVrr;oprWE)qrr)ir!mBj'rVn,>r;Z`ps8W)q +qu$BkrqfPeZ(F@7K85=SebT7@s8D`lqu$?]rX\r!F\5D5C6ks8W'$r;6Elrr2p(j72d&iW&rPp\XO\#QOGrrVuoos69O_rs&K#rqlB_qu6U!jl5aq +h=LIB"9/5rrq-5@~> +o)AdjrVlfrs8EB(osU<^^\@^(qu-Kns8;rsrr:gRr;Qcrrr3B)rqb`M\'4UHr;HNlrrW2urr2rs +rW`>t:.h[]6N2QFKgX5\s7u]nq>^Bkqtg +o)AdgoD&7`'):=>;GD>Fai_lNb/hTA`Q$!?rlX$;rl-b8b082P`4).-=ECab_SjF7aiDQHaMu6@ +bfn/Sgc\$(PrO)=Ll"1(bK@fMa2#^4`5TmubQ>r.aSj*m`Q#p=bg"=0/N-0j7guoMcd0YSc2GZ^ +aT'E^aT'9[`=U&2b0eVV]u%e.`l5g2_o'?j_u7RS_[4/p_n"+Db07oGo#UjR&'rD?ahlWRF=8t# +4(bf7WAb0/2BX'A%a:h,pCmJHq`rVQHi +nc++~> +n,EIdq>U?m#lNf?gAh3Ns8MupfDbgJ#6+Z&rqu]jrVluPg#W&0ruM"4rqZToqu$Kor;$Bcrb.Ck +=EfLQLP1=rr`9#p\Xsi"RkaI +nBCn3!r`#oo)8mls8)]cs8Mr[rr2p+rqlTjrVcceh<"7tg&:pOqYpHnrr2QiJ,~> +nc/Xgrr3E+rVcWEX.?*DrVHKlqu?Nl!<;$Ws8W&s(]XL6s82irZ`CUlq#1*hs8W#rrr<#rr;Q^- +o_i?\Uf_hpJq8B"1&ghAqYUL?irqZQtrqQ3_qYL0g +s8Jtqq>:Nqp&G'js8;osnbs$ss8;orp]'m\o(2VKqYfm^/cYeLs8;oqqJfVjJ:iK+Imfd);U>C@ +rVuosrr)irrVlispWL:a]q+`lrVuorrr)lprr2p'r;ZZno`+pgrquf`rr;osrr)j-p%QM&T:VdQ +lMLPZrVuosr;Zcqp]#a~> +o)AdgpA4^e%/B%kCf=K(`lZHIb/jP$s2b5_rl>)ZiQ)#Ar5]Spbfn>L^pmlQ?\b`'bK%]Oa2Ps9 +rQ#hqb/E.7KUkkVJ:ic9J0s0sb/hcPai;?Gr6#&\!QN4Y`Y$;7bgar[JS8V/aOS_\e&ohNrPnlY +rlY8^r5Sc["jbWEd+HtJd/VOnd/_Yoec45#eHjUDdEoqS_T'Uda:QM7`kom;_q!&ae'uspcHXN& +a90W,ai48!/CMqsc-=JTaN4>"r5]Pk +`Q#pC_8Nq+b/h`F`P][7bfn6!aT'BnaMl':cG[AI;bBbfDT(W-rVlrtqu-'bJ,~> +n,EIgr;QWo%e\K1kl:\^r;Zfrqt^'crr:gRs8;iq$NBtus8Vurs2=$_rr4);pA=[cs82fqs8;]l +rOZF +mJm4c%K65&o>dQ!\G,k!s8Muprs&K$rVlfrs5EtWrrE#qrX/N%s7-!XT"2Pro_ndoqu6TprqlTl +rt>7=>cEo[Kn"f.J:fk(rVZ]gq>UBoqtg9ns8N#rs8W'/s8Dcio(2PJpAb-hs7u]mrVuZlrr3<' +r;Q`orr;oorqueprr)for! +o)AdiqYU6k%K6"hes0WDCqIX.b/h['`r=$Zb5TK^`o5#"&BN#@c-=_`dalsndEp(_`R"J% +s2b,\s2b2Z$-1'.c-F\ccH=3*aJm5[ao9K^bQuS=bfIfD_8jRda:HG0ahka1`lZf^g!A*o_9DGj +*QHCK_o0X=cd/X7H$t6tLP:/&9VIM6d*0YS`l#^n`Xfu7[]"!s-TWr[^!"[CcHQ:/rl4lWs2G\j +`llE@c-ODNb/hTA`Q$!Cbf]Lts2t>^%`Z?&dD3(k<)$+iNo'0srVuorr;QcqpA]X~> +mJd1ar;R$$fAtrks8Moprr`/mp\b!irqtaSs8;iq"TJ?!qtg9k$-;*&p](*aqZ$QkrtkV3:T:'T +:O\+QKR\H-I8fq8s8Vrqr;Z +m/I+crVl^%i2WWMgA:jLr;?Qmrr`5tr;Q]qhu +nc/XfrVm6%p@ddaF')G1^W-/lrlY5]rl3s=3QfRib0%fHbfnMX`6>cuU.n6sQ,1VhcHX8C`Q$!? +`Q$-T2O^'r7sK]AN/<(9BJe)Ye^MO]`5ostaT'EWa:H>:d*'qge'6IccHjSV_TMhss2b5_!mJg0 +rlG,Zs2G#["3/F*a8O)Va8a-i`5]g;aiV`KaiDB@`kod9o#MBc^W"@.^rsj?bKS5QaMbd3o>psS +0?qPWaN;WLgbF##IZSl4Im.1;KUu)/cH+)H`P]U4`l6$,C-!Xs1PNEacdC:cb599Zb5]Q_`XTo, +aNVrLcHF5H`l?*@rQ=]P!m8a2rl>Sh_84%(I8a$?=)5_@pAOacs8)fpp]#a~> +nc/Xgs8N,trqud#jkSnWs8Vuqrr`/kp&+dgrqtaSs8;fps8E)rs8Drs$1-gJ4JW!s82Niqu$!`&-)D#s8)Wms7cQnq>^9hqsj[arrE#srs/Q%r;HTl +rViJg"onQ"s8W&drXSu(s8W&qpAb!fs8;fos7-(+s8DlqrVQWpP?EHnMLL,3Kg +mf*mus8W&qrquQ.TVAgdrr;onrr)j!rVQNlrr:mT!WE#sr=Ao-q>^3hqtS=9ZI&IJs8)`p+8u3= +s8DhWQ#!E(It`P@ +c/h3`UHJDOrr*B,rr<#rrVjaS^T,*HqYpBpqu?Zqp\t0ls8E3%q=sshq>UElrW)uhrr~> +n,ECdrr3B'q"jU@Qrm7&[)'c\aSj9]aSj-rPnlYqo\oZ +&B;i4aN2<>aiDE?`5T^6`Q"ps&B)H$_SX71`Q-*DaiV]I`Q%Ghs2khhb/_H<^od +1 +o)Apnr;Q`rr;QWo#6*N>gAh3Prr3)tp\4[cs8MrTrr;oorrW2rqtpBqqV(02rr;urs8W,r)?0Z' +1lS5(pY%WYKS=]*MLC(Fq>U?mq>^KbrX\o+s8)Wmr;?Tpr;Z]js8VTf%K$)%qu$Hms8N#ts8N"j +rs/Q'rr<#ts8DKe#l=T$rV6Bls82fq"9/?#qsjY's8N#tp\FjW4aMd?GDM<0IRqWTO`!jXr;HQm +rVuo_"Rtm)s8Mus!;#^Z#P[V(nal\Ys69O^quH`orsn>Xkj%9ds8VuqqYBsarpKf:~> +mf*@fs8W&r$NK:iTV'p8s8;forr2utrqlcqrr:mT(&e.1rVlforVulnq#C$5W4q0ds8;ckrtkY2 +PV.]`8G(UrJqSf%Jr+o,7/-WWs8)Wmr;Q]qrVd9's8Vuos8Dors8;opqYU +n,ECdrr3]0q#(![aDY$VOhAZQaN2NJbf\)Lr5Ra;"3Sj3b5KE]at:f`bJV*4Z%5\UK=AqDb0.iC +_oBd:bAS)VbfnA4bn/(<`5]jhlncHOAN^;J@@-Z4KqHART.JO?TjG#gGEdEBW1`Z<.Dah4QN.4%2FXLc6bbg=2K`l5s= +aND`Nb0%fHrl,PmbfG_[Vo5@9iYrB~> +o)Apnr;Q`rr;QZp"o\JghrO&#rri>uq>:0irrW2tr8IYTr;-Epq>:*frrUU(l2CY^r;QZqr;QX0 +o6,5Oq;X_nJqJc3IYTp_ZMX[os7l*_&,cJ's8W#srVlZnq>^Kjr:0ass82cps8N#rrr)fqs8Doq +U&G#j"TAB!rVuBd&-)M)s8)cnqu6WmrVucns7-*gs8W':r;ZAu5_k#HMLgD6K4oB&pGqX>qYU3f +r;?Qnrri/ho'FX"rrrE%r;QNio)8mi?lgZ*r;$ +mf*mus8W&qrr;rq`2'*3q>L6irr2p#rr)cnrr2rVrr<#urr;rsrW<,trVlg'r;HJnYJ'5ps8Dip +)#aC.mW*6>pu4SmK7\c/I"jRYYPJ4js7uTmrVlcqrVd?*s8)cqrVulsqZ$Bks8)]ms7Q?ms82cn +s8W)urVuotr;chqr;cimr<3&tr;HZarX\i's8)cnqu-QkrVufps8VWg!WW/urtG(85D4N:Lk()2 +JnB0$oJYt1qu-Knrr*E$e&9#5T^_jYs8Durs8N#tp&5~> +n,ECdrr3]0qYgHkm$;*ZA@B=n`lQ_rPghTo#MEccH48W`m)THc-XVV +c,7KB`q.84b0J8Q`P]s?Q7IB9LPpV0I=>@#e'SoSgXFBh`l?*Baht#e3[QI>V8LKo]u.e1`5T^8 +aNDZLbf\*+a:ZbK6N[X^e&TGIaN`&Wbf.K>o#MTdaNVlNaND8_<(^"_@?fL@s8W&rqu-El"9&)m +rV->B~> +o)Apnr;Q`rr;QZp%/^))ddQ;ks8Vuqr;HKls8:jTs8;co#6+Puqu$Bkrr4:kkj8?LrVuQequ?Zo +p\OgN=nDKF?@m`^L4b28KO0f8s8;osqoSi2s2"[Ts82cps8;e9>DIfdL4=ko:haqIqYpBgrqQ0` +s8;lr"T7ZWpWid +li-tarr)ls%.p`SLWr;ZfprVuiqrVu`no)JUdrr;`lrr8Gd!<(mUrVc-_!rr/rrr48CF'=6G +K7\K)Dbk"bq>(!cqu?Zns8W#sqYT$]]"FR&qu$Elq>^Bl&c__*s8)cqoSjk.+!lC0s8W)tr;cir +s7?4&s8Mrrs8Drmp9U]hS"Qb +n,ECdrVmQ.rVlijeSSMgNP<>!p.@[rlc,df8^;\+2`Q$!ArQH(r`llBKccpYE$kEbN`luHDc-OYW_nj@a +a;Dq9_TpBKbJh&(:e+)ZEkLc;s8VumqYU6hrr`5squ-9hJ,~> +nc/Xgs8N,trr)j$q>C9eh<3brrri2urVuosrr]nl:MbW9JRh#<[\\BDs8N&p +pA=a\qu?Qns69@RjPU"8rWN9#qtp +li7"a!W2orrY!_JT!7n^s8Dunq>LQ4O!rr2irrr2rhrX]&-s8W&tp\WI? +RA$1Ba8#Z5qu?]q!r`,toDa=~> +lMhOrq#16im&X,]D76pm`6ZQIb/h[&`o"l:`r3mV`W4*Xau.5S[nKY/XM)g#dE'MJcHFJJb0e\_ +B,*I;I"m9/JUUB4f>u+R_T'R6`l?*@b/hQ@`l?*@r6#&\oZ$jN#Kk--`Q#p=b59E_b5]PV`rF0] +bl5cbbl5cIaT'E\bl5cUa>D&abK@]=ahU%t?$^m@<@]bhK3QXM`llEEcH=,JbIY*15V"3<hcg_T0d?d_s+h=?]8M?^fpE +s8W&opA"L_r;ZcppA]X~> +mJm1brr36$q#CBSlIYk.s8MrrrrN,sh>[BQs8FVJqtp3bq"4Rcs4lo5s8W)tqZ$Hfr;6Eiq>^9] +?\qlP?"/P79hVYfr;Zfrs+10Kr\"!GrVZ]is8B_l2_e$diO*V"r;?HeqZ$E`s8;Wks8D'Fo]ap+ +s8Dims8Vulr:p7$s7cQnq>^1>)D!*T.ba[]rr +lMhIqqYpNos7*"]SC$s/rr;uorVlirrW)uWrr)lqrqZR%rr;TUV6\MGrr<#trVmQ1rVZKkqt"6R +=@$+tK6L!]XShtms82fos7?6es7uZms82fqrVc_hs7uZhs7?6`s6KXsq#C(s7lWe-PIaF)(*u0rVclsrr2osoDTd1 +rVuors8)8pU7RpMVX=?^s8N&urr;uss8Murrq$/?~> +l2MCjqu?Zlen\GkNjHR@aiqoMaN4A#hoPf:qo/TTrl?M1bf\/>Ue4L-Y/JZ6bfI`GcH!iB`R;oN +3bgo8;-83`3\,p2bKe2Jrl4uZs2tA_s2G#Xs2b5_rlk>`rlFuVq8``Z`Q#p=b5KQac2Pofb/U3q +"3/F'b5B?\`r +m/ILnr;Q`rrVQKls52ehrr3*!s8W)tr;ZctrqtRNrr;us#5nAoq=sj_rr3AZjSSuWrVZHjqY^?l +rtG5.rVQMoBnV()8A=rprr;lqr;D3GWW!M0rVHQor;QQeahkr$o)8F^pAFpmo_JFZrVm#VjR_]Z +rVuoq"onStq>UEirXSu+rr<#gBI#&D*@A,js60Fns8Mrprr;iVipu3hp&G$hrq-3lr;6Kfs*t~> +l2M=nrr2rto$X,(W94!TqYgHnrr;us!<;$Wr;Zcqq#:m's81QL[b(3Pqu?Zqr;Q]prtPD2s8;es +BS:t(8A4flrVucprVliorquffrql`krr)lnrr)fqrhBGkrq-6^rq$0Xr\=HOqZ$TorqZ<0_89II +rVQQgrr;uts8)cqr;Z`fs1u@a[\!9orVuosr;HZqrr2rtqu.**rVuosmq!Rc%i-=mqZ$Ekrr;Qg% +fcM+rquW=W1KrVT[3Q6rr<#t#Q=Z%s8N&trpp)>~> +nc/Uer;Qfoqu6U(iM9deA"pQM_op@ +b.u-Ac-"2PbK%uVdaj=MChbFRT@1m.cH",H`Q%nus2k>_r5ScXrlY/_s2tA_qo8HQrPnlYrlY8b +rQPAeaJ-]QaSj-Y`W*jT_#_Hno>h*T_nj1,_u%FQ`pUo;c,moEaMc%ag.F(-S.#*UqOOab0AD_b/VND`l?*Bbfn5NaMZ$Cd`Frd*=WDdOjh_Wb5]Zgb/hQ@`q%1d +a2Gm6ai'*>8Pr&eWp&sLs8DffpA"Ubs8Muhs*t~> +nc&Ugr;R3'rr<#qr;6Nog>UfOrr3<$s8;forr<#tr8IYTs82fprrN,srqc`mrVlfup!WI2ru:q7 +s8;fls8)Qkqu?TiqY'[`rq6$`s8VloqYu-GW;Zeprr3`.s8W&qqtU!ds82`op](!erVQTkqYgEs +hWXFma8Z,=rr +l2M7mqtpEnqt$\YTu-?qs7cHkg]%6Q"9/?"s8N#qs!RdFs8)WFTtoLVr;HWpr;6?jqYC0gs82Tg +o_AIdpA"Oas7lWks8Vrp!<;!Vr;W)^nc/4[o)J1X(Ae%1s7cQnr;6<`p\b'fqu?Nmq#10grVmE- +p\hnF_nDfAqY^ +nGiIcrVm]1qu?]ddWAo"I':9)_T^0D`l?*@bfn5haSj*\aMu<@rP^>-aND`PcHX#3MFD*?[DB\o +`lQ?GccF/Oaj85UdEU%ke]Q+\cHaAP_TBg=r5o&]`q[XQaoT`/nAtOIrlG,^r61kW!6G)XrPeZO +!64WO"3//]s2OTM(!FY4`5/6Z8OlTZGiS>gqu?Wk +q"Xabrr`/pqYg'dJ,~> +nc/XgrVuoqrr3K-r;Zfjg$.2^s8W)nqu$Hn!ri,qiVrlWs8N&rs8Mus!<2uq!rMrmrr3)ieF*;7 +rsS]'rr;fos8;oms8N#t"8;Niq>UBsrr<#os+10KrW<-!qu6U/o`+pfs8Mcmq>^3hs7lWmrqQHk +rr3,bm-a#Zrr39$s8N&ur;$6fp\k^'q>^KN.N]i\,Z"74s7lWYrWN)tqu?]qlO!(Ah>70NrqlNh +rVm!!qtp +mf3:crr3H+s8N&uqTth3UsB#=rr2ourr(OLs8Vuqrr4,?qYU-gs89m0_;+tds82iqs7lWor;ZTm +rVlispA=mfrr3-#s8Vrqo)8Xhk5P5WQ2^OXnc&OfrVlZnmf"X5qZ$Tpo`+pfs8Mcmq>^]^Y%K-8's7O+%R@p+KfDPUF +s8Muos8N#js*t~> +n,NCarVm]5rVZ]jk,DTi?B)nCaN;NE`l?*Bc-4>ia9'B&aSj6^aN"4us2HM1bf\)H`lGZ48oB]s +\\u>-bg=GVbf7rKd*9kccGITH`QcZNaN2N?chtrbaSs?\a8j6YaT'Babf\)saSNpVaIUKNaSa$X +`VmgHaSs3Xa8j6Y`W*sGa@O@iaj%o@c-")Tbf.rJc,J#M^WO[DaN;<6W0pF?0/YS\[_Tc*bKS,K +`lu]Ob/hZHb08#L`QGp8cF"#b&/Z'9dE0SIccXE/aT'ERaT'BscG.<*H;m7%9l%$&q>:'hrVHBf +rVc`uqY9j_o`'F~> +nc/XgrVm*$qu$Hns8;lr"7PO2hu'pWs*t~> +n,N@cr;R-%qu?]j\>>e!meQh]s8CUL!<<#s,5qN?s8W)ts8Vokqtm(j_;b:jqu?]os8;iqqZ$El +qYL6jrr36$s8VloqYg?`rql`arW)urrqQMErql]orVc`qs6]dhs82ipp%n[f+T2$7s7ZKls8Vop +s8)cqr;QBhcc*N*U[n +n,ELeqYL0hrtbM+pA"@9GZRsa[(su/aN2B@aNDcPb2C>@b5KNbb/jM#s2lb5cI'VM_73EA@>qD/ +aN2NEdDj,J_9U'GaiDTCa2l<@bK[fB_8O@9rlbAbrlG)_"Nng,`l7nrs2alUrQ#5`aNDZHaMu6s +`;moLaS!^SbQ,fVa9'N.bl5ikbf\#J`l?+!b43PLbf.`F^VnI=b/:m6b.c*=a2c!9b/DNPg!m@! +7k,`<5,5QpbfnD]bf7E4_oU'Hb/h`Lbf[rE\]Vt7`DI9[$l$4taMQ!9aNDTD`lQBJb4E[hb082M +^6;et6psq4j7<6Jq>^Klqu-Hm"8_`bqt0o=~> +nc&Ugr;Qlur;?BirrDrqrs7NDjm`^Hks8;lr'E%h/rVucor;$Bfs8Dut +mcX!,f`(mQrqHUEls8N#tq#1d'qu?]Ykj.p/ +hYmKSr;Q]krVlutqt^0Zs*t~> +n,N@crr2uprr3<$s8(iQTV]^)r;Q]srVY%A.f]MIs7lTns7cEdVlmi.p&Fgerr)los8Doqrr;ip +qu?Wps8Dorr;ZfpoD\Xen,<=er;QHjs8Jbmo)8Xfmf*._kl(kas8Mfns8;oprr4#9rr;utqu?]o +s7lWhqYSg]_RQB!o)Jahs7lKhrr)llrr"j>s8Dusrr;usqu?WopA]X~> +n,ELeqYU6jrsnu(qYKpal*4W%AVn.,aN)B=ai)6@bK\&P_TKd9a2c-?_oC$Bb0%rNb5TK]bR2M6aMu3=`Q#p=aS!^Sa9T`+ +b0%fH`l@nps2P)[U;dJ[b0%rNbf\/Pqof,dbf]Ou%*HfQaN4>&n]3B%c,dcH`ko[0 +_T'U5`Q6$Ba3)K@ahlBPaJiMr*@EM%[`$8.bg+SZ`kT7(`lu^,b5]Zub/hQB^r4OLd=2Z;J(2W] +^;@hfaT'6`aNVoQb/jS%r5eoXs2Ynsd)a55AkYK]8mo:"rVuiks8W&rqu6cpq"XaYs*t~> +lMpn`q>UQVj6,J$rs&E$s8DusrTX:lr;Q`nrr)lns8Mlpr;ZfqqYgQoqtpU:"s8N&ss8W)[nFQ,#rr30"q#CBnrr)lqrp]r<~> +m/I(bp\td#]q:ahg\:^IrVuoqrr2ldrr)lqrXo)-s82fos82irqu?Torr)iqq>^Bl$N'Ic[_Vb; +r;6HkrVlosr;?Qms6KXcs8N&[rql_Hrn[SRr;cinrr`5trVc`q'_VD(qu?]pq#B9l_n2q^qYgEm +s8W)s!<2utpAPU#rr2iqs7"Q_pAb0krr<#orr)isrr2iss8;iq&,uP&s7Y9CS=6+=g@kRHr;?Tp +rp0T7~> +n,NCcr;Z`o&c_L]OC2$rS\N1c_oKm=aN2ElaSj-Wa>h2\bJhWGbeh96^WOXFcHFAO`l5p:`lQ6D +bfn/Lbep>B93A1*`QHE%`5Tg;qoBu(bg"AO_nj71b07oG`Pop@b/)97RScC@ +huK=rrMrprr3#ur9=1arVlWms8;lr#5eH"rVHKkq>UEl!WN#rqu?ZuqpFHar;R'%q=jdd +rr<#trqufnJc;ABs8N<&r;?Hirr2lr!ri,srr3&ms7ZHl"Rka;m+MC +i;X;NSYVg[qtL-gs8W#rrr;NfrVuiq"o\Grs8W#rrs8Q&s8DfmrVlforr`9#s8Mus&H2=TV7FS? +s8;]ls8W)us8DlprVqHJUAb5lrVl]o)#jL3r;HZqq#CBjs82imrmf-$^9$#^rVu3_&-)Y/rVQWp +q>('brql`qrVHNls8N!!rVlfqrser+rVlifor+FTQCkGJr;Qcqk5Tr~> +ir9Mco>tWR=`(pg`l#g:`Q%AfrPnfW&]_u1ccsPYb/V6:`lZ`Q? +hu=)ThX'+ms8Mfms8W)tr9F:]rsJT%r;?Ehqu?Zqrr2rorr2p"r;$-brr3H,s3KZjs8W)qs8W)t +r;?Qsqt^-dJc;8?rqufqr;Zcq%K6>'rqcZis8V?Jp!r7)s69Lcr;Zfqr;Q^#p](9krVlclrW<#p +r;HX$rquWls8N&ur;Q^*lJ1gkpYu&Ls7uWms8D*[J,~> +iVs5`qnnqrRa:m1s8Doqs760fs8DrsrsJT%r;?EhqZ$Norr)lnrW2rqrVuos%0,k\X0oqBrVuor +r;Q]qs82orrr)kJrh9>kr;ciprr`5trVlfrs8EQ/qu?Top\s3e]#CVcq>(!RrWN0!s8Dlprs&5t +s8;iprVHNns8;lorrE#rs8Vj#]UbgaUn-$6s8N#t!<)'[J,~> +ir9Vfp#;BI=&4jlb0.`@_o9dAb4 +hZ"&MkO@+'s8;lrs8Dimr9=1jr;Z]ps82imH2%49s7?-arr2p%rqZ?ds8W&srsHs5mJm4_r;Zfr +r;?Qsqt^-eJc;>Ar;6QoqYpd!r;ZEhqtpBms6p*[jN[Z'l2D1hrVuoqr;69ds7u]pqu?Kks8;iq +"T8)mrVQTo&GuM-qV(E+lf@U9s8Mrrr;GaWJ,~> +i;X5_o=1BiUA"TdrVc`nrr;QgrVufp&,cJ*s8VurpNH6+s8V`hs82csrVQNks8N!1s8D>V\&nFF +rV?Hmrr2rqqu$Hls+10@rr)j"rVZTlrqud*qu?Tks8D0"]XkMMr:oIS%f6/(s8;flq"t*es8Vur +q#19nrVlcq&,H/'rr2rmgRk=8St!=]rr2ouqu,XVJ,~> +ir9Vhq!6Ld=^#(>bL"2C_8FLBb4I0aNDZLb0%fF`P]^Ac,7<5 +bK$_X>&0tP`koa8^q[q2aMu6Bb(7X,a:?A:c-48J_o9dEccEr8^Ac(]\AuM/bd`R$.k*8G[(= +h>[o;mbmm?s8N#trquTil2D(ds8Vojs4JhIEqK))s8)`nrs\i&qu?]pqu?]ifAlf,rrN)trVm!! +r;6DCrh08hr;Zckrs\hurV6?js8VHUnF,-1s69L_s8DorrrE&trri&qqYC0erW2rrrr3T0qtU$c +q>^Kkr;ZfQl0\',h#@ +iVsAdrVH#RUnQ!'s8W)urr2rgrr)lprX8Q%s7uKjf-DGUo(Dt]rr)orrr)usrVlZn#QFMEVmFtJ +r;Q`rrW)utquH`qs+10Brr)lsrqu]prr)j-rr;uss8)N+]!f,Qo_SUfq>U9krVlZnrVccr!<2ut +"SVllq>^9i!WW/urVZ[$q>LctjUZ).NrVuosr8ms/~> +ir9Dbqt97!?Wg?`^X:(#_Z.j\b4beq9; +aN_PY;GD54]>_k5`P0++aN_rJ_h#n$aSZ5t+Xe^$POhr;6 +h>[oPhs'(qs8VurrVcTjlM_=oq>('jr8'%N,pAKKq>^9drr)lsr;Za%rV?Kjs6\J$qu6Trp\Xph +s8N&uJc;;@"oS8qr;Q]lrri?$r;?0brsA#^oAo!4s8;lel2C\]rr2uqrr32urVlispAape!W;rr +rt58-q=jd`s8Mrrs8(R8o(;/?rVllorr2utk5Tr~> +ir9Viqu6Hi^Rh6sgA1aIs8Duss760fs8Dp2s7uKjs8:_)*?H2]q"asds8N#rrVZZprVZ[1r;ZEd +`hp5so)Jadrr;rrrr)iqs8RZLT`4rj!<2rqr;QZp%f?;+r;=@B\ZWI8s8Dujq>U9krVl]o!<)os +!;lcq#5S5ss8Vfmq#: +iVsJeqt/s0>$"q#`5op7_9U9Io#UdLrQ$nC_o'XBaJoCS((#$-a3;?=aNVoRbfn/I`Q63EaMl': +`Ql96GsbAWVo-Qma2c'9aN_rJ_o@8KS]Dhqc-OYW`l5s;aMkp/`k]a5XHu1%-R_cb^;SF?bkfEX +`r4!U`X^&2bK7`Fc-!rEaN;ZAanWsW`r=*raMu3G^ +huE`U$ifSWkN`*IqYg9jqW[tiqu?]or;ZVE,U4EZ\,H4%qYgTpq>:0jrrN&squ7)bjQ?@>s82Zj +qYU0frVqHJT`4uhs8MrrrVd6'rqufrp](9Xp$hM&r;Qcpl2D4krr<#srVucprr)`ns8Vlgrr)j7 +rquKdq#C?ns81j9lgaEBs8W)us8;ips8M0\J,~> +ir9ShrVccko"Cch[HRPgs8N&snc&LerVd?)s8W#ps8%3d*#^$.qtg6err2p%rVZZos8VuqrsS\t +\Z`Eko_e[fs8Drs"TJ>trr7QKU&P)ls8N#q!<)os!ri/trVm6#rqXFK\@/=*qYL3kqu6'a!<)os +s8<9(qu?Zoqu6Wqq"k!hs8DorrrE&trt##*rnX`KSY)Ffk5YD[s8;lr!<)'[J,~> +hu=2blHtAj;/rVTbK%NFaNF(prPnfW%`QZ8`lQEE=:c5X%$T,^rl>)^rQH/#`Q$0KaMu6=^;\%5 +_mj2PmV`llQGa32NGaN)9@b0Im#a<8LA`lcNLb/VE;b085R`kn'M91V]PLX#Pfqtp6hs8VuYs*t~> +i;X;cqZ$Tpj6>A!s8Muls8D3]%fcJ*qZ$Njqe@LNpq=sjdqu76+s7cQni6pW[s8W)m +q"OR_rVllqJc;AB!rW&srVuoss82lorqlcqrVm#dnF?(krr3'!qYB@S&-)S)rVccrrVZZprVuil +s7cKnr;QZps8!,rq>UEohr=1qk2uR?rrrB$s8N#rjo9i~> +ir9ShrVQTorSF<>R+_66s8Duqnc&LerVd?-r;HKlrVH>#)]TnAh>7*Hrr2p3rr)fprr;ipq>^Bk +os:F(h"^pLrVca!rqlWms+10Crr^ +hZ"#Zl*k8(:QrN:b/MQDn]:[KrQ$q?`lH$Eai;b.'+bNT][P3Nb/hZHb08#I`mN2T`Poa0_Rd_+ +_l[<m\`5Td;aiqlHaN;KGa2?-ta +i;X>brr2rtrS?`!s8W)ms8N&XrWi9l+<_pYpA=[b"8quhq"k"#r;$?ls6%>[o)Jaep%%kUrrW,o +r.4j?rqucurquNdrqu]o#3tXIo$RL:rr<#t!<;lo&-)J*rV-?lrr;lqr;Zcqs60F`r;6EirrVue +pAY'qiU$%,kNMdB!ri,sjo9i~> +iVrrWqYpL%p!&nqV;V:Orr;lcrr)ljrX/Vt6lI!b-1^m1rr2rsrr)lor;cirrs/Dpae68koD/@b +#6+T!rVlcqJc;>Arr)iq!WW/urt>>0r;6Hh_n +hZ"#]p"Z0C;HTU0bf\&In]:[Kq8iQX)U[@t$4@1Oe^Mje`l5s=b0%`FcdBtV_8!h(rl#Pg_7G1i + +_90m@b/MQDaMu6?rPnlYs2soR(WOA3b0A8V`l6! +iW&rW%fcJ's81X8mGIsBqu?Wpir02XkU%As)IiI:rWN9!p\4U`rr`2rqu$Em#L)E,s8VH[qYgEq +rV6;Arh9>hrs&K#qtg-_q>C7!n+#u +iW&rW%KHG-rnOHDV69gmrVu`arr)ljrX/Vsj<5E_'OCA/rr)lrrr)lqr;ciqrt>8,q!b#ich7>3 +s8W)qr;Zfrs+10Crr2lrrr;uurr2p+rVuljs6%A8]=ag/qu6TtrVQQlrVlcq!rDrorr3<#rVl`p +s8N&rr;HZ`rqud-rVZNiqu?Wpq;eHDT9u+AjSf)8s*t~> +hZ*WO$LtfQ=]oIQ_9:0Bn]:[Kq8iQX1!e:i$O74G8(H)4`P]U5b0%fHbfn5L_84.4b/hQ@bfIAg +5ZW<:a2-*I_nX.6dE]mcaJ-cTarAF@`Q$'Hd*9MGb1+;.Fupum.kR-n_T0d=`lJ)$s2b/Y&&HH+ +b0J5Mai_]JbK.fC_u7UTaT'ERa;i4=`QHHMbfIc?ahPp,ULI(o78R^@pAP$krVQQQs*t~> +ir9)Zr;QWo"7tR7i;W`XrVuoWrWiK"Zm7+D,_l0prr3H)q"Xads8Mojq#: +i;X;ds8N&up!T_0T$m<3q#Bm`rVuQi$30e*+!_aHK)GWHrr`9!r;HTos8Dusrr2utrr3/ul^593 +nGE7crW)tLrh9>irVulrrso&.s8Dlor92/7[(;n-qu-NprVl]prVlfnrri2us8)QirrW#nq"t*Y +rr)j1rquWis8DuqqR`>lR\HA!qu-Qmrr1mVJ,~> +hu=,`q>0gHZYroCMS%*KnAtRJq8iQX1!J(nOpMg2$Y@l4`PKC0b08)N`P]^:`l?6KdET\Mc-4&3 +Tgi.,XN8;ua2l9@d+$9jaJ-cUas"jG`Poj +ir9)Zr;$6i"6\V-l2:S@rWi>sr/DpH,)Z6rrVm?)q"aphqtTp\s8Voiq>UBq`p*9trs&H%rqlNh +r.4jArql]urqlQeqY^@%jmh[!ec5[Ks8Mojr;HZlrX\u,s7lWorql`iq#C9lrVYjXs82lrrVmB+ +s8VN?m.BT&kPtSYr;Q]q"9/2proO01~> +i;WiUrVca%qn&MpWo!OKrUKmcs7Q?tqt^)F*ZlDWr;QZp"9/8rr;HWsrr)iqs8W)srseM`S%[>E +s8Dlqs8MrrJc;ABrr)iqqu7$&s7bira1JdZo_JCa!<2urrr2lrs8EK-q#CBmqZ$9`s8;oqrVcc^ +rr +hu<`Tq#:*rl&K.N?(\mL`5_AhrPnZSrQ6h7ajb,6$jULqb/M35`QHELb/(g0`lcNOdF6F_aNVlG +^pTt$>_&lf^;J"1`R!&XJ]OS,s2k>]rl#YmaNMWC_nELTI3omR0Li%R`Q#p?aiaV-s2tA_rPfAe +aNMNIeBQ.``lHQLbf@[#`rF-[b3m@LaplG3^:V"2=]A6l>)qS:rr3&sq"s%MJ,~> +ir9)Zr;6Kn#Q+Ats3pE%h#793rr +huE`U$N9i$s5KZHU8RV]nG`Cdo`"mj#4ZLW\+'@qrVm'#rVcZmrr2rtrr2rtrVlg,qtg3JV8(CK +q>U?ks8W"JrgNicrt,2+rr2'!^VI"VqYU*grr)fps8W&rrr)j-r;Q`ms7cQlp](9js8)corVu9a +s8Dlp')h\+p@Y>[S!g%LmJ6b\r;Zcqi;\<~> +hu=2`qY'X^oYbTP^W"IFci)*%bfIfJ +_SEjQ;-Ik7\A,boajA;TJ]OS,"Nng)`Q%nu)p-(7\[.uO2\?lZ9s21Hc,R`CajADU`lQ +ir9)ZrVlcq#5S/rmc!NfrVtjU#5%rpptu#IqYgQoqu6Tp!;uils8*'!s8W#pq>^9irrUd6li-nd +qZ$ToJc;8?s8W,u!rW&qrVm#lkOmTjrr3?%s8N&urqZBhrr;io&-)Y-s8DcnZhsRqrr2rrs69O] +rr`2rq"t'j#4CX@mI&X*rVm-$r;ZfrqtpBRs*t~> +hu=/brVlisr;":#SXIjus7-*es7Q?ts7H?ip>,TArVlfrrr*'#rqu`oqZ$Tos8NW+s8;oli2X?, +li7"]s8VtIrh'/is8DrsrtbS5s7kWu]>Ub^p%n^bs8W)srr<#tr;HWmrseu+s8;ZlZMF:lrVccp +r;Z0`rquirr;R?(o=UonS"ZA(rVlirrqucpqu6ZqkPp&~> +iVsJgs82ZirVuTQS6]#cL:>1Bo#UdLq8iQX1!%AKd*^1Je'ZF_`l5s=b08,S`PKC0bg4PXaN2TJ +`l5d<`Q#Z[8Q'N#]YD2&ccXC]aJ-a#b/hQ@`lQ +iVroUrVm9)r;HZqhWt!mrVuoVrW`Dur;Q]os8)]rr:p*drr`8rq>:0krr)ruqY^?pdF@[brrDur +s+10Drr;orrY>J4rqu`os8W&algX-0s8W)qs8)Zmrr3*!qYL-drX\o+s8MupZS>Y`qZ$KlrVu-] +rVm<(q"4=Zs7+t0o'5Jlrr3#sqYpKsr;$0fj8XW~> +iVsGes8Mrqs8W)nq4aIZT&TV:rr)lirrC6jr;Zcq!<<#ss8N3"qtg6hs8N#trt,20qZ$Qm +qqnBPbjbQ-qu$DDrh9>gs8W'5r;6Nii5WC@ZGZ)0qYpNos8MrnrVlfrrr2lr%f?;+rVZPt03J2K +s82cnr;Z0`"9&/rrr)j-r:n*tR@]hDXS);es82coqu6`ss8M3]J,~> +ir9Vhq"jsfs8W&kl.g/[<_K(#`q%4L`qd^Taqr.=c,IiLc-4PWaN2BBb08)Pb08#J`Q%nu'ZIu/ +`l?*E`6HBB]9Q8dCmq]F`Q*PNTZI/U+i_F;`QHHN`k%dW2]3\o?(/F>`Q5m7aNr8X`P&t)b/h[& +`Xg&4c-=P[L^#T`c.:(]aijY&s2b5_o#M]kc-42H`Q69I`4^_$8k;-JHeRc`s8VrjrVccqqr[p/~> +iVroWrVlorqu-NsjQGjZqZ$Kms8N0"r;?*a#QOSus8;iqqYL0nr;$0drVm#tq>C9mrr*'#rVuok +rVlrRg?AJ7#lac$rqu]kr;Q]q!<2uqJcUBqqt^-Ks*t~> +hu=2crVufos8MiITpV[Up\asgs8W)t!r`,tqu??g#QOW!s8;iqqYL3jrri?!qYU9kru(h7s8W)s +qu?]ls8M]Q['7C-r;HNmrr;usrVufprr7QK_>aH4q>LHorr2`n%K?>&s8(Hb^U^;Gq>0scrri?! +r;6Hls8N#qrso#'s8&rF*?nPYqu6Qlrr)llrr)lqrr;uurr2irrr)j*q"pSSTU_dNo_JLbs8Muo +rrE&[s*t~> +ir9Vhq>1$hs8;`ipuo,#92Js%_u@aU`rF-[b5KE[`qd^Tap?)2^rXd?a3DX&aSj6aaNDcPb5TE\ +a;;h5_o9^>b0S;Kb/Cl^=%m.e\%fc#b0'\(rlP;``l +f)GmChr=.gqYpWqr;?Nns8)foir8uU!<2ut"oeGtrr<#prW<#pqu6TseDB]os8Vm"qYp?jp%eOb +rr2utrqc]nJc +huE`UrVmW3q=qn&S#W^hrVZTjrr;uts8Dlorr2rWrr;uurr2j!rr<#trVZZpr;cirrtYM0p8=k# +f_kdIrVliqs8N&urVZTlr;V?I_u9r?r;6Ejrr2lqrquorrVlTl%fH2'beh0*VW[gRrVlirrr<#t +!WE#os8Dp-s8)ciY9G&)+"+YtrVuoqqu?]qrr)lrpAYa's8;cos8)WbV4sNMUn?B=rr)lsrnd[*~> +irB#Vs8O&:qu-Qppun8Y92o<2`l5p7`Q?'>b08)PaMu6WaS[b/M9 +iVroWqu?]qrr3,aj6>_'rVm#us8DlnrVuop!;t1B"8quiq#:9qk1oG&rVuop"T8/mp&=pi"8quj +r.4jarq-3rr;6Bip\4R^rr2p#jmDQsf)G^7rX8c$pqS-q*@<&)o_e^e!<;oprVlotr;HNkr;ZWt +r;Z`qs8)`p#3Y7 +huE]Trr<#r'D2%,URS"1r;Zcrqu6Tprr)cmrr2rWrr<#irr;osrr)j%rV#fhVS:[Urr3#srr2ou +qu6Tn!<2rsJcR$s8Vrqrqa:-R\-.>[J'RNs*t~> +h>dNP!<)os%di,)>XhGP_8s[>_SsI7rlbDa`o"l;`rF-[b5BK_as"jKc-=>Lb082S\>WV"?[o,n +a3_`Caj/2TbfIc@`Q$'CJ]P^L!6Y;b"3Sj3b5TBraND`Nb0&#L`5fj7_Qo_&.3T]`OKlXna9'B# +`W!mVa:HJ7c^kLQ',Lapaj82SaijS$s2b5_!6kDd!mJg.rPfJk`l>j8b/V`J^N`sF4[iD2iV(n' +J,~> +gAh3P!<)os"5W&"iVriZpAOphrr2oss8::DrVlutqtpBlrt",.jRW?NpAb0jr;Zfpqu$Hn!r;Wh +Jc,pOUhp&G'irr)fkrr2otrqu]k +rXSl'q>:-jrr;ZMk3qKtime!Y~> +hZ!TTrVmE-qu$;^MVsXB\fDg@~> +huiQ)2B`Q#p=aSa3[a8jB]aT'E_bR_e1_m4G] +C3ukiZHC>5bK0\&s2b5[!m8a0J]PaM#K=Hm`Q63C`r +gAh0Mrr39#g#M#Ls8Vohrr3-#r;Q`rd/NtC!<2uq#QO`%s7jFmoD\airVca%rqlNhs8W#or.4jb +rWW5pq"ajequ?Qm!<)iq"Q].Kk1oq8l2D4ij!>lk*[6F4rqHHks8)]err2iqrql]trquZlrVm&[ +l1F/qhUDLT~> +iVruYs8N#t%fZM.rVlWfc_ID0XS)Jirr3#trr)lTrqufhrr)cqrr)j,q>^0GTYAbIp](*hrVl`n +s8W)ts+10erql`qrr;rsrr2rtr;liprVm/f]tD"]Z2aIrmJd(a&H;La+s%X?*/!e[pAb*kqYgBm +q>U;dLVr@IKrn%1#~> +hu!l`1!pW+H!aMYp:b08#L`lQ$@^77u+ +DP4)J`QufL_SaC9b/h['b(7XOa90Dq^;9ce)90b;`P]U5c-abYaht]kHorf,0M/[`c-=E%a;<(C +aMu3<`lcK9!sJl5$uO;;aO/2U`5hhrp;m9Rrl+oW'$/5Bc,IK:_5(a<9hJ5m[-7>afDg@~> +g]%EUr:g*frr3Gge*6&ls8Dror;Q]nr6Gs8V<^&-#ue.N:!no`+jes8W)os7uZkquH`ls8W'.p&+jhs6AtB +l0%*\s8VuIs*t~> +iVroWq#CBn%Jfb3TV/(0qY^Blr;Q]os51*jrStr2[@kb=pAb*hnc&Re!<2rs&,uT6-RKTXJFW[:qu?]qq>C9k +rqcZmrr2j.rr;lqs7Z)gStVREWjqs)p\reEJ,~> +hu*leR_4;,E7d`mW&Qbf\#H`PpcVs2G,Z`lA#!r5ScX+Nh^?_8sjCaMl-Ac-k%W +X$'!\VR=Ie_oB[7`lQ`Q$!@`k%UN1F+@rEO!+#b/s4q(s:+J +b/VE>`lupm$4QtKB@>GUbK\;S_p&)!s2Y8aaN4>"qSa,faND`XbJqE/[;Ar?6q_$Jiq2rqs*t~> +g]%EUqt^$arVmG[i:#J#s8Vlkrr;uqqu$Hndf07G$2j\trr<#orpT^_rrUd"g%kXMr:os[rr7QK +_uBW7!ri,prV6Qmr;?QkrrhNEo((B1rrVoes69Lnq>XG=.=_6mq>^KorVHQmqYgQoqtpBj!<2cn% +fZ:uqZ$T]lLF?1in+50eGk%~> +iVroWp\ta&r;6#aS"ut^9is8;lp +rrN)rJc +hus2G)Y`r3sY`r3jq`lQ5bc-42HJ]P^L+NhsQ`PK:'`QHTWdEKVJ_847*[$4B7*$?Y\Xieu/]uKWa +(s:+Jb/VE>aNVrc1B@bef@7sfccXAJc,KY$s2b5_!6G/Zs2FuTs2>YkaNDZMccsAEZ[,VC7SQp' +f^\ggs*t~> +iVroWqu7*&q=s[_rVuooio&nTrr39"rr<#tq>($idf07G"8V`fr;HWrq=ade"7^Bg +q>C3kJc:-grVm#Zlgj&_rr3,us8W)ul2Ckdq"RsJqYpL!rVQWnqZ$ +i;WfVq>Uj%s7uTjfr1C4V<@CIs8W)trrN-!j8T#WrVl]os8N&s!<2rss8;rprVm<'p[ZY#^!lKN +rqHBjrr3#urIOsarWN2rq>1$ds8W)srsSN"lG^TUY+t:Qs8Dlqmf*7d&cVb/p@VF?q#C?nrVQWn +qu?His8N#ms8Dp.rVlcprVuomi0pI.S=Ze=rR:ds~> +hu'm'^V';-Tp+cHX>G`Q6h9jVk1]Z8(:bg"DT`l#^6J]PaM+j/'UccsPM`Q$0Me'H1U_nN=VFZ(6[*%R8ZbKIuK +_8sXfa;E(Dbf\#H`lcQUcdFRYdb3'raiDQE_Tfjo`rF-Zaof`)`l7qps2>&ZaNFM+%*Qu<\X6&i +7S6U"\an5LJ,~> +iW&rWr;R9+qY9m`s8Vurn_rcqnGiLes8N9%q>($is3glMs8;Zdq>C3k!r)Nfr;Qi:hWXt="9/2n +r;?SGrkna5rrN,srqccprr2iq"o[KGp>Y04rrr;ts8Dinl2D4lrr;ojr;6KmrVlisr;6NjrWN/r +qu$Hls8W)sr;Za,s8Dusi:641n_s +eGfjSo)$J)Tq/^jp&>!krr)lUrr<#qrqufqrW)oprri;ur;ZZnrt55-o^&-T`:Wd'rr2rsrVc`q +Jc +huC]ZS..`luiV`l#gUaT'6\aNFM+!Q`7Z_^*4Bb08#Nbf\#H_o'I7 +aN_lJ_SE^Y5YYd][*$V=dETbO_84-BaMZ(BbgFneccX8H`QHHQccX5@Z(>ed+!;@dQatDcbf7K7 +`l@De'$AGCcI(+eb0S;Vb/_TFa2Gm;rPnlYrQ5Gf`l5j5`Poj,Z:/YQ#kkb>Z +rmq+"~> +ir9)Zr;?Hl"8r&nr;?QtjPoOlrVca#qZ$Kfs8DuWrWE2qs8;lr%fHA)rVufqrVcZkq"Xacr;Qls +qtpBkrs[B2k5YJ\qtg?ls8Vuqs8W'"s8MnHrlP->rVHNqqu?TlrrqrQo',&_rr2utrr3*"r;6E@ +rWN/rqu$Hmrs/Q%q=sjaqYgBm#2eS2o]b91rVuosf)L7~> +huE]Tq#:s*r;=-rS"dt#r;Zfms8W&trSd_es7lWkrr<#rs8DrsrVuiprqud!rVZTlrr2rtr>5D1 +oWG4&hu!^?js82fqs8N#trqh9Gao3(OqY^ +h>[TSqYL*frtPG/qsiKu<_?;=]>;D)_pI#[ahc!Ua;)S9bL"bdb0%W=ahu3AbK\;Ub/hTArkfbq +`lcNMb.tZk>t\(LXi/W3d_s#?]uJ4;aN2NF`IZ+Qah6X`PojUbl,]`bQ,`Z`r4!Yb5]ZdaN"1t(!4P?cHOJTai:j!A5Plj8m7eC +pA4[9s*t~> +ir9,[r;?Qls8Drorrh9SkNMpDs8Viqqt^9NrY5;1p](3cqZ$Qpp%SL_s8Dijq>:0grrrE!q>'pc +rr3)fcg0oqrsSi+q#C9jrVZTms8RZLb5MYDrVZWns8W)trrE#rrsIcJkO$s^s8Vrnrr2p"r;6Bh +df0:E!<2rs#Q4Jtq=XOZr;Q`rk6gYIn*fQAs8)cqqpk^s~> +huE`Up\tm*rV5ZQX.#m6rVcWnr;Q`ls8V*X%K6>#rql?_s8N&lq#C6gs8W)srVZ[,rql]irP4u= +bjtf4qYU +h>[TUqY9pdrtYM/qZ$EWb%b +ir9,[r;?Qars[`BlJqpErr;rls8N&WrX&N"qSlInq>UBkrr3/os8)Qer;HWqrqlWnqtpBpakcIb +rrW2trVc`urr)irJc +huE]Trr2utr;RB.rVlTGUR@jThXgF?s8DusrSd_^r;6<.:uD!ErVZZp!qlTlrr;ur!<2ors8Mus +$iBkojdW?jn+lhYrVQTsrr)irJc +h#@HQq"k!i'`7\%s8VuYY[gI)>ai7Qb0&\a'ZS2:SLqS+f$DF\bfIHAaNVlNb/jM#(!+PA +bg"AR_nj*oSOH=eR'Fh'S_84.6rlkbl`PK=(_oBd?aSNpRa<8XLc-=JR`l5s=bL"JUcH*c7]7NpM91Ms"e+EG/ +o_/76s*t~> +ir9,[r;?Qdrt#&-s75.4jludEs8Durs8:jTs8NM^-79qnrqQHlo)A[hrqlZo"oeDoq"t!grWE)t +s7Q?j"6.bqnbrIhrVulrrrN,tJcs8Drpe,Op~> +huE]Ts8W)trVuos&,Z@naeY])Yh]*Rrr;utqr.PSrs@@',95J8pAFs^rr3*"s8W)tquH`ps8Dp, +s8N&js8;][ZCo"as7lNkrrE&trrN,tJc1$grrW/qrn%1#~> +h#@ZYqYU!!6Y;bs2t>^ +'YMW/^SY:"?%',g^W4IcH4,H^U9eg?8aAb/Q\pjaP+eU_84%.`lQ_nN[=>uF*d=)G28rqH-Yp=0+m~> +iW&oVo`"pjrr3B&g>CoRs8VurrV6EOrWrP_/gDJ_0\64%rrrE!r;QWnrVm'!q=jXZqu-Nsqu?]j +rVlunb2VL]s8W)uqZ?`rs+10irW`E%s8;`hqu-NumGmQjdd[58"oA9!qt^0hrrW)nr6kTEquH`p +rrr>tq#CBmrr3/Zk2tjgf)>UNrq?9heGk%~> +hZ*KPqu76,s8W&lcCUK!]Cl'os82QjiVj/`i%,rh*@oS7rVlisr!*0#s8Dlmr;6Nnr>,D0s8Vil +s7Z*0S@6<6r;QNjqtp?ls+10jrr)j1rr;uss8Vure\]/!S'1I(qY]pZrVlrurVlfro)A[hr;QQm +s8N&um/Ht`(&mk%r90oLTqe0Ff_>%;s8;osqY^?Es*t~> +iVroVq>UTrqtg0drr3N-o%]jFla:HM#"p+r8(qJ>Lc-ODI`lS/!rQ,#]rlc8& +aMu6=bgF>NbIt0-5#YjfZbsZ#`Pom>aaqOSa;Dn2^WF^A`lH*>WJa%_,UYNsZH159`5g.$`rF$X +_Z7^IaT'9[aT'BaaMu3u_Ze$!aN2B@oZ/,sbfn/J`Pop@bfnGT]=tnO>#S?r92r@cpAb$\pA4d= +s*t~> +eGoOI$2aZ!s8Ua;hqn#(rrW&qr8IVcpKT0i,:"V`rV?KirVufpr;Qruq=j[[r;?QurqucorVQTo +"3]-Mo)8Rkrqu`ps+10krr2p!rqZHirrr>WhW*&PrVllprr3)rqt^0hrrN&pdf'@HqtpBkrrW,q +q>C6skNqKsjP\hlrr`2rr;P4GJ,~> +hZ*WTs8Dusp\t^%q5C?aW6G,9CpAam[qu?Zjs8W)sr;HWpmf*(_ +r;Q`ro`"ghrVlis&,lP*q>]`hUR@dLTW.bZqY^BnrRV"!~> +irB#VqZ$Ql!<)iq%fZ(UH>ZbmL:=t4a32lQj2`1^bfVgQ#n[^YYgph;_Ssd<`l5s;aNDZLb0%g' +`YQhAb08,TaN(EN6qM'IY.DToaMu9BaaqOTaIb08)P +`kfL2o#UgQ!6G#Vr5eoXo>psS!65#W(WOVAbK\#?^SG^G7o3/fFQDuis8DilrRCjt~> +f)PdLrqm*"p\Xsis6Sb'h#.0Qp&*hK&-)J%IOZ?\,UTeoqu?]jrql]ur;6Bhrqud$rr;orq=t!d +rr3)YbM_mgrri?!s8W%KrlY6>rrW,prVlg#kk+N(de<\?"SVlqq#:9m!<2uq!<2ute,BIIqtpBk +rr`8ur;HWp#4'e,puUN'rVm)mp\b!grqb4GJ,~> +h>dHPq#:m's8;QU\"f[qg%b:@s8;oVrX]&(pN\#")'1!)qtpEnq#C6i!<2ors8N&s(]++-s82`o +qu-Qg_5"EXq=t!irqufrrIOsirW2lorVmQ*k/"jGVl&&As8Vins7lTns8N#qrUBgdrVulqs8)]p +s760errN-!rr*]2qYU3V_jd6jQ_M4`pAP$crVulrs81CIJ,~> +irB&Ws8)fpr;Zfo!<)lr&H2M+s6J!`?rpKn\%Bl3ai`Yb&'E5BemfO,%hK&*c,\#M^])5!`Q$!A +c-4>L_8!h*b08#PbL4PTcGIK:X`$j5@%OdJbK.]FaiAtRb/baG^r+78be]lp2Cp@*4+FKH_Rn"< +`5os:`QHTYe'5kJ`q.7Yb0A8VaMl'7`Q64$bQ>r.`r4!Sb596[`W!ah`lcNJa2YopI83@*6s>St +kP4rGq9f4m~> +fDkjLqtpHnrr3G]hW3Mgs8Vllr;Q`rrr2g!rr<#tr;?!^%K-8$JgDWb+Y1"urVu]ks8W)ur;Zck +rrMlnrqlfnq#:9qbhh7Rrr3*"s8W&qrW)lqrW)ukrr;nIrqucos6BU\rrr8`n*Sd%rr36%r;6?d +s7uZnrr`2rqu6Tpe,BIIqtpBlrrW2tqu-O"map^nkj[@"rr39#r;QTkrqlTlfDg@~> +h>[KSrr)Qj%K65$k,>#NV9er4qZ$Torr2rrrr<#drXSi)oR.f%)]U95qYgHjs8Muorqufrr<)rp +s7cHk%f-+tgnD'djn/TOr;Zfqr;H]rqu6Qoqu6WqJc>ZJs8W&s!<;Whrr;rr%f?;+rr2]M`4i[R +`Td-prr36%r;Z]ps8N#rrVuforr)lmrr;rsrr)lrrW)obrr)lqrY>J4s8;f_^RqEsPc(;)qtpBi +s8W#oe,Op~> +iVs#Yqtg9hs8VuorrE#srtkY&_fK!^=GON*aO//O`l?*@b0%fFaN4A'nAl-ac.L]a'+kTV$&csJ +bJD=!`Y$A;c-=>K^V7J#b08&Ncc+2SrlYee_6Stf9j3da]=u,%b/aG$rQ>,\rPjE0p;m*MrlG)] +prNHSs2ZM/aMGd3]="2E3$TIg@\UQQ`59F1`R2T@_SaIBe^`!e`Q63CaSj-YaSj6laNDcRbfIc@ +`Pop@bg"ASrQ+oZ"3AX+b5B?\`r3mVa:l\%k;1q"sd`s8LIHJ,~> +eGoRJ!WW/urtGA2rVbF6kiVpFq>L*ds8Vrlr;6Kn"TJ;qrr;Ke%fQ/%nqJJ8)(@$1r;Q`prr;cm +!rr9!rVm*$r;6 +g]%9Qq#;'+rr)`ns7GBHUn+@=rV6EkrVulorr<#t"o\H#rr2lcrXJi"s6kO_*>KkIp\Xsiqu6Wo +s8Moq!<2ur!<2rss8<9%rVl9+Tteb9p\t0mrVca#rr;uqrVc`ms8Drqs+:7Ms8W&us7QBdrri?! +s8W#rrt###k.J:?ZD6=WpAP!gs8W)trr3#us8DrsrVufqs8W$(s82fqq>L3is82corVu]ms8Vck +*rZ* +i;`iUqu?]pr;Zfqs82ir)=,:W=\;VY^qe=;`5KR1aj82N`Q$'FccjQ$a<8UGgX'R@#65#3cHFAP +`Q#p=b08,Tbf[rC_8F10`lQ6uapc>/_S^Kl!<(OLJ,~> +c2SLTq>C9lj6Z*is8V`ks8Dutr:0U^rr3&tqu#s_rr3E*r:cp1/L@e(rVuoro)AUf#QFYuqt^6i +rVlg"r:8(jir&fYrVQNkrr2lr!<2rprVlcnJc>]Is8Mlps8N#qs8N)urqufq#6"T$rVcThrVm6$ +iqi#nhZ*WNr;HWpo)8^jrq??k#lal%qqe+Dqt0CWs8;ipq#:EpqtpBm%Jff!s82irg?\S&mFM47 +rrN,sr;Qluq=ss@s*t~> +df1![r;HQkrr<#me"`P/W:Ku\s8Vuqrs/B"s8Muqrr;Hdrr3K+qXg@$.3Ytpr;Zfrs8W)ts8Drr +r;Qp!rVc`pqu73)qYg +dJs7F!W)Wkru(+$APZ!(X2VftahYj>bJqcG`Poj@c-=JTo#M`kb0nSad=DE1"HM'Yc,mrE`lQ^0^T`e`7TYG>Y//#o`Q$$Bbg+PYb/hZDrPefVs2b2^J]Ir7`l?!r +_Z[ruaND[)bl#Q]ar&.=`5BR4`ko$SP[Gmo+s0FN^VIt6aMl7"b5]O"`l5p:aN2NHbg"Y^^rF=0 +`Q?r.aSX!Wa;2q>`PfjDcc#@mV;HTdUp\FgkrVQKjfDg@~> +c2RhCrr2p.p#kQ!i;`iSr;-r,II@Is8;osnc&Of!WN#rqZ?]p +rql^)f[8aEs8W&pqu?]pq>:$g!ri,prqQZnrr<"MrW`;tq>:'er;?Qor;ZWor;Q`qrr2p$qu-Qp +qu$Hn%HQpMjkT\3s7Q0eq"t!Zrr;j)s7ZEkq>:$+,pT\ks8V9]s8W&s"9&/rrr2otrr2p%gu%,\ +mb-t*rrW#nrqufrqZ-WIs*t~> +dJs7G&c_k/rr;rhaJPZ(XlKHVr;6BhrrE&tr;cirs76-us82ipqu?,k+L1b@s8;iqq#:9mqu6Km% +K62%rr2ffZD=tMo)8OcrrrDuqu?ZprVZ`qqu?ZoK)YWHs8N#r!<2rsrr)iqs8<$!p\Ogf&,bV)^ +:gYLf(&e8rr;ors82fos82fqq[NT"s8VokpV.gmY5&%sli-k_qu6Wq'`J%2s8MuprS=?GSXuLF^ +@D'rrVZZp!ri/te,Op~> +ci5'br;6Eho@ojI;bp_oaMGm5`QHBIccF)C_o9^Ac-#Uu%`ZB0c-P4iOoUNkdEfnSrl+u[b5KNc +b/h['`rF-[`rF-Yao]Z(`;[Ro_RZXO7oj,bWjT^bbK@f?`Q?->b08,SaMu-8`Q$("blGn +d/O1Hr;QTn"PDc#hqJ&/#6+Grs82]krVlrsqu$$a&,ZD+rVlWgs6TIZr;Q]ns7-*gs8W)squQWl +q>Ucoe'ZkArVuopqu6Ttqu$BkrVlotqu6Blr;Q]qKDklKrV?Kjqu$Hj!;u]n%fZA*s8N#ts7>aF +m-E?]rr36!r;?9`qYL-\rX]&*s82imq>ZF*)CHcfs8(pXs8W&s&HD\*p\b'krr<#Vk2bpkj4aJ3 +"TJ8or;QZps8LULJ,~> +c2S1Nr;6Hkq50m\T!%Y_rs&E$s8N&urr2itrr<#irX]&,s8Muqq"OgRo`+gfrr)irn,E:c'E%h/ +s8Dfjqtp!5XK&_`li6qZqYpL#r;ZfrrVZWmrqucurVZTlKDt`Iqu6Qo!<)los8<]3qZ$Tnq>^?R +e%rf3WNtsis8)cqrr)lprr2rorX]&+s8;onp\osq'I"XUrqbs[rVucos8N`1r;?Tpq"s!JTqJ6M +T<%DTo)8Ics8LLIJ,~> +c2Re>rq.,shK<.'9P:ZMaNi#MaNMWHaMl'5`QZQKo>h`f_T0a@ccjbk`6HfVbfI]E`l?*@b0%s) +ap$#3aMu&b/_B=aNVoTc-48J_o9Xa>jrWW>uqu$ +bPr1Rs8VuVV4ONW\FKOsrVuopr;HWsrr)fcrr)j*rVuosp%\Lcqu?Klr;Q`irWE,urr;lp!<<&t +&HD\+rr;cnqU_RD]AiG^s8Moqs8N#r!<2ut"9/8srIY%Fs8N#orrN,trqlcqrr3`4q>L +dJk9drr<#no_/(Wn&00G;cIeC_og0Ga2#U8bf[rA_8c#drPf8dc-anYbg4YXbJ;08rl4rYr6##_ +"jP94`lQ7"ar/FEaN)<;_SjO3^p8Va7TY23\\c>*`5B[?rQP\nb/VE>`lcNOc,tXZrlkAdrlbJe +aMu7!`?3+@b08#L`l?'Ftrd"IRHQ +lcB%Es2b/]'@"VBcGdZB`N3BI:/k%pHc54@qYpNoq>gE?s*t~> +dJj=JqtKmarrDinrt"&MkMkJ#s7QElrVuciq"t!ho)8gkqYgHlrVca"qZ$HmqZ#gZ"98B!rr2p) +rqZTkqZ$TRh;Io'rs/Q%q=sd_r;QHjJc>QErr*-"rqucnrr2iq"TJ;oq"t'j"n1jIlIY>"rsA]) +s8;osrV-3eo)A[f%KH8(i$T`o*@ObBo_ngLrr +ao;kKrqtcPTUqsloDeF^s7uTms8E#snbramrql]pr;HTo"T&/rs82ZmqYpNns8E#uqu.?1rqu`p +s8N&rs8M`hm&d+cbO#*%qtpBrrr)cnrVlcurVlcoJc>WJr;H`srql]urr)fprql^.jN"g?[]@RG +q>UEms7u?bs8W&qs7ZF's8N#tq#&ki*#o_<9AT1;s6K[_s8;itrVl]mrso"l\YkdcR@L20qtL'f +qYC-nrr)fpfDg@~> +dJj4Frr3u7p@e@]qWbe1=A`.B[*$2.b.Pa:c-48H_8F73o>hH^`50F5`lZQQc-=8P_ofgl`r=$Z +b5TWbbll83`lQ7"asbKV`lH->`Q#sGa18^R<(9GpQ)qC*]u%e1b08,Tccs\T`Poj_FR`r4!Yb5BbhL)M +&ek`e28%&SbNm7J`rF-Yaqi1AbK\/O_l@WU78?HVFh@&2rVufqr;QNmrR(Xq~> +d/O7Hq=jabrr2utrr3)RmHEC&rri>uq"ajds76."qZ$Tns8Vuos8W&ms8;lrnc&Lbs8N&u!<)ip +"T%lgs7lNls4m_6oD\amrVQEgrr)lrrql_IrqQKrrr;utrr2p$rquTeq>C3k"n([@kLnYhrrDll +rri5us8DugrX]&*qtd]K-QsKZD"RT(qZ$*bs8W&sq"tX$s8W&koDejUk4eK +c2SIVs8MrrrVcWTTqe0Sh>I*Hs8;lns6optqu?]ps8Vuos8W&ns8Dutrr2iqq>U?ms8N&s!W2fo +rY#80r;Zfks8Vril*ROqaQ`['r;HX!rVZQirVlfr!ri,rJc5lQrVlfrrr2lps8M`l)#a?rcG6rj +U:'Lfs8W&ts8)]krqQBjqu6Tpq#:6l%f?+sYTY,-(F3_cq>^ +dJj4Frr3i6qYBseqtKBiBNRl*PeY/Yb/2*:b08#Jrke`Uo#M?YbK7]Dai29BbK%E@`5]puaT'6_ +`Q#p=aSs?^bQQ)0`lQ7"asY9Pb/hZD`59O=_9L0A];/n48RRCS\@fSk_8a^He^`'k`PK=*`Q!JM +29`aMu*6`5KmEcc*oK_SsF3`lQ7!b5]Q_ +`Xp,+_9CK$(E*tq&5(-he&KN"aSEjQaT'BobK.`Dbg3D39LqH@UBqr;ZNjq>gKAs*t~> +dJjCIs8;ckr;HX+rr<#^kiV!^pAb0is8N&trqccps8VWg%fcJ(rr<#srqcWorVufonG`Cas8N&u +(&e14rqlWlr;6 +dJj4Dqu79,rr2rtqn](rU8?98qY:*frqufrrW)uhrW<,uqu6U$rr;lqs8N&srVc`ns7uZms8W)u +r<3#ts8N#rrsnu,q>^Hns7XU2UqP@Eq>UC%rr)cmrVc`ps8N#rJc5ZKrr)cos8<&trVc`kru1k6 +k0UiT[]-t+qt:!brVuoprVccpr;?Nnrr2rnrql^(q7@jd)]U&ko)JXbs6fmYrrW3"qYpL-oZs)! +R@B_Cd.[D:s8N#tqu?Wmrr`9!rVkFKJ,~> +dJj4Dr;R]6qYg?lqW4#Q7nZd?_8jO<`5]g`koa9`Q?'=aNDZH`lA"s +s2b2^rlG,Zs2b/].*0WWb/_WHai;X`is_X5[/W*Xfo(B_91*QdEThQ_nj1,`Q!JM!QN+X +_(WdIbg"JYb/M33_o'=*^V$tNGu-%?/2CV)\@]r(bK@l@_o9jFc-42K`P][7aN4;%'?J25`P]R5 +c(5X\'bV"(`m2TCb4*LJ`r!ghb/hZ@`lZ93Le^N.6VV3ljSJfS"Sqriqu+nAJ,~> +cMn"GqtpBls8Vuqrs7HKjPT\2s8Mrprri5pqY^9LrX&W!s8Vuqs8;Zjrr;uqs8Moqs8E]0qtpUNps8;fo"8r&jrVca"p[@k;f@BWq!ri/s +rVm3&qYpHns8Mios7uX)rVuZfDB(/LaR]B.s8Vrqnc&RgrVlip'`It,qu6WprVuoms8Uj5m.'Z5 +iUQpI!rVlnr;Qp!r;6Eke,Op~> +dJs4FrVlrurVZZp$iAA@U7J40q#10js8;lqs6fm^s82d%s7lWor;Zfpq#13l!r`0!rr*-%rr2lp +rr2lrs8Dips83B*d@[5HanPT.s8)Zns8N#t"T/2urVqKK!r`&qrVm!!s8W)srVZ[(q"j="]"P,; +WTX0f)o_@Ir;Zfns6os_s8N!1s8Mros7u]j +\u20kS=cY(qu$ +b5VqJqYC0kpu7oE9LW-E_#DR`ai)*9a2lBGn&POPaSEjUaT'E_bR_t5aiM?O`l5j1]XW_n:L%LsTa_T0^: +bjidO`rF*raiMNCaN;NA_T'C4\oh.M7n69\U>tnDrVulqc2W:~> +c2RkCqu6Tp&,Z.us8VlVmG?b#s8;oqrr2rtq>pTnir0,]rVc`qrVca&r;Z?cr;Z]mrr2fps8Ec2 +qtp +dJs7Fs8Mus!WN&rrs\54U8"-VoC_nYqYC$cs6]g_s8;j"s8Doqs8Mus"T8;jrVc`qqu6Wos8Mus +s8;]m&,5bj_O7RBdH^T-s8W&trVl`ns+1.Xrqucorr<#qs8W&prqud0qrG`,\Zr*:i:HsBrr2lq +rVZWmrr3H(s8)Kds82fnqu6Wqq>LNkSVs7u]pqu?-aqu6ZqrVuor&H;_,s7Y'L +S"6"BTZd3/r;HTo!<2ur!<1=EJ,~> +b5VPAqY^?m%e7T"=&2Fq[(j`2`PKC2rlkJib4kk_nj:2aNDcMK#e;Ac-=AL_o0F1_T;`"/'cMa +]sYDPKN0i]/2C+i[DTJic-=5D`Q$-Eb/D->ai23B_SsO6aNr)Lb5TK]`Y$2,`6m2b'L'RXcc +aSuAAqt^!crrhiRk31\"rrrDtq>^Hei;O5dqu?]os8;`jrVlisrVHKlrVuiq!;uin#Q4T$s8;fl +r;-F)hq-fInc/XfrquWhr;Z`nqgnbGr;ZTsq"jm_qu$9i"mk^NleL4nrri<#rVZZnrrrE"qtL-c +rVm&qs8;oqq>UEirXSu+r;HQIp&+jir;?Tpr:'^as8W)uquZlqrVQU$rVucQm-sH/hsrr1FIJ,~> +dJs4E!<2or(B4@6rVuoe_5Ep$VY0W\s8W&mr;Zfli;O8equ?]ps8;]hr;HZqrql`hrqufqrr)j+ +rVQQmo(hmbSu9$DnG`7`!W;hGrr2onrrE&trsnr$qYK3i^qd7PWS@7KpAP!nrr)fprr3&ts7uWn +"oeQ"s8Dcms8Muts8N#trser)rVP:?qu?]or;ZfplMgb^(&n74rVccroDIFBU7RjHU!3?4s82`m +rri?!r;HT?s*t~> +ao<1Urr<#sqWuOfA5Q90[)(#7cH!fBd*0Dta90T/b0'P$s2t>b&'`/=a2H0DbKeJ\e&fPE`qmdR +b5B<\`r3mV`;dgjZaQoK9i+r1Oe];'cHaVTaMu6?K#e8@c-=JSa2Q0Cao9L!bK.T3ZD)M51bpX; +9p!'4_Tg->_o)Gms2>Mcb.l'?bgFSZaNquIbK@s+a8j6Za:ZG6_oBgAX2`?2d)a;Qbf\/Po#UjN +qoBf#aMl3>`5]j8[D%u.9hJ&W@\DNJqu-QprVQHgrVQWpqu?WGs*t~> +ao;PDrVZTgrr2p-khtRcirB&Trr<#rqu?]SrXSu+rr)lsr;4k3qZ$Ehs8DrrrrE#rrquZorr2p$ +rqu]os7uQl%ckpbf^]"Aq>'m^r;Q`rK)PoNqYC!arqQ]orr)fpr;RH(m.9Jli;N]Urr;ono_ndh +s8N#sr!iZ!s7l?fp&>!jq#C?ip\ko_eC^rVk=HJ,~> +dJj@KrVc`prr<#ts8NW-s8W)j\=]:cXS)G\r;ZfPrXSu+s8N&uqt\P,q#C0es8)]prVc`mrrW2u +rr2p0rr)Kfs8Mccjg1\a]A*2\q#1*iK)PfNrqZR/r;-6bqYpNXc,%?/VR+kEq"F^es8;lrrr2lr% +/^)(qu? +bl@_A!WW/us8W$0qX)dg>>J4-\AQ/+dEg%YaN!bkrQ=rWs2tAc&'E/:`l6'Ec-iQ5dFQFYbkfB\ +a8=!uaMu3:`Poj:aN2KC]Yhh+]XXYJ8PN30U9D&ArknlWa3'"T2Tj7gbK7iHb082Ua2,9kY,I.b +5r^535^BNMa3`,U_oKO.`lQ6@`Pos8beUs6`mN8[a4&8PrlG)YrQ#PaaMc6?aiMZH`lcWQbKJ/Q +o#UjNqoBc#a2Gs:`PfR*[uOrg8P)liOlZ60s8W#qqtg0dqu6cqqYU6As*t~> +ao;D@rVlg'rqlZos4?Mnl0/-H"8i&rr;Q`UrX\u-s8DrrrVu+30td8\qtp?lrr)utrquZmqYpWr +qYgBm$2X]"s8V<2fBi;3rri;tr;Q_JrX&Muq>0p^q>1'fqu6lYlgEodd-^f3#5e/hrqcTkrqc]p +rr3N-p\"%Uqu?]jq#C6krVu`irX\u-r:g6kq"k!gs8Vrms8VWgs8W&ss8;oqqYq!%jQ#Ckm,Qq% +s7lKgnc/Xgf)L7~> +dJs4Fs8W,us8W,u'`\.2s8W&kV5TfZ[dF+kqZ$Tlqu>XS&,lP.rr;uqs5kX[\Ffgtqtg9nrVlip +rr)isrr2g5rVZ]qrr)fjs7cQ\_P3m?dI7)7s8N&srr7WMqu7<.rVQ9fs7lKklGL6D[Ap=Up&4mi +"9/?#rVZ]qrr2lprsJPtmd^&OqZ$Noqu-QmrXSu,q#CBiqu6Qos7uQls6K[as8W&srt>8,q"E=? +T:2LFT!J"grr;orrr)isrr)cqrm:[q~> +c2RnEqtp?js8W'2s8;EAFCn0ID6:k"b0ePU_o]phaSs?UaT'E^`sfr,b0J_[#ni`TfZM82a8j9W +aF$VT:1,1t3 +UU\1eb/hTG`5fj9`r*mga2>a-cHG%nc-+b`d)jPLrPnlY&^&26aMY[/`4a%/bKnMVaj/,taT'E_ +aSa'p_SNmmOBbIQ<)?kUh"LaIrr)`mqtg0fr;Qlrq"Xa9s*t~> +ci=%Equ6Zqrr3<&qu6WqhrNbPnG`IfqYgHQrW)forsJW%n7a$f/V*Knr;?TprW<&rr;Q]irsJc( +p&Fmcs8)]jqu6U!pY4Q`oDJLcKDtoKs7l]mr;$?rm,[NncJAI!%K?1rp&"dgqu?QhqtpBkrso&% +pY_$=/q;7ErVuims7uWurVuins8Vrnrr`2us8;Eds8W&srquWm#P6a6n`o<"o`"jmq#:9go)Jah +f)L7~> +dJj4Gqu6j!s8Muqrr2p.rqlMpV4O9knG<(YqZ$QjiVilXqu6U0qY]_l*#^5Qnc/F_r;HWprr2lr +s8)`prsSi)r;?Nnr;ZZjs8Drss7H`d_kjKFe)p9%s8@NJrVllsrr3K-s7>[HiQApPZ_+V`o_JLd +!WN&srrDonrri?"r;?QnrsIbA:E=]*q#(0jqY^BlrW`?$rqufrqu$Hrr;Q`oli-qa)>sO7r;6El +rm7R8TpqUH^ZbXqqu?]ns8N#ss8MuurVl`p!<1RLJ,~> +c2RnEqYL*brt,+sg4!L-:N7M[)C&, +`Pfj@`l-!taT'E_bU:TXaNV5157Lhpcd:%\`6?3>`l?*@b0%cG`PTX9_8sdBcHXMSbf9%ks2tA_ +&]Vl4`50:.^l2Jj9MSVrNQ^qlrrr,nrVcWjqu6cno_A=5s*t~> +ci=%B!;u`o!<)`n"mF\"jP9V.s5Eqgqu6WqqYm?:.39]Y:=K";rr)lqr;ZcnrrE&rr!EB$s8W&t +s7ZHdr;Qoab15A8r;VBJ"9&,prql^#q<@25k0N/`r;R0!r;?Tpqu$Kor;?Qlrt"u+nq/PA'dY+J +oD&7_s82Tj!;?El"o&&rrVZWnrrN,rnc&RgrVlip%fZM.rr<#sqpt'!lM9`-qYpTjqYpKtrqu]n +rql`qrmq+"~> +dJj4Gqu?Zp!WDrnrXJi']V1pkWS%4Mr;ZfpnG`Feq>U^Ko +s8<#srVl]o(B4@6rr<#ps7u]pnbr*sS>WX2i9g1:K)YcL(&[t+r;-3go\@Bt\uV=qrVlisrqu^*pA4-&-Q`mE+ +bl7eBq"Xa^rsnnqiJV#:9NmRqc,n8Ua7.1L`qd^S`Xp&'_oKsBeqFJJ%1rt$YKY2haT'BW`W4*Z +bl>icaT'6r`QH6?bK7N<]#r1-`3a_Y8PEH=OI`_mK?,=W^qe%:eBYk0RX.pl1H76E@>Ckj\Auk3 +`l?-@_SjU?aN2NJrQQ)!d`c5<'ab][!6WL.b0%T?_o9X:aSs +ci="A!<2or!<)]m"mY.6hs'q8rrrE%r;6KmrVllsrVZ'^&HD\,rr2gu-QaNT,:+S,rVlcq!<)op +s8Mrrs8E#rrqd-%qu?TnrV-9hr;Z]kqu6fUcI^q>qYpQprquWl!;u!Y!W;rjquQfpmJd+brVl]l +rr2lop&=pis8N#qrr2Tj#4CU5j4)BDqZ$To!WW,rrrW)srr2rsrX\mu/L;Va)&jYEK(o +b5W:WrVlforr2lqrVuopp:R)oT!d8Fs82]mrrN-!r;QZp!<2lqq>U9k!<)or%Jj*L)&sM7+!-mG +qY^ +dJj4FrVm$!qY9mbrVllsrVmW%fSEU(8njU(b/q`Kb0%fF`Poj:aSs?^aSX!QaSj-Y`>$/,aNW$h +$jdIU&J#4sd)sJQbK7m%`[AjPccs_WaMl'3ahu3A_o]s6_o9R=bJ_<(MGlT+;eqGq[CElc_oDJm +s2P&ZrlY/[rl43D#0P$0aNDTHn&PRMaN"(tr6"rY!li=$r5el[s2b,\rlG,Zs2[FI`l5g4_S3.: +HVujW/0uZFQE@s?_p$ELbK7`D`lH?OaNDTJbg"J[e'`LQ&/Q6$'F4hHcI1"T`l5j7`lQ7#aq)k> +bKS;Sb0%`A`5TX3b/X;!!6Y8]rl4uZr5oVm`l>s6`4;">;b9Y`=aSceo)/OfrV6BqqtB[WrRCjt~> +ci="Dp&>?tq#:13jqu6NnrpKgdrW)lnqu6Nlqu?TorW2usrr3'!rVlcq$iU#$qtU$fs8;osrql^" +o^25*ceA-hs8N!!qZ$KmrttV4qYpKlrVlGd.jH;a*ul:D*@!2mq#CBYrqucrrqu]orr2g&qtg0e +r;-?jqZ$Hkrs% +aSu8>rVulr(B=@4rV<(`US=XFp\Odes8Dutr;-Bkq#Bpar;RE.r;)p,)]'V3)]r&C7)rVZQiqYL$P`MBEGa5H:Zs8)Wm"T8<"qYU0is8EB*rr;rqrr;ur +rr26`"9/?"s8N#srV?Hjr;uusrqQNmrqZRDrr;uts8MroqYU0fr;Q`rrVccrq>UEfs8)ZlnCl!s +]rIa2fCo4?rVc`prVuj:qZ$Kms8Vlor;ZcorVuPf-QX3G(Ddr0)BLB^pA4g\rqucrrr2lrrr2p" +rr;usq>V*+s8W&tqu?Hii2EH@USjQ]m/6k_rr0h8J,~> +bQ%S?p\u!+m+#TM;,CMl^;e7=cH+#H^:_;$b5TWgb/V?:`q%1N`r=!l`lH6GFp&$V(_R]"1pkHN +cca;KrPnfW6c[-M;aNDZDaM5a+ +^7/A)6r.)nHHPLInG`Uhqtp?@s*t~> +_>aoBrVQNls8VKEjQY7orr`,lqYpHn!;uil"8i#qs6oses8W)ur!rMM:*pr',TeGJpAb!fs7lTn +rrW,pr;6L!r:Tser;6BjrVlotrVZ["o$,G3i:?mFrr`2rqu-Kn!WDrpr;ciqs7ZHWr:'^as8Drm +r;HNkr;Zd#rr)cnrVlcq"oeAprr;lkrs/Mlk3(p`d-CT0!rMonqYpKo*;][4r;6Bip&Fj"9dCW$ ++s&$W,TJ'M+_LcLqZ#j[r;Qp!r;6EirVlfo#lXYtr;-Emrr)fq#2J>*n_W-frr3-#r;?Qn_Z,,~> +_>jQ5s8W,r$1YHqTW4aJqsaR`rrN,sp&FO\rVuoq&,4FC+Vbh5(aZifs8)]orr)lqrrV'*rr;rprqH3MW1KBKP+TA]p:UEU~> +^&K)EjMu[J:Kpts_S"%;bf.H5_oTm@c-=JR`l#^6nAkXPa8X'iaN:D5%1/]rPnlYs2kDeb/jP$s2b,\rlbDcaSNn)b/q`F`lH3AbKA#P +bfe2Ud)Eu@]=Y__X.4W?2(0^q/7+^T`PK@0aSs?^bl>g1c-4;JaMu6?bKe2Zd[)Qm":u1V()[o! +'+"q7gWR[^`l5s;mDoIN`Q$!Ab5TTeb/hZDrl+oYs2tA_&BD`,_ntdRV6sPMsiV'tbJ,~> +^]+Z?qu6NnrRg;pjk]q9!W)`nrri;qq>C0irrW2tr:0aurVlfrrr2lps7ZJD.Nol_II$k3!W2ik +rY#/-rVuils7Z?frr<#squ?Tlrr)iur:g3errgokdGO9drrr>srVHEhp\tBpqtg3equ$'pcrqQNlrquZtqu-HjrVlfr$N0_ss8Mros82Zlqu6lejm:mKf'WG9"ShT^qYU-g*rYp7 +s7u]pq#C0bdTS1L+"&16bk/4c+ +_>jQ7rr3B$r7e$FSY;n:qtpBls8W'#qu$Korr2osnbrUgrr2oss8<9(p&'4a)]0fGlM^_`qu-Kn +rVd<)rVccpq>^3erVlisrqu]orVdc5s8Vrgq=a@8[%=/$^tAM[qu$Hns8N&sqtpBkrr`2tr;QBh +s8Mrr!<2oorr)iq!WW/arrN,trVHZorVlcq!<2ips8E#sq#:Nrs8W∨Q^,qWuqP]#(qRZHW(= +q=jXZrr;usrVmu=qZ$Tls8VloqtSQ?-mKZV*`pMTYoar)(*4j@s7uZis7-*grVulqrrN,tr;Q`r +(&\%1rr<#sr;ZHKUn=KTTWFmGq#1$drrE&9s*t~> +_Z']9q>:0k)XlmuCL9_.=II%HbKnAP`l5s3DjdE*]u.q2_oKj= +`Q%nts2YJgbg"DUb/q`GqT9#``l?!:_o;Pprl4rV#/eEt_o9^>rQ+rWs2b,\"3AX-bl,ckbfIfC +_nj1/r5^>0bg+JWa2Pm1_8F1-`5T[;d*0MI^9F8jF]^RV3\r3OCT"VX`lH3Fci(r``ZN.7bg!uF +cbe&Od^)=](_@>e5dB"4%M0?d&eh(raiaG!n]:aMs2b)[rlG)Y( +^]4<2#lFT#s4Hf%lIZ%8#lX]"s8)Njr;QZp!W;ibrW)onrX\l*q=_lW-:m_-rr;fls82Wk!<<&t +!W;oqrs8T's8)cqqu$Hkrr`2to_81]#5Qcne)06_rVllrp](6ls8W,uqu$6gnc&Ugo)&^hq=sd_ +r;Q9es8E0!qtpU`uoB5&pkh=;:r;R!!qYgBfp&+[cs8O,3r;Zfis7,B&.NolV ++@`g9r;65.,U"BT,>n@;s69O_s8W)tquH`krrN)tqYpfrhWaFombc=fs8VusrkSPa~> +^],2Mrr;omqV\'FS#E+@q>1'erVulps8W)trVlcrs7-'grVc^.s8N#os7c-',9]JEs8Dunr;ZZm +s8Doss8Muur;HWp%/p5+qZ$Tns8N#trr2os(&Rk-s7uBcpA+R7WhH>t^tS8Pqt^-grri?!qu$Bj +qZ6Wnrr2fpr;QZp!<2inqYog\!WN&qqulutrr<#rrVlfrrUp1*r;-9is7Y^6d(m6"X/!/`rVZWl +qYgEn!<)Zl*qfL3s7QE`l?js")B0Ya[JKk!p2;)l)AsPiq#16lqZ#s^rr2utqu-Nn!rMoqrr*E) +r9Ve,TUV:BUS$W@rVHHl!<0k8J,~> +_Z'W7rqIN4s8M]V]4bJ><*,a/_o]m>c-+8Wb/hQ>`Q69Ib4/]s2k>crlk_mb/hQ> +_8!h+r6#&`s2ub3ai;98_8O:0_nj.$\@8lLRXf*K3@u^0.nt`"\A?/+cILRsbf]q)rP],^`k014 +c,7oH`*iQY&ets0QIaU8r?e^;X_qSqpDqo\fW( +_Z'l?rVZ]qqu$L9f +OXDOs+!;Vrs7QEkq#9G(+=/$M,ddUOkPkMZrr2Zl"9/?#rqud#iU,_!n`J6trri<"q#(-,s*t~> +^&JW@s8DojophbKV5r)Cs8)Nfs6KXarVZX+s8N&qqu?KfB%?hZo(rCcq>UEkrXf&.s7cNmqYg3d +r;-Hnq>^Hnr;cioru(e1p%S@YpAFRUq9l+:Wj'CslL=WGs8W#nr;Zfq"T/,orVcQk!WW/hs6TaX +rs\o*rVZQiqYU0frVlfprrW2trVlftqu6Tp&F\`1`kJmbX.H]Oo(MYJqYU9l"T8,mr;QQm!<)os +)Yj7-p5gRH(`OG6ScAHas8)cY,SqC6(a$$]rr2fpn,E@er;Hcrr;?Nl)?0R5s8;lrqY/O0SsH7F +TVMAVrVl`pr;Zfrrknbd~> +_Z(MPqY9j_rr<#sl-WsD:/tY^_90d:cHjb]aSs0ZaSs?Pa9'K+b5THs`lQBEbLFnf6a+kc_T:*H +_p$-?aN4A'3m>jpc,%BC`llHHbfIuK]#W%>b/V94_8FIBcd'\Rb0%W8_n!:cLJgc98m6@gMOh&k +aiDNLda-(UrknlWa2\+prkncTs2OrWqo/HPrQ+oZqT/ZY!6G)Xr5eiV"3&:$b5KO6b/VE<`PopA +c-=__aM5?sXI=QO;F3H&1,q-mKrMMm_T'U;b0%fHbfn0-`rF$X_]ck:ai_]MeC/LN((:`c#_C^N +cd'_c`=X"6'b1V7eBZ4Xqo8-HrlY&XrQ5l#bfe&E`llB:PuCOQ=\r:Bf%K9Zq#(0irPnkf~> +_Z'o@qYL6lrVZQlrr3,Uio\tQrVlusqYBpcrr;ltrr<#^rXSu-r;ZKdqu?Hjs8DrsqYC-jr;Q^& +rqQNnrVuios8)`or;Qcprq-EirVuosq#:Tkg!\F&iUQpErrDobs8W)uquZcnqu$9hqu-Hkrp]sf +rW<#pq>U-kq>:*hoD]!nr;?NnqY^@"rU]::j58M>j7rHM"9/5qr;?TkrZD1C6,s*t~> +^&S-2)?9^6qWt#PUmnF=oDedcrr)`os8N&urr)f]rr +_>aN5rqZWnrr3W.keq7@<_H__[E?V6bf[lD`lS)#n]:[Os2b5[!6G/^&'`)9bg+5PdE0JN_T'U: +aSs=Abg">SaN;HAf%JR,d*'SK`m;uT`l#R+_84+5cI(%baN)?LdEKSBZ'fAp3B]c1EKHotZaRK[ +^&Gqc_o9U7a2>a2_nj1,_o9U9aND`Nb/VEt`r4!Tap,r0aN2BB`r!jS`VdaUaSs?[bU^iU`Pf^4 +`Q66Kc+LZuZ`'.3>X:5"/hf.XFKVp\ajS\gdE0>D_8XRBc,fk'rl$&#`6H?Qc[lH7#S@RXHJ6fd +ahkpBb0=?6*uc"-7+06#aSX!IaSj9WaSs +_Z'l?q>'g`s8;fmrrqTEjQbXmrr3#qrr2p"r;6Bhrr;Qgs8Vuq%fcG(s7lWoo`"mjqYpNmq#1m* +rqQNmr;ZEGTum?1p](9krqucsrquck#P[les7Q?eq>($qma]V1hrXA)qYp`sqtg0er:U$ir;QKh +qtgEiqsF@_s8N#tr;QR!q>:!_q"agbrqcWqrVZWo$3'bjmHrf^dGFZprr`9#s8Mus"9/5rrV?Hm +r>kq9[oOIo*@MqOl1juIqu?Znq>^9G.3BWS*c(TOl2Ch`qu$Bkqu?Tn(]412s8N&so\o$"l0RTn +s8W&tq>U +^]4?4%fcD&rr2WcpUI#iUSPlqrr2rts8N#t!ri/snc&Rgqu.**qu$Khs8V`js8Vrps8Drsqu.6. +rV6Emr;Z?AS]1X'pAb0lrVc`pqYqQ5rr;rsrV$'es7ZEgqX;_3TVJm-bhVFQqu?Tnrr<#ts8W)t +qu?Wo!WE#fs8Muus8LsVs8E,urVc`pr>>J4oAeHD]:'frr`5tr;HKk +&c_h.rqa!!(*4>>)Kb30()\!'p](6ls8VKcrVuoss8Dcm!<)lr%fYe?WM,lK +SYNIdrV?Khrr3#us8Dr7s*t~> +^]+E3q#1*hrtY(deq@O/;,hqFa3DiPai26?bg"ASnAtXOqoB8i`Q$-=b0e#Ic-42MbJaD"s2b5_ +s3)2)c,.Q@`n&AIJuIPRb1"_`c-"&DrkTht`lQ6?^qn(.bf7E7`l#9sZ`]jE<_>_H6r[fCL6nj@ +]>VhjbQZ86b/hZDr5J]W"Nns5c-#n(q9&]X#0+a(`l5s:r5ScUr58QRs2GDeb08)Sc-OYYaN"1t +s2Gko^qI:\TSIVu:H^Tq1ce'8N2<\f`PomsaU-/1aN)9;`5]m?qT'r%a2uWUPrf6a%2K7Ub1+VV +aj%lF`QcN9*ZZ%-"%UY]b/hTBmDoIRbf\)LaSj-YaT'E_bl>iu`l#d3WJFeV91DfgIamZPqY0me +"9&,qrP\_d~> +_Z'rAr;-?jr;Z]prr)j0mcNWok4nuVrqucqs8;ckr;Zferr2rqrr;p(s8)cqq#C?is8Mops7uX( +q>^?lqZ$8Q,qUPmEn9s`quH`prt58.qYBm]qu$6_rV6EmrVlirqu6s$gsja(h;IYqr;Qltqu-Hh +rqcZmrqHUj%r;6C6frsJ]$n*0$$kM+_Jn,!%pqYUMK7s8'ef*[MjK+LCbrr*#sr;?Bes8W) +^&J03rqcWo%0$2!]V:pgWQk,9r;?Hl!ri/snc𝔒Q`p$NL#'s7u]oqZ$Qlr;ZZn&GZ;'s82ig +O!#eq+`>d#qYg?kq#;E7s8Vurs7lNlrVc`nq"O('Z)Xb-]"ueRo(`._rr)irq#:3iqYp6h"TJH# +s8MZjq#9sd,6%K;rVQ?Zl/^.+]",)@TrPrhp@\1Yrr2ios8W&ts8;cmqu?Kk,Q7Z<`@)QE()e3u +o_\Iarr;lqs7cQhVA^Ep'e-Nas8N#ts6KXas8MuurVlWm&H;_0r:o[&TqS$PUn+piq>:*i!W)ip +rke\c~> +^&J03s82fq(&7.HMd]CS=GXH0ai;<;_oBjCc-4?#aSs#>J4MLP3^cYLHBY#+ +\\5bpa7m[Rb5'9Zb596^`lQ0@qo/ZU`r$HO.jRW6K!ri6"rVaY6J,~> +^]+E6qtp^9j +r;Zfrrqucm!W;rmrs8JZcHO_iio]n#rrrE%rVZQlr;Qcro`+sjqu6cts8W)prs8W&qu$BjrVlfl +rsASnnF>Yqccb)8o`+sjrqmi;o`+g_qZ$?jrVHEkrqcZnr;ZFo,oS*R+)CKGrVlfn%K6=uqi)gD ++sA:bq>^EXrr;lrrr)j(rqZHfp\=[cq"t$hrs.66n*K<#hV\;3rVQZpqYpZsr;?Q7s*t~> +_Z0Z8p\tp)qsVLnS"u^iqY^ +_>jQ6rVufo(%geccsW'aSj-YaSa3MaSj7+aiD?;bK$4e'G2)i%L>(Z +cH"5Ra2c0<_T9^7ahu3=b08#NrlG&\3QB%S^UgkSQ#T_i5!DA-?[J*5XL5[S`5p'Bbg4Y_c-",H +`5U$Mcd'e^cd'_Va2Q$;`lH0Ab/ha)aT'9jaMu?Ba3)WObfRoE`lH*u`F$[-bfIT5]t:k`YGRnL +G@XW=4$G\a1d,&qTsM8C_8XF6aN2NFaMu6=`l?6=b/V96`kBL5`5U*G`6$'Be'C8m&0);h;Ua1A +b/D08ajS>`D@@$f$OL.bc,[oFaN4>&n]:aSs2tA_s2Gnm`Q,s +^]+H9qt^-grr3#trr2p$qrm2)i7e21#lal#rVuohrVY+Cs8N5srUg+=rZN@%-QFNY,UK/NqZ$Tp +s8W)us7uWnrVuosqt^$^q>C6krrW2trqcX%mG$(4ce.7?kP>#Hrr`9!s8Drp!;cKjqYpHn"9/8r +qX4@nr;6NhlfR3ci8NPIf&HK$rrE&orso&,qtg0dqtg3iqZ$NopAY(9rql`js8VcldO61\*[)]W +r:g$cqZ$K`SfT*s+sJD)s7uWWrW2rrr;R$$r;$'\qtKm`rs-sAoCDG:hr4P6"TJ>srr2`n"9/5r +rl>%h~> +_>jQ6q#CBm'Dpq7U77pY[.s@nrVu`ms8Vins7?6fs5j7[ruCn7o_p*S(`3l.(*X\@,bsc&s8Dlq +s8N&uq>L?mr;ciprrE&sr?)%=s82Q_pA".0^ojQ.VR""Ybi@jQp%nR`r;$3drVlfr#QFc'rVZTj +q>UBmrrN)rq>U?ms8E&sqYpEpr;HTnquQ]mrVuos&c_XlkN_$SaMPQrY,7n`Z-DV6q$6fsrVlcp +rVc]o!WW/prrE#srrDfms!%I>s7u]po_m&Z(`=23+4U8br;ZcrrpcO!+;l.>,FSQ+rVli_rW3&u +rVZ`qqYq0)s8Vuoj.**AS"QOQ\GZ'trql`qrW)orrke\c~> +_Z']9qu-Kn)ZBO0qu6KdmE_D085rr5[^sApb.b^7bJhfMo>psO"NAC#aN45#!Q`FUaSs=&aN;T: +_SFb5&/c>r(^_#j&-dO$c-aeXb0.lJbJX>"r5pA1c-=S\c-=DP`l?-@^q@1SK4@J72*=T?D0:l( +TW,6*]=tql^;'Z_&]Dc8bK7iH`P]d+WW!UD!Uons7lWWrqud2r:p-^pAP!gs8N&uj7)[8r:AS*s8Muss8Mcm"9/5rrl>%h~> +_Z']:rVcNk&H2V,qsq\$S"ZgipAOshrr;us!<;Whr;YmX*rkm3s8&H7'c.]1()n,0*?Q=OL?&%( +qY^0hqYC0dq>VT:rVcZmrVliqqtp^Bms8Dorrp"8A(`4,5:%eJb%J@,",9.OG*GOd4q>^9is60FarVZWnqYq3+rqZKEWhc)LR\#k` +rV- +_>aN6r;RZ5qtg3hrVcWeh3;>E:fqD:_8jX;b/M??bOWaS`WjE%`lQ6Db5BHbb08#uaC!0&^WaiO +'b_Q+*XiPg'GM)c&Sf86e'H7ZdDs8Qa3VcE`lQBKcdC4hccsVP_o'U@bJhE:_nNUfW/"kU7n,j3 +85ro/E-RY:TVnou[^`fY^qde&_ns=2a2Z*=aijM$s2QY0`5T^9aMl3@aiqoI^V@1XU7%-tH"U>W +7mo^05X%P+<+giPXKoOS_Ss[sbQH#/`l@trs2I%>a2cccF5L_84CCa&3N_ +*#fVLc-FGOccMJ-(D[`,#>2nZajIoF`l?*BrlkDblc9g\`Q?'9aiD37_82=@<^o5>9i[_Gn+m"] +rP8G`~> +])Vd/"9&/rrr2p-oASrqhrXV4qY^Bhrr;lCrZM%:s7H(^+t+WV+bV3e,T@aH)]U;>bP_;Uolk1J)-db!72i8j4qqUb`Xq=a@Glf[ +_>jQ6s8Mlprr!H.r:\1,Tq.d`g].-Js7cHkr:0dbs5s;'s8)cqo_(os*uPe3INfdQ'Gqi.)CB%9 +p\=[^q>^6dqYU +]`8$1s8)iqrr)j.p=YgEF+s%L2D@['f% +MBHb$PQ:8f@&*kb1+MTa2Z*2>-cHF>Qbfe)Mb5TK^`r +_Z0W7qu?]ns82rtrVlfr"oI9@kMt:qrrE#qs3goHr>kq:Zn!+:+r:U'prr;uqq>:3lqu6Wn!rMurqu7-'p]($\o_8(;W10=.r;Q^:rVu`hK.%`\,TfPQ +icHIe-QsU*\G?1#p](!fs7Y^V!W;rqrs&K"rq-'erVca$gZ.Dcp[[A:rr3'!s8KG+J,~> +^]4?5!WE#orso&.s8DW7URe$RX7#ueqYgHErr)lsr?)(;YpL/!(EA+tnWtOJ*?H.=*Z6A#[dj.f +qY1$_rr2lp!<2lq#6+T"s8W&qrVnSNs8W#nrVQBhqtKU>c+'mKTqeNdY-G:@]Yhk:e_8m=l1=fN +rr)ckqYC!`qYC!^rqRQ*m,QsSe'H7Z_nEUk[BHR)T:MOG[E7(apA4OXrr3,ur;Q]oq>^?kqu79- +rVlcmq#CBd2V59a^RL#:uq=t!irqZNlrW)utrk8>^~> +^Ae?5qY9sdqYq0+o\H6R:eOo#YJnW&bKIoH`p(SBao]f0b4`pT`rF(^bKPtZ&K)K&E7*$W&K)B' +(DRYn(H@o3eBlI\e&'2Ib08,Tbf[rC_84%,`P][3_o'UAccsSQ`P0!u]!AQ>VP'*#B3S#!7R]^4 +6UF79RFTa,`QZ0?c+_9Ab08#Nn&YOKs2b2^(s't=`lG[&^9N]&>Z"*e;I5jOnGWCerVlfrr;FA0 +J,~> +_Z']:qu$?k!WN#orW)iprrqEMi9'"jrr;uod/G'ar;::P,:"H`cM@M>oX%4;+X8!L*>fkJS,3-a +p\Fghrr)`prr2p+p\Omgqt^*crVlcmr:9jkpZ^VjcdVjD"P;2Vg=lP\!o2ViqVqJ3!oD_drn.Y1 +eCE$qd*p\0nc&:_s7ufoqYU3j#6"Gss8D`aqu6ctr;HWis!@43o\,3q-mp>h+1h7Go`+ses6:aQ ++!MOI<@T4?+!)Ssp\+X_rr3)tr;6NYrr;oprri?!qtL$ers7rKl/h9slK@g +^]4<4!WE#mrs\i(p')BTk;)AX;=QM1.Q +p&G!irVZ`qqu6ctrVl]mrs/Q%rr<#tr;HWps8b;$VO=!JTqeH_WNED3\@K5a +pq?X<+1\ni['?g7W268^SY2jd^s1WtnFQ>Fp\XshqYgEnrr2p"rr;rrrr3N.p\t0jr;HTns8N&t +s8N#srVumDoDSUGP"?lB+X.l7p\jdbs7u]X*?Z7@'Hp@$+!DO@*h +^Ae<4q"jsart#,&hPXpk9j)1l\\uP+_85Z_rlXuVrlXoTrPg#'bKA.%$6(0-)6D'-dF+F>'G)#n +'bLc\&UDjQd)=>SaSj6_aMu-s_)9-L`l>a0aN;TGaN;QD`59F4aN)05_8O4)\[8`AQ%3RJ7Q`=[ +2E*TX5X7h3:Jt1n='/I$qc3Yp*`5a/8k;953AW67,9IpN0LT)aLRP0J\@]MqbKIoHrl5Mjb0%fI +a2Q'=_n`k&`le8()Tp%<`lH3CaNDZLcGIlO[:q3'%h9*OQHRC^rl"rk`!$&U*!m6F)AF,)&J'NC +bKn5VbfmuC_p$9Gbfn5N`l@Sj"NJF"`lS)#&]DZ)^Vd=( +_Z']:r;?($Xrs\\ll/pjYf[A'jcd'T-`XL&5b0/)Yf@f'A +lLOrIrrrB!s8W)rrr3H)r;?Hdq#16kr;6Els7lTls"= +^&S-3!<)orqYq!&r:Ja3VOEpRg@kFBrr;?aqZ$3eqYpm!r5g3)&0;fsqYpL,qt^!2B-89<(Ej_? ++<6./s7uQls8E0"rr2lqr;SDJr;Q`pqu$Bks8W)rr;QZns8Muqrqu]kqY9m\p\O[UkMFb5a1]'k +XJr(nVP4!`rLk:1VP^8kYI(dS`6?immIg;Jq"agaqYU3jqtL-jr;6Kns8N&u$3'l$rr)clr;Zfp +rr>P6rVulnr:A+&UR@aBUSd8JrVHNls8W&gs8W) +^]4<3qZ$Tl')28&s7YBY?s?)g>*QM9bLFh[o>gpSrlbGdaN44toZ7!N,/qRIbKcgi&JHB,8^>At +`m<,]W_*X3(D@i*(D$UZbgOPQrl+oTs2>&ZaN4;%&'`58`Q63C`lH-Cc-4>Na8O4!ai208^V7@k +[^s>k9`@fc:bPaF:esth>$Y`TFF]C@V6-i'[(!TW]tVM%]u8"0_T0sE +air#SbK7lIaiMNDc-cI1s2I@Gbg"AQ`PTI/c-4J0C(M$l)%@N"'GM)"dE'JJbKnXd$5X9p',)K"% +M*@(d)aPQbJ1m7`l?*Ab08)PaMu3<`q.7q`l5p:aNDZLb/MH=]Y:b4=&;+N7U0iKi:QjFrVcWeq +YgHmrVHNrrVQKja8^Y~> +_Z'`;r;?QlrrE&qrr`9#qYU9l"m+n8j50#!rrW/sr;?Qor;Za!s8N#qr8IVsqu6DE.2jWT-aisO +rqufqs82]TJL;Q^)^-:K,Uh=Crqccprr2fp#6+Ptq>1!dqYpd!s8W)ss8W)t"o\Ats8N#prrE#M +rtYJ/qu$?fpA=mgrVlisrqucpr:g!aqu6`rqYpEjs8FtUrr;urr;-6)L+"Dk,9%gL+sA-M,pSHL +qY^^Bgrr2ifrr;onrqud$mFUXmmd')err<#t#5nN# +p\aaar;Zfrrqlcq_Z,,~> +^&S'1"T/2ts8Muss7m3(pu/3DTpqmon,!"]s8W)trri?$s8N&trW)uprqcZerql^;qu6>@,8215 ++L1nEs8N&rs7uKPJL)?W(`XA6+!fM;qu6m"rVcZlrVlcq!<2ut&cVb-rqlTks8N&urr)fprVZZo +s8N!#rVlfor;QWqr;-Eds7QEcrq-!Y!;?Bc!quB`r;$Nns8W)rrrE&qrs&K$r;Q`rrr2rtr<<3# +rVQHjqYrMPrVc`or;-3&JgM]]*Yob5)Aj>**Z^.9qYgEnqY^"5*>KJ2*>B]=k5>)Us8N&ss8N#t +q>^Bjrql`prr)lorr2rqrr<#uq?lZ+Tr=WQT;\^NrVlg#rVuoos82Efs8L% +_>jN5q>V90qYL!_s8Mrkk.5D-9L`-2[`6J5b0%g)`r4!Y`rF*[b5KNeb/hZD`r +_>jQ7o)A^hr;QrTio]I_g&:pVrr;rsrqQHhr;Q]ur;$0dq#CUEorrrr2fpqm.[i,pXWY +ZMaY#p](6ls76-hr;QTmr;6KkrrrDur;ZZhr;R#[j72L&j4<)ds8Mlqrr2rtrqZTnrk\Vb~> +Z2Y@6qZ$Qmr8FTKT:hjShY6sIrr`6"s82fqrr2os!ri/sqYgKprr2rtq>LBor;Q]q+T1FJ)\s5. +9(W#]rr2rss8W#os7)C'*Zc=>)Ajjcr;HWp"oA8urqu]irrE&srrW2urr2ourr)`nqZ$Ek!WE#r +rU0UYrVZZfrsA]&rqufrr;Z`qrr"/@rr<#ps82ijr;Z`ns8W)f_gWdM+WD.8*#Tb:,Tn-J(*[nu +r;Q^/r;ZVt,8;+:)',mVr;ZNkrr2rhrr2p!rr;uorrE&srVultrVlg/qu6WopuSEFV4=3OS[.5P +rr0)#J,~> +[K$7)s8+&:s8VfV]5_%P<*!P%\\cV;a2,^3_T9g<`Poj>c-OVVb/h[$`W4*Yb5'*Yao'4Hai;07 +_@@A#)Ab^]2.M_ZRlt`lS)#rQ#Jf_8!^t^r"+6b0S>TdEDO8!6tMc36&eW +`Qc6:bJVEHdF?=7B+Yaq(D.Aq%1W[V'G_Gm%T^oq`PKO2bKc4]%NQZ.&!PJbdD=)MaN=1t!6Y;^ +#KFp2ccs\V`r3jr`lQ:7;+F;W>(OZ]rVccqrUKpfrPnkf~> +YQ"\'r:U'h&aSKAki:Ums8W)rs82]nq#(-jrr`8tqu6BjrVlipo)9d3rVhZo,9J9\W;lhrs8Mrr +rr<#qq>^Kd\l'Uo+sS;'rr3E'rVuorrr2rtr;6HmXT&.t7K!2aqu?Tfs8;]ms7ZHls8Dfos8Dij +ql4Zc,U4NU*Zl[R)_+I>hRb*_B[unXqu?Tnrr;or9ICf"+!kUms763iq>^Kcrr;ltqu$Bjrr2fq +rr)j#rVZWko_JIc#l +Z2ak$s8Ni0s8;/kUS4?SW5eQ)rVQTls8W&ts8DrsrrN,to_njjo)AXg%#GfA(`jirs8Dutrqu`p +&Gu8&s7*Qm(`F82*1?d*s8;lr#Q4Q!s8W&qrVlirrr2rsrr2rnrr)j!rr;utc2RP=%/g/(qu?Tf +s8;]ms7cNm+oV9;s8Dilr2O`a+WhaC(`=G=(aqq4gUA=NA'suLqu?Wort,,,80JZ\(E[2Zs763i +q>^KcrW<-!s8Drpru:t9rVZWjqu?]ns8W)mo[B_6UmIaIVonW.q#.>pJ,~> +ZMu!Eq"Xmhq"EjhKk"7b='gKe^;n.1^r4L8`l?!:`le;)!m8U*qo8ZW"Nns4b0'=s6-$pfCCMF/ +*u0R^aiME9^;.M#b/qfUeB4.=&/Z<&&;/TZ`kB1-cd'qabJqB8aihN:b07rG_o9dBb/V98b08$+ +aSj*\aMu<@rlG)]q8iNWiQ+g;aMl!1^r47L +YQ"_&q=X^crs&8Tkh>7^k5G;_q#'pbrqZTnrr<#]r[Im80-MGX+sZ[Yqu?Zqr;Zfrrr;uor;ZK[ ++s.sL-(k,8rVu`oq>UEnrVlosql0Q"r;6?js8N&rrr5+[rVlisp](9kqu?EhRj':),8q[C*ZuIO +<0bu2s8W(3eG/tAs7lTks8W)sq9C96+=&0VbQ%V +Z2Xh'rr3]3s8DihaJ>c%Vl-p"o`+sgs8Dutr;H]pqYpHnli%7kmOT=q()n=`s8;lr!;lcq(]OI6 +qY^BblPgWX(E].Crr2rqs7uWmrVc`uqt^6jrr!-%s8Muqrr2rt^]+62s8E3%rr2imqYgEorVc`r +rr2pBq#CBlqZ$9dRNNpu+;Pq4)&sM?;O#Z.rVcY+de8aT);9s8Dcn +nc&Rgs8N#rr;QlurVZTjrt55-fZ9ndS>)CEU=K&7qu-NkY5a"~> +XoB%2qXj")RUol!='BIS]?JL=bJjJ#s2b5[s2>#[b5KE[`rF-[ao]f0b4E\>b1+QQ(a'_3$d?u8 +`l6!;aN;HEcHOAPe'QVW'c.GoK%/)baM#^7c-Xe]c-Xkgd`fq_dEThRaN`&TaMYp:b4is[b/h`H +b0e:,bgFh_b/hTDb0%iIbK7cG_oKa9`Pfa7`k9=8aNDiMe:%c=%1rs]%20Bb'Kp]Jf[n^)5HN5\ +e^DR^`lZ9@aj-ss(`45,$`DS*`QuK?ammIP`rF-YaoKZab5THrcca>H`l,d1Q$cJ+8l8&YC9-%O +p&+gnqY9j^qu*u'J,~> +Z2Y%-qtKm]qu-Kn$eXLuk2c(4s8N&nqtL-irr<#]rY"k&+=.pK-D1>9rVcZos8MurrVlg+rr;qh ++s\?X,0'Z[s7QBk#lO`$qYU6js8T5#rVZftrr2lr5Q(QVs8Vrms7lWkrr2rppg,3`+!_XJ,:=[B +Y2oESqZ$3fpAb!hrVH9gs8;fkqu?ToAge`E*@G4fqYpBks7$$arrrE"qtp +Z2Xn(r;HWp'E.e)p"-%:StDjqo_%tYrr<#trVc`qrVQTms6T^urUd!-+;>_:S,`Ecrqufqr;HWn +rr<#t$i6>3*#]h<_Z'E3pAY'pr;QZkqtp?lquH]prr2itrr<#trr<#:rW2uqqu$Trrr2lr4oYKX +s8Vuos7u]mrr2rnp0&US)^#e:+=&%7XQ0-Pqu?6ep&FmgqtTjas8;flqu?Ql@NlU*'d-]Oq>U9j +s760hs8W)rr;uuus8N#tr>55,rVlisp@5PdTUqsNXeOMBqtU-hqu3buJ,~> +YQ"V$rr3r7oBb,-AmA,-Sb/_N@^VRP$bJM0<^s0d +Z2ah$rqQQkrVm<&f&lD\khc@>qYU0cq#BFSs8=AB<$N2&-m@jPs8W)urVucns8Mllr;?N`-RBiZ +,Tlp\rVZ]qq>($grr3*"r;?Mmr^["fs8Mops8W)urVZ]prr;orq>^6fOWPPi+<)dT +Z2Xq)s8W)srt"o&o[BP4V449Xhu*3IrqlZmrr)orqu6QomJ[Ikr;';5&f`/;kPP8Y*<,j8q>C9l +qY^ +WrE\0o'P&-Bjt+5;d5j4`mN2Vb5TE\`<*lnaSs?^aSj-Ga?dkjfK0cW)&WRma2c0=cI1Cod*KqS +_Sa=9b7#'t((pc%cHF5Oa2,a=ai_cG`Q$*Hcd1(fbfIfFcdC"[`l6$?rl+lVs2b5_s31Je!mJa* +ouR0QilD,BrlkGerlPYjbfIoG_o9R3_oKg=rm*RRb0.cQbLCG7%iuGo((*'Qa2cBMccaGPfZDL^ +a2u`CEja<@aN)?GbL4!0()eJ:':@G"_SsU8o#M9[_8!n,aND`Occs_Yb5TC%dDs;I]!7B"9MIuD +8mcPXmIL&Ls8Durqtg*_qYU6!s*t~> +Z2Y+/r;QWkqtg6irVm2[f^.qfh>I^$br;Zfrqu6fuqtTsZrVm0& +jQlC$kjdcar;Qosq>C3fr;QluqtpB%s*t~> +Y5\M#r;R9'r;,l\RAZXMVq1hCr;QZnp\t-knGX7(rr2lo^De76(`di#rr;rss82clqYL3irtYCB +*>KG,)iF=cs7ZBcqu?QlrVZWnrr3-#rVc`prr36&rVc`ps8W)ts7?3is7QBks5EqWrVlfps"aTM +s8W)urVHQiqY^$Wo^htQs8)Zg@j2m.,>k&sr;Z`qs8Mfms8V]grV#--80T::p\t1)rV6?ipk0_# +'Ghrbo)&IdqZ$*b!<)or"98B!rVcWorr2p:qZ$HeqU;:5U6hCMUnR;Tq#: +WW*\2o^Ch]E`Q=::LBI-air&Sb/hTBrl"lYrlY8^rPn?J(!4P1CO1_8ajJf$r"E +b5]O3a3'hW%2KR`luZLbf%HEajJYgdF6Ij`QcKNeOU;g'-0Dpb1+h^aihZ>a3`A]f$VRFAh=Du +Cs1/Ebg"5NbL(#;%fRFP2UBRh_SF4_a9T`&_8F73aNFG)!6Y;^*5p1A]t05)91qQA>[VBtmIpS[ +r;?Nip%nO^qu*l$J,~> +Z2Xn&qYU6k"9&/qrVca"eE?2[h=LUF!;uiq!rVrmp\b!hs8Vims8W,u%fQU +Z2Y"+r;6Hlrr2p.rqZKgq:_F4T:ht#oDALbrVl!ks8EW/qjS]F*Z6KA#+rV:8 +*h<90r;Zferr;rprrrB!qYL*grVmi2s7*RaS"6%?URT@#o_eObs8N&uqu?ZprVc`%s*t~> +Z2Xn'qu-No(&e(1rV6$KfWAO[9i>,H[DgA7c-4?0aSs?ZaSEjMaT'B^a>(`YHjgE!&K&1.aMPX0 +a3;cPda#qM[_0]-/I3F7&dlCubIYg=\P\I+d*KtV_SO.-rl,,\_8!b$`r=*baMu6@bg"E3bR2M6 +`l5j5_8!b$`W!mQaSj-rldmZb1"hedDWekH=]Sf&Jkgug<\*mcsHqh&I_n"d`0,E +^;8%%4>gl1k2Vs8)Who(`+\YlB4~> +Yl=_&rVm'#s7uTlrr2p&pYFonkM>(nrr30$r;?Heq"O^brr<#orW2usrr3]3rVU@S,p"-Rc1h25 +s8Vinrql]mrVmc/dO6=Z+X1Y#qtg?jp7rrW/6n7]p&G!hs0)DrrVZQhq>'R2\W^u41*@ti,UOZU ++se_(r;HThX"#86+sZg\r;ZWlrqQ<^`.00]-lsBT*[DpUlMU\[rVlins8/-*+ +YlF\$(An(1rVuoor;$*8U7S!OTWd#Bqu-Nnn,N.]!WE#srtPJ2qdplj(De+5o_eRcs7lWor;QWm +ru1RV,9.=6*+f*Gr;Z]fTcb=&=H(Jrrr)fqrqQNirW)oorq6/)]Kp`r;Z`nrVucpq3)^7)^->M +p](9lrUKjfrVl]o"TA8qr;HWp&,Q%LVjX-IR@BkE_ts3*rVHNurr;urr;HW%s*t~> +Yl>O=s8Diiq>L +`PTd=`koR7`N0Ud',M-NeA],B`lW`9#STF>VT@'^b6c80`l#X-^VRn/qoAf^c2PreapH//`P]O, +^:qA$rl4cTrPmj<:rU2ca3E&\dF$(>Qul7f'a5*W&f2;t(DIBuc-+>WM@pU:&e*@AbfRW:a2ZEN +V.U>P%g<7T(`jD#^W+C6cHjYMda3.E',;5h;o?\sa3!kmrlY8^s2P&W'ZS5:b08#D\X$r0<),qV +=`'hspA+[e"oJ,jq"O`js*t~> +WW*)"r;Q]nrrpj0jlkshrr<#q%K-&$q>L?nrqlTjrVZTlrr<#gr\^f)+Bgdo`"mes8)Zn +qu$Elr+RAu+W;f[q>^Koq=e2@)^HUT+t.ZEl1b/Rr;Z]nrr4nTs8W&os8Drss7lWkqu?]nrr;ro +rqlTmrqu]or;Q`or;Q`qrr;los8Mrqs8N&ns82irr;Z)Ed,#ZP^qY^?mrUKjn +s8Mlgq>:'crr2lr%/U##qq^H+p[7Y4jR`BN"oSE#rr2ipqZ?]prilEQ~> +Z2ah&rVuosrVuos%/fajUn+$MW7U\>p\k'j!r`0!rr2`nnGXO.WZ`5r*>ou1Yl=7ns8;oqrVuiq +s8Mk+*#BG),LZAdrtG5#=WS7t)^-4HF0k!#qtg6jrVlfr#6+Z&rVHQmrr3i2s8;fps8;osrqufo +r;Zfqr;Z]os8;forVmE*rVulprr;utq#C6ks8;ofr[7aBs5V8$Me=4T+WVUG*$#e3)AsM7(D[l+ +-i!`ApZ`k<)]L*-q>^El,l``O*?Q18+!275(`4&4Vu-Jns8MoqrV6.3,90FtrVQQns8DKes8N-! +rVZ[3rVlisr;$XO$V;rVHQnq#:?nrr)lr[f:j~> +YQ"b&q"X^ar;Rf7q"F(%DduX68m%+_a2cELbg4ANaiVWEa2lBDaSX!JaDfI`%M9Eu(DKW*e]#\T +]u%@uahGI(aO*Zq(`Nu!Zc^;'_8jql&e5]i'+#+OR*=+/b/h`D`6$$=`Q69D`m2WEaiD3>_o'R: +_o9[7^r*t)bf7T@bfIrMaMuGZ,"V%1a-h()75d% +M9Hs)As:s#g1?8bK1/*(_ROIe()OX`llnJ'bM2o$kEjW&g/>7'7n3Ba3W,OaN)BM#7q;&VpE5t` +QlZLo>i#rcd'_SaN;NBa2Gs +WW2horr32nh=9gljl?@?%JTerqYpHlrr;onq>C0hrr<#gr^-9`-la?O+!W:%ZM3thrr<#qs8Vol +n/ro%+Y6:Xs8)Qks7%Q],on*S+=/6T- +Z2ak&r;Zcqr;RW5q>/+)W1TWY[.""op&G$irVZTms8W)rqu?0b%fPs$+qth/(`aqdYP%Jbrr4_N +s8Vrlm2?l`(aVZAs82]ns6V'L*>KG6)BTt<+BFKEp&+jbs8Dlqqtp0gr;Z]orr2ourr;us!WW/t +rs8N#s8Vuprr;uss8W)us8W#srsJc'r;6ElrVZ]mr:0b6s/.@n*Z#V5*?Z7<()@o1+r_gL&fDi1 +*@'D6rr;do*ZYt4)-rr48:PTUha*$-";+3YqY^$^s8W)tq>^Ko!<)lq\,Us~> +YQ"_%q"Xa]ru1e"gm;JQ8OZTpYK"f'b/VHA`5]m?aN)9@aSX!Ja?\"r5SaqK(D7;m,*:-ha2,X7 +_oTd3b0120+r_UZbL=5;b14iS',qPr'+>B^$ku0MUs.3:b0eDPa25g6e'-&8_CWaIaiMTFb0%oM +dE0MO_SjL;`Q?9Gb0.rL`Pom>`l?-DaMu6=`m2WEai29ia9p(8%M]`k%1`mZ&H3>0(D7Di(D75s +,Sq$uYJA6*dm"Zi&JS9uf?2@`dEQhD&/Q#l"pY\U-Pm[62:':]aNDcCaj/.o#a$R'-oDSF]rVllrrqZZmrVHNorO2`V~> +Y5\_*r;6Bhrr2lr&,uV,s8V`Bh^]s` +(]4.1s8DfhqXaF\rr<#kj6c:'jR)6en,<7grVl`krVlotr;HKnr;O#%J,~> +Z2Xk'rql]rrr;usrr3H)rq=a6SYMmSVTJ<,q>L,`h9br;$Bj +qu?>:,Sq%-*jP\Arr;`i\/Z;.*M@9t,T.aI(`atMD6,Tdm=*ZlOI*?l\8KoaTe*#02- +,K'HZs7hQq(E=>Do^r1`r;$^E_rr +U&Pr'oA-BZ;c$%]=+d]t_TBj@`l5s<`Q64!aT'9NaDf;Ja_qe2)%mc$((1e#bfn>RcbmWGb[qh5 +)&L>AoP/N&eboh$CAcT +bg1;F(DmZ"fZqsi`Pg'5"VV=Q$9*" +YQ"h+r;$-`qu6Km!ri#prr32kiT9Cjj5Tk6#5nK#r;?QnrVuEes8PR\dsrbg+rMaO+W_kZchIJ1 +s8W$C*?ZUS./!W=s8W)kF<2_RFo(t-_hq23-R0cY.46L&q#0scqRaDjkkOoMpAb'js82]n$2OT" +q>^?lrVc`nrVuj#qZ$Tmr;Zcq#5\8qs8N&unbt`PqBR4-,oA$a8SO +Z2ak'q>^Hm)?0[9rVuorjhRRYSYE'kmJ-YXrVZWorr)iqs6K[as&S^:H6=+F&g&)5)BaOTo`+I] +rq[ZN(E4D>o)J^hs8DJ*(EOW:p\46oL.N:,*?,tC+X)[6r:g0d\[g/Mp\+LWs8)coq>^Hmrr;um +rr;foqu?Wnrqu^"rr;ips82`orr*,ur;?Tos8VWg.0'&`)&F5(+tQ/oNNgaqq"OL]p%3bS*>fog +q>U?mf,Z"N*$)Qfr;$?l')#-N=/b>2q>]llTpqXDR&Q^fhYdBMqY^?qrVZWnWrIS~> +TDo;mkeE!Y8OuT[Dlp_P_Zn&ua2Z'=bkoK]`pq,]b0A/;C(q3m$lg-&&J8]\dFZ:cdb,*C*$?15 +e'Z.Ub0e^F&Ju-cf[S6>CGcG2%1np@eHos*t~> +YQ"h+r;$0cqu6Ek$i9eps8UI-k3hV +,paZ\+IW3-s8Musq'md2,.77Hs82ion"t=R.j#rX*GbKNq=cZk+sn[1D1\CdcKkE)p%JC[rr2p4 +rr<#qq#CBnr;?Nns8Dlqrr)lsrV6Bl"TAAts7u3a6iZj/+=&$ZU\"^9fr;OD"o_&7Ys8Vq2+L3bqu6Kms7-'hr;HTo% +fZM.rr;rRjQuO/m-WEPr;Zfrs7u`oq>UHorqlcqZN#F~> +VuHktr;?QnrttY5p\sf\WLTZQUo`VMp%\C]rr;urr;Zf`r_NRhs8W&mhNfB2(`OG;(*+K2Y5\.c +Q6.+`*ZC=@rVuors7IW\'-4CRs8VurqWstu*@2RB(`$`Xs7u@B+WV^M7VI4=YKH&(p[e%Up\t0l +(&n75qtL-jrqu]ms8W&qs8Muss8D`lrri<#qu?K^rX/\t,T%I9-(j`&p\st-s8Duos8Vb^)AO,4 +FSc%9s8,>%,8_A=q"t*jrr3bKo(DVRpAb*cAf2O()f,'Fp&G'fs8Drs#l=Puq#(-gs8VZhs8W&s +'`@k+s7ZEIXeMAWR@:"FYPS;!r;QQm!ri/srr0&"J,~> +Z2Xh&qu?ZorVlopqu6U4o'b+MC/Ii&=(Ir4\@fVo`5]g;`lQ6Db3m>U`k'(5eCVN`%209i*[DL; +'317Pe'WRW)AjG(I+?K^`lZfc()mVgPK_4qajee]KO"p"%2BTd:sRG*`X`=^%Lt0jB7uSSb1b@i +bKn5QcH=ARc-FGOe'H7\aSs9ma2Z6Ba3)TG_TU'Ga3MZG_stfDc-$Y5+;l&ObKS8[d`p%YcGmuN +c@ZZA'+e$`a2H9U2[gN3#ZT6pb.bU+b)XQfb0n;ZeCIM.(_dK\c-^Knq=s^\rV6BsrVQHfq"jrus*t~> +Z2ah&rqufqp\t^HlrVlisnc&Rg;>g:js8)>b:`p\t+C9mrr)iq!W)fls8N#&s*t~> +VuQbp*<-!>[qY^<_s7Z<#9HtGt,4bd3oc5+r)]9h;*$H^O+Wj +X8`;!qYL0hs8VuprtkS"g:(j':KLS#Ejrj&_na42ai;ECb0'.nE5oi7`lQQVc,t`!&JH$%)ANl$ +09G#u&Jc&m%b]7Q`5TaDW=fdZ'u\P=`PBR<_Um>qTfa>4)%Od6aN">o(DRGr%1j0k%Lre"9i>ha +T?kQuahZ3HccXPYc-4>SaiDQBa32KAbKn)Iahl$8ccbk":W1IQ*ZQ"-Us[N4c-4;M_TBj:bgNX3 +)As2Ccd'GHd!-02%gilscGdl;]#)e3cHsn`bgFq%%Mfcg1"=Ll^<+7=ai2W@bJMBFc-FAHbONXs +`5fs?a2Z6A[(1ci;b9AK>@hK +ZMt%+r;?Qgs8W)mrs7iGhX9Uiir&fQs8W#qrr<#grZ(b6s8Dusp](0`\n3B7+WDIH-6F?W,T\*X +JGJp;s#Tk9,p"..qYU +X8i1u!r`,trVmT0qtg9krr)]\\YGXkT;&IJo_n^ar;Z`ps8DKe*;KR4qu?]ks8;Gu@jN-9(DRf0 +)&aV<(E+NGp\+Rc3qeRU()Kd?qZ$KnqZ$ToqZ$?.(_e&9p\k-K,8q+6+!;pP(`"23*u>k>,oA/o +s8Dutp@n@Up\Y!jrr*K-s82irq#(0brr2Wks8DimnbtiSnpD>i)]>1?rVZ]qrVufnrqcT\V&U +Y5\_'q"FLXqu-?js8EK+qX`dlMe?$n:f1Yp\,O5G`llL"b43Pk_TpBJcc3W=aN9oJ)%mMi()dts +$5+'c&eeX!`6QWI`5$4#',3[acH3uH`luoTbgY"=$k=!\bK%l8%hoa$'+G`f$4%:T'+56]'a5Dp +d`9SZ`5g$Aaii#Tb0%fGb/MNG_T'a5aN20CbK%TAn]4ML`+B]$*XdJ@ai_lQa2Pj:bf%WCN#<`Z +'be[/_o^H@*"NMd$_>tobe_-8`QlrR`Qc_m?lBXh&dphOb0RQ/`6H`Yc-+#?_8=+2ccF)qa:ub? +bJqH9]<7W2;cud`7Sd +ZMt%+r;?Qhrr`8ur;QWo!;ZQm#3"D(l/U:]p](9ms8;lrs8Mros8MrrDZ0M5r;HZnr;Zcfc@@/b +-6"-Q,U"9T,U;[Ss8Vlos7fhF+!jOVl1b5Tr;QTls7lM\-mBp=qYg?=,9n:DpW^sYRr;de,Ut;b +,q:&9rquckC+:T8n+Zk^rVc`qs82iqrVuN*S"-8>r;ZNknc&Re9UdIQ+t.R1s8Domq"sjZe>S48 ++^Emq#C6kqu?]prVlfr +pAY*i!<2lq$N9JOnaQ#:lf?mlr;Qiqrr2iq"oS/kqu$Hhs8W)*s*t~> +X8`;$rVc`mrtkY3rVc`ks8DiefW(@5St`12o`"^arVlcos8N&ur;QWos8MuqDu]_2s8Drsrr)ls +o?Db0+Wh=9)]]h7*$-0cr;Zfls8Vjs*u>qcU#u%Hq>1'erVuWgQQd@i<;HLjc5mo>?h2=\TU'>! +-6FN\(a(+Dq>L9kph2$#=RGh]s8Doqs8W#srr)lg^R:OblMU\Vs7$"^r0JTC)'X%qrquZgpA4OS +dAD\-)]BS0*$t@Zr;ZTg7i< +YQ"e)qY'XXqYgBhrtPJ4q>^9Ygp(s'<_ZJ#O0l^<_8aRlb4`pSb&>YLaj%c@_S*e/e^:R1#7MI` +'b_5n',D2daO&5S_T0U53Y<)G3/bc$eBlUhaiVoMdd*BbTb/q`BaiMTO_i\nXFLoQ:_9MMk4iPWo)AsM1Z"'e:/YMeh!b1@"T%oepAFmh!W2ins8Mu%s*t~> +Z2ak'pAb-krVm!!r;?Qmrs.iCj5oCcmIg>U"TJH"q"k!i#QFYsq=sd_r;6Njrr;ors$QS]j,k]; +,U"BU+!E%]rVQWls8Mu],TmsM,9J=)P.pG_rV6^HkrVZWnpAYR#r;?Qns8Duss8;cn +r;R)ii9'RuoCV8(n+-MVri,pJ~> +WW3"tqu?Zpr;RK1qYKWlT:hdHUpnYCqtp(KqYp?gs7B56(`J>1r;3B6)BWeir;6UEorVu]nrq'eG+=#;4s8Muer]gG^as@c=*E2[thT_:2A0E$;+WMI@ +(E489Fn>Y5q>^JM*$H"A,k_07q>0jGX\p2^)&aM1*Z?7DF8bn6s8;iqr;Q]orVZ]os8Mus"9/5u +rr2p2rr;`kqt\40TqIgCT:DOehu*HOrVl`p!<2forVl`p!<0D+J,~> +Y5\Y'qtp?jnGa:!mF.2/>#7jlD2Gd;`5g-HaiM`Ob/sP#s2b2^r65/_s2\rp`Pod8cHjVZZtTpa +',29"'G;$ce]c@[bf\2G%20Br((Cm3J?7GYd`BV]d5VgM$?"/1c%HK>*FRCBcd:.gcHOA>SUYQM +>0=k&aO*g!&e,&>c-+>Mbg"5FaMZuONe6qgHdN4eBHqY^Bnr;lfor;QcqW;hA~> +U&Y/lrr3*"r;?EirsSh[ipH$kiTgFBs8;lr"9/,iqu6Trr;6Hg!W2lns8)^Xs8;`nrVZ]ir2<76 +,9e6W,lI]BqYpElqm.Ih,oRpR*@)^S.W26KqY'(q,9SP_q#C,Y-QsS+qYpKlrVc`q-M[K8s8DNg +rVlfmBI"]F,U]/_p&G'cs7QElqu?M),U4Yar;Q]onbt6?rS8YD,9S=11*n@o,q'r[,p+3h"-d1J,~> +RK"3(rqH0^e#&k3S=cdti;!?Drr<#trr<#trVlZns8N&s!W;olrb)9/qZ$Nls7Z8a7NNNg)'C't +rr;ior;QW#*uc=;*[;RH)BpMEWTa-KiuB$V-d28apR!-C(nLI$rqlWkrr<#or;?Tmnc/Ugs7g%B +(E4G@RfE$[s7ZKes8VurpL,'^+OBrbrr)Bds8>(6*??%8+&+@2,9.^I)]'J1)BKb;*[)WXrVuZm +s7bUs)BB_9_"@QfV-G2`+WhR>)BBqC,&Yd'r;$Bhr;Z`p!WE#rrVl`pr;ciprtkY-s82`gs82cd +cFTgES=5n>StsHtr;N&_J,~> +T)Sljqu-Kn(&I=TWGPj67o!B#%LP6V^ZtT%33&QiDR~> +RK!u,glPXrr+Xe9W)^64E+=&@U[Jg($rr3#rr;-Ejr;HKnr;QWo#QFVlqZ$9hrr)j(mG7$mlLOQ6ip$B) +s*t~> +T`=rhs8N`2qu-KeqWkeoSYDaIW7189s82fq!WE&trq$0drabp+rVuoprr;cko>55f/n*Z6qYpNm +s8Vk6*$#u?LfYiH+<)%;+=B`i9-"ud..I90rH9+u+JAE$qXsmbqY:*gs8Vups8)]os7p.E()\)2 +)K,BDrVufqs7Z?dqILTa+3scPrr2p\n0Snd)\jP:)AX;4&fr23*ZZ1?,Y*jis8Vfms7lTa:Db#e +)K1O3*[MUI(EO;1)BL,?Z24@nrr<#rrVucop\t6lrqm$"rr2ros8Voort5/(k.[RcTUhdJT;&pV +q>C3jQiDR~> +TDo&kqY9pcrVmT,kM=+5@7sHm:0ij]^V7=sb/hNDrlbJeaMu3p_Z@`rqT*3db/_WI`Q6EIe'Opj% +M_CBbf7WC^rXdI<=KQP@qme$#8%RW&-i\*<^@H<$5Cb0^X#mf&f?VP_T9^Fbf[rJ_oKpAcd^1Z` +Q$(s)&X>,%h)XoaihlEa3)'3aOElq&e(tgn&Rc6a3VmA(EXn<'F4pL'GLZ['abK_$OR@pDV!Fe_ +T^0;bfVgP'b_*aFup'I$Ps0`#nIFS&6G1Pe^2[hbJV9aNDNIdDErCb +eCZ`G#h[?77KX=A<+dsmdg7As*t~> +R/[0aqu7B"gYh2_i90"ps8Vlgrr;upq>:*hqu6]rr;QQnr;-Btqu?Zks8W&orVoLgoCDkXs8Vop +s8)Ze.N]lYY5%bWYAZ!?*[`'O+WqaD.!5Ccs7U(L+!Mn&Ck8RidGY$!s8Dorp\Y!hrr;h.,9J'P ++sS70i;!^3kq>:0irrrE!rV?*arVm5sf@o`] +mI9<%hY$jI!<&5`J,~> +TE"rjs8Drr(B+74q#('aaJl#1W0jHbg@bLGrVuorrVufpq>^^KnqYgFhqsa+Pqu?]n +s8Vuop*Ue$*j#25mBCIX,TA$N(E+/+'I%MmrVuM"*#KV7883OMXj5f$p](3js7ZP[Drr;fnrs&(*)]BgDqX=DJs8;oo<#?Df*#'>8+W;IL,9g-1QF,*4pA+FZqYgHms8VbK +,oR^B)C$1H(E+22*[<.RZMXIopAb!hrVQQnrr)cop](9ls82orrr2otq>UC+r:JLaJ,~> +T`5&kqYp6iqu-O7rUK7:esC2[<)uCf@["%2]Y2;*`QZTNb/hTA_tq1M_oDPoAAug7_o^'A`5g!G +f%&$fd`]kY]ZA"*b1%+9'Fg8RaLm7*(DR8e#n7R^%h9@VeB?7_7Lg%E%P("QKoE(Be&TnYaj%iL +dE0JLa\*6])&F,-&idJGbfIf>aN2]M7h$"'ZI@rmUnl +,fn*I_o^3DCD7I"(_IPp)%7)f%h/sY +U&P,lqYpcurqlTms8Drs#N=Osn*'&rn,<7hqu$6ar;6KprqlZh!VuZirW3&trr51\rr;lqqY'je +s8DuopAapcs8:%g)'C:#s8)NhqX;Ul;C*n>,9eH^c2I\>pa742+=/'g]r;Q]prr`8qs8)]o +$L?j;jmD^/l/q@/rr3)sp\FfTs*t~> +T)SunrVcZmqu$I*q#(!L[A9:fS><+%iq`WSr;HX!rquZkr;QNlqYgNqrVlg\rVliqs82Nes8W&t +qtU3frr;l;,SV4CpAb-is826,P>WYR*??+B,0Kf_qXl$N*>on>*>f_:+f\9',_Q&,I7:Gs8DusqYXnE*$EQ(rr<#hrr +U&Y/k!;c]i!rMrorVmN-o(24iL1EnJ6r.-=U:nRa_T2Yus2Y8]`PqenrPeTRB?J9:bfRoHb.ba/ +_TUBSaiD0,^q7A'bH_uq',&sE_njIBc`VIG"r@j^$kNiJda-(a$kaHl(`4&%',2&f#cuFubL+VY +b08)La[QdR(`X52(CUb8eBZF]a1oF66jsV9YKb/.`q%2@`l,g>@Mg4.*uG7l9kAgVYgU\AcI(1` +f$2Lcd)a8F_Saa0%Ls*i(CqAl%2KTp@@b +UAk;oqtp6i#5e>qpAFpbrr38ss5E5.l/1FprVlrpqu6Km!W;cmp]C9gr;-Bus7cQnrquWhqu6UW +rr)fqrVHQns8MonNZBJk1-es7lHis8W$= +r;Z]op](9mnb?rJ+Wh[H,U4[k^ZYXos82irqZ$Hkrr_fhs8Drrq>UNqr;?Ki!;ucp!;c]p$0C70 +melP3lKRs?rs&Duq#C6^rg3Y8~> +T)Tc/rVZTls82irqZ$ThqtKj.bFbK#TUi(.o)&@ars&K#qtg3frV?KirWrPus8W)rqYU6jrrrE# +rVulprVnSLqhuC:*`D_1s8Duss8;fdm]1FP-7-+qrVu`g,o7I>)]ft@*$H:D,h;YhrV6-err!0& +q-tH`*1n/h)ZCfhL\^ZDrqHHeA/u[5cMRV@nc&Rg')hWl*#9D2?h3^Xr:]sdq>^6drr<#s"T8;t +s7lTn(A.E<+rhL;*$HFNKYHUKqZ$Hms8)cmrVlujrVuiqrW)onrqQNlr>5M.qYp&sWh,ZWTp;4E +\(UZUs8Vlos7_iZJ,~> +T`>&jrql]n(]474rVuons750CNDiPE;,_.l\%ol&`p1WXb/)3FaMu08^;\7:aN;KC_S*t*`Poa@ +FUfB)5Ldln]u\FOeC;OWN`uSl#aNiccd(/U&Jl,n(`=2*(CgfZZHgnA_SO=6cd0bW8.Z:GQPCJW +(D.$kf[%sd]>qrX%1s&Sc,doDo#O>@aMc>M'H%r/9\%J;gXFEp`Qc9>bK\MZbK.oI`kTX9`P'Ys +()@Sr&J>TcD5,,)b/q]Jd)O;KaihlL\\uV1bfn5PaN2C"`W*sXa99N#_oBdraqr7:_o&d&?qrs\ +9LMcbJ?nD0rVuTks8A8`J,~> +UAkAoq"O[br;R#uq>C6dr;Zcprs8DZj66($jQQI7rrE&sr:^*ms82chqYgFSrqlTmrr)lsrU^'h +rql\%-6Fh*r;ZfrrV-'drVuZlp&4a^p&Fmes48qE+=d*BeYT$ZA1f5'r;HTos8F)>rqU1Q+skP, +@3Qa5-7;sopAb*kq.h3!-.;_jrr)Bd&bl8(_CZoV+(kHKs8Dfnr;ZZnrrN&irr3#pqu6U!r;$Bm +nlYl=&kdhoo_JOer;Q`ls7QEis8;omrr3#orr2fqrq??orqH*_qYprtr9<;-oChA>m+pe%rr`,p +q>?KWJ,~> +Sc9W-rVc`qqYUUC;rqH0drVu]lo_eOZo`+abs3iG4)'nb,d@m4L@4W\tqu-Kn+9)69"Fp+!=PXoDedhph1Ze+3sc_s7-($s7lTn^F'p?).NI>s8;]lr;Z]orrN)jrr3#pqu6U7rVHQo +mo(AQ,"@$so(W.`r;Zfns7ZKks8Duorr3,srr;utrr2ios82rsr;QZp!<)op&cV[]d]fC6St2"< +Unt]kr;?Qms8Vusrg3Y8~> +RK"Z5q"Xmhr;Zfqs7Z$)UMbK.`A_o9gA +a2l<<\B)h6ajWKb$Q"0BccF#B_o9pCaM?'?bfS&LfZVRk\/Geg'\gF1KRli_"ougEb9G!KaNr5X +c:el_&$sq^&.K$c(-pX$e&T\T7h61BZd6_4`U_)>`kKF@Sei"S$UrOjf$MUa`lPs8aiVK8bfS)H +`Q?0A_nOIIcohsQ%kqLcbg+S^aMu?;aLoR2a2>s6aN_oDaND`Lb/h[%`WaH+b08#IrkfMkb08)N +^6XR`:J=MV4AK!nPM,R'r;6Kn!WN%`s*t~> +UAkDpq"OR\rr2p"rVuosrr3&ts8Dlq#MSA*m-WfalMLS]rr2fqrqcZkr[IgFs8Dutp](3lrr<#t +qu-Nos8;iqqu4Hs9.Jp!s8)]orr<#qs8Duorr*f8qYpKjdip(^@f-*$qY^9dpA=^ds82irqYpL5 +rqU1Q,::b6q0je?*?c[UdJNt?q.Lrr-.2YYrrUEkqu?WBdd6i0q>^Bhs8Drls82irrr2rsrqu]ns8Dlmr;?Qfs8Vfnr;-F"k2PdpqX3e +ScA`h!r`,sr;RW5qYpBjqu$0CW2#lOT:_dnn+lkXr;HWorr)cm!<)`m&,lM-rVuols8Duss8W)q +rVlisr=K#*rP4Oc-JnY"qYpNos8W#rs$$;Zs8N&orVcN;*$-)+q"jm`qtp-\qYL6lqu?]ns8N#r +pL50a+jBcUH6j[K)''pGq#C0aA/lO1c27M>s7-*gs"X8o+;l+<,l%?7qZ$?jr;ZfmrVuoqrVHQo +rr;ips8;`lqT[=Lp\agdr;6Nns7u]mqZ$ToqZ$Tprr)lprVHTnrr4#)OK +kPP5Trr2rtr;HQkrg3Y8~> +RK*6_!<)lr(&n75p>N;o>Y\$o=^?HX]"Z#"aNW!/d0.e?bf\*(aCiQ7bf@iJ_9'a>b0/&Pa2c3A +a2H*Eb0cYs.LX#Ad) +`l>p=f%I:6bLG(bdDj&D_Sa(0^rQGn#KOg'`5K[;ao0B^aSs3Za9B`1c-=JPrl+oY'ZRtsQ=j#h +6r-9M>?>RVkkb>Zrr)ir!r_uoQiDR~> +UAkDrq=sd_r;?Qis8W)rrs.fIk2Pdiipm$J"oeJrpA4aas5O"_rV?-[q>L +SH&Wgqu6Zqrr)fq)Z9C&e[;9MS>DdN_!Umor;Zfpqtg9fr:p3gnG`Cbrr2osrr2oq#l4,cq#(-i +rr2oq#6+Z&rr2rtrr*`4s7Wc@*["kbrqu`ps8Dors8)cos8Durr>PBC)&aaEr;ZDq,U!m@(*]RL +rUj\C(`pius6osdr!`EH)&XD=q#C?nqu?Kis7lTlrs/Q%rVZTlrVl?er;QTnrr*u=qtp?irVZQl +s8Mokq!R1TXJ)A^TU_IAa2@<@qtpBm"TJAurr/2_J,~> +U&P,kq>Vf?qYL*fs8W)sqtg*Yl.iCg>#%I^;-SLO\%fc%c-4M[eBQ:X`l@Sj!QrXbblZ,3aSs1Y +aND`Oc,mrCair&Sai_iPccsVO`Q6->`Q63Gc-ae%$P3b5cHs_WcHaVVcHF5La3)<=_o^*HdomCp +$DGVic'/bW',M,kMpg2-:)=NUZ-g\7`UV"[`lQBR$4..J!mJs2c,IoArl+oWprNER%EcrCe'l^h +b/V?8_8F1f`VmgJa +U&P8pr;QWnr;6Qoqu6ZqrqlcprVm/PeE$)dn_3'urVllsq#B@Q#64Z!qu6WqnGY?Bs.1M[,,,&6 +s8W&qrr)Wjs8;ois8Mrrrr;gn+sJDbqYpKmgES-l,:+UWr:aeJ*[f\@rr)?crr3E#:*;&/-d;Ac +q=t!idJit>"o.ros8;fjrrhrLjQc45klTl,eF`bBrqub^s*t~> +Sc8]hqZ$Tp!rW#qr;R<*q>9]^T;/0LSsHP'oDSUdrs&Juq>1-jr:0darVlfqs8;lrrri8srr<#t +rr2lorr2rsr\s]QRNNOiSc8Kcs8DlprV6?kr;ZKjs8Duss7f;-*$`u0rr)`H*[;UC*Zo@so3`LQ ++kHVkrVuEerr3>t8K&il,Kfc\q=ssho`"dgqu6KmnG`7`*rGp9s8;`mrqufop$gtVStD[RTV8*K +VPr\Nr;6Hlrr<#s!W;n\s*t~> +UAk;nqYU*g-2[Q9qu?]qrV6*]q>'XIcZ3/e:J"G[BSW_$]Z&%=c,n,MbK%Tma9]r5cd0k\bf\$+ +``1%%b08#I^r+.6cHF;Obg"JYbfIlJb/VE?aNDZHdX;N5$@paNbg"8Nb/qZHe&]\Fahts8b0A@! +'bC_L`6-?Q[gEog(_dBWg=f@?',A.,aN)_84%/kf=gbaN`)Sb0J8Na32K;X+!i45=In4;G_%Pa6**nrql]ks8)fpPQ-.~> +SGrfmr;6Bhr;QZp!WN&rrrW3"rr2p'e)KicmI0&kq>UBur;HQmqYg:3e +BIFlD,.@ILrrDc;rXA`%q>'g^q>'g\qu6NlrsJDThs^+)mc*9\f(/hBr;6J[s*t~> +PlLd_!WE#os8W$-qraTCSXZ7LTr-*&rqZHhrrN)unG`Idrr2Eeq#1Borr2rrrXAi!J0GgKOR`/F +q>L'es!RX=s8N&qs8N&trq7f\)^+bLs8VrjqGn[V()S2kej^Pp-KkC.rr;Nf$MjT!p1#6[(`]jY +rr3#ns7$$cs8W)rrrE&trUTs\ru:q:s82Zgp#a2hSYr0RT9uCLWoisPrqlTkrql]o!r`,tPlH7~> +UAkAnp@e@]qZ$TorVmrO`r3mS +`X9Z%`Q$$A`l6$?b5TTgb08,SaN"2"2pK?P&eG\+eBQ+SahuL`5T^:cPdN1%*H]8 +cH+)U3>3)8$4T&94<#%Qb0\8Ob43Ob`Q?TU:Djl_%[Y\md`]>Cb09h,!6kA`r5]Dg`P][6aN2B@ +`Poj>dF$5^V..UKO@YU8H(se>A/H(hXUC;s8W&lqYL*fP5g%~> +S,`Eas8N#ts8Mus"TJ>rrVlfr'&W!AiToa`hY[3Ms7uB_q>1!dir/rVrr2lpnGY?>BcS<@.8Xc) +T<6Z#p%/4ZpAb$cqZ$Nlrq7HZ+"$pcq"Oggq=oF`.3Tca*[2gO/GT5Err;Nfs8NQ'rgY2T+sNBJ +p](3ks3^iGrsJc'qt^$]pA4^`q>C7#mbQ^^m-+$#mH*$krr3,trr<#sqYpZsr;?P\s*t~> +U&P2nrVlNj!<2`m&cVJ!pYDO>Tp24ESZ1#or;Q]ms6]gZrrE&ts8Drrs7uWqs8N#ss#0O^(E"/9 +94rI>X4,kqnc/@UrqcHfs8MusoGS\d+o;66p](9hp3I]')]C%>)B'D=p&4pis7-*grt"l%QQ-bY +)j9jes8Dutrr)Eequ?Zps8N3#rVZTmrUp-ks8N#lruV%:s8D`R`2TB'Tph[@USas7nbrLcrr;up +r;QZorfR52~> +UAkDop@S.VrU^%0rVH#Ra`<)-_QQ*[s3_8jgGc-#Y!!6Y;b!m8U(p;R$Nrl##Y +`Q#p=rlY5a"3AL'aSs1;`CC.H(`55';0R\p[EQhIdEU+^`PTg:_oBk,((^u*d`02Ib/;J7%M]?V +((D'#(`:iKb/h`qa:ZeCa45eV)]TH;dE9eU`Q--Gr65)]r5]&]_nj1,`r='Z`WjT3e^`!jc2>fa +b5TK^`[\sI`lQ6Db/_ZH`4j(,^9sS\?;sm#85DWU@s"[(nbDqXs8W)ss8DilrK@22~> +RfE3_s82lrqu6Zlqu76#hWO7emH +U&Y,jo)J[fs8N?#rr)Zeq=Vo0U^WW_SYWI?jSo2Zqu?Q]rq-6jrr;uurqHHmrr2rtrr +UAkGrqY9p`qu,m](&d^efpu\[>YnEu6b05e_'-.i[eCMdYahc.!bl#W[a:QM8_nj1,aNVlNaMuBIe^`"Ebl#W]a +T'6]`Q#pra;r7;^q[XsYEsWC:fUFd8m#)$F/\!bq"jsdqtg3gr;QcqP5g%~> +PlCmcr;?QnqYpQpqYq&nhr*Y^m,Hg[r;?Tos8V'Wrr;uss8VWgs84aAH"/s82fnqtp'E9-YE++<_jQqY^BlrUKjts8DrodNfq[,ZOO:qu?Wndf04F +!;uin!;u`o$Lcm/jQ>UimHNQcrVm9(s8Dlns7lNlrql]nquH_\s*t~> +UAt2kr;Z]or;Z`p)?9^8qu-Kkp&4gZ^S[ZoSY2RM[GC!Dp&G'\rql`nrr2rtrW)utrq$0hr]'rV +r0DJ3,UsfX)&=>7(_n)--6"$Wch.8;rr;O:*#9dms7lWls8DlloA)nX(*aV4(a&MMs8DodrXJo+ +s81+a)&s_qpAOmgmJd(a"9/8trr2p!rr)fcrW<&trqcX0rVH'DbH[b7TV/$TTq%t0mJ6VSrr2os +"o%oos82fns8N"as*t~> +TDo)nqtg0drU'UurU][Db):1J>?+Qo:LK0Q]XlA3n&YOKrl"lWr5_dU`Q$3Kc-",KaiVQA`P][6 +`l?!8_oBjCb/V9;D-0FH(((op&/Gle#7q(Q"Tet,`6ZHBbK1P9%MRCAaNhZ?_8FF<]IXBq*>fe3 +$e38ef](uao]YT%qu$Hni;W`Smf#'?s8Mfjp@mFn +WL\'G.k2hk.4-&_hu3TSqu?=<+!E!,q>^ +U&P,lqYp]trVc`prr<#tr;RZ6rVHQms8Vlop\X0gV4sERT9u@Nbh_sfrUBgbs8N#srV6?ls7cNl +rrE&trAFTPs8McfoCgteV3u47-RKrY,U"!Lh#%-Nqu?74(E+Ikq#C3hs7cQmrr;V$+VYk4*?b+U +rr;Nf&,Q2's7ddLEgjcCq>^Kjs8N#ks8W'!rVlcqrquirr;ZZn"98B#rr2rnrZ2%X2=0!q#(0hq>^Elrr2urq>^HnQiDR~> +Sc8ljqY9parVZZqrV6Emq\Ar(rUp$OjN)[6-@9d_8XLAdEThLa3)TUd)r]#ML88o$k3OP&IedG]@>6Pa3MpE'G:o'aOA)Gbe_TCaO\g5 +&Iod"(CJO3a32Zra:H;0a3;[,:L&1/cbIKF_or&#p;RZbb0A/M_o9X?ccsVM^V7J%qoAlZaMu=# +`r4!Y`rF-[arAFA^q6nAH=9oR;bKbX92TVjb3&0fqtBRTpA+I_s8DupQN)I~> +UAk8nr;QQor;QNls8N#t%K?;&r;HZqqu?Kep\4[crsJGWkjA!9ki:Xorqucrrr)cprr)llrW)ln +rW)utqucotrr)co!;uTj"985ps8Musq^qaCq"a^Vm*W/0L1gP_rVufqq'-n--2@N=rr;lqqu$Ki +qYuO$+W_mTp\t0lnG`Fe%/9f"pA4^eq#CBgs8Dior;?Ejq>C9lq>^Emrqu]np\tWij5B%cm-X-( +k2u=6rs\i$rr2forqZQmr;HNlrfR52~> +RK!EhrVc`prr<#tr;Zfr"985trr)j2rr)TLa.f2pS=l:CX30>rr;HTnrr)lqrr;rrrr2rrrr2lq +rql]rrr)co!rr9!rr2utrr!-%s82`or;?QkrqcZh.JE;rcEEUeAGu<$s8;ok*Z$"DpAY$is8;oo +rVuZfreMX7(EOamrr<#frr2p,q#C6_p\Y!ds8VinrVlHhs8N&srVulrr;ZZns8W,urr;uss8W)t +s8=/>qY9d>^oa9#T:MI@S=QOmiqiTLs8W)us8N&uqZ$TorVQQn!ri6!Q2c@~> +Sc8]grqZZmrVccrrVucqrVQTtqt9g\rr2p3qXNISQ=jAr9i4M\@=k#\^VRn/aSj9[aSs?^aSs0\ +aNDa*c2Yugb/aM##0+^+aiqoMrl@CJaMYp8b0SJ]b/h?8cHOMO`QZ`TbKeGWaKLbTAOY>#d*KeY +cj0pg#1(TaSj-X`V[X[aNDcP +aMu6@rlkVh_nWq#_u.LR`qd^S`ZW@B`l5d.\!qbV<`)^h;,U:oAr!kOmIL5UqYU'Zq"t!grrW/p +rVc`urVQKjQiDR~> +U&Y&io)J^gr;ZfrrquZn$LH[.kj.F"o&99@rr3/tqXsX[r;HWtrr;uqrr!3%rquZkr;Q]qs8;im +q>:dX!<)oo%K-2(rqu`i.junur;HWorr`9!qu?Wm$+290+>*Bfs82iTrr2rtrW)loqu6Nn +rr;orqu?Klp]C9gr;$?mrVlftqtg +R/d0bmJeL2s82Tgpt`TYT:hjHU8FU(i;3?Mq>U?mrr;uts8Dfor;6Ekrql`qrr;rcs82fqs7QBj +r;ccpq@3?#rr2iopF.F<.Js&Brr<#t&,cJ*qtg,o-QsK\p\t3is6oses8W)urVulss8)`gs8Drl +ruV1UWVS_BgJcGZJJ,~> +SH&Qdnc/Rdr;S8Do'PGINbVct9h.lT:3r;9]u7h3b0.uNbKS5Ub0J,J`5T[6a2Z-?b0'_-!6G)X +s2b)Ws2G#Xs2YVkbf\#H`lQ6Db/hZHr652c!RK'jbl>j/bf\#MaT;GE!mf*7bK7lI`P]j@c-t7- +$3gJ6bKS2Nam[=N`WsT/c-=JR`l@krpr<Uc-+5K +`P]g=`kf +JcGcLqYpomgu7VbmdfDlkP+rT!W;oqquH`mrrW,pqYpEmrr],jkK%ursAVtq>:*hs8W)tLAuc~> +JcG`Ls8^Blp\tHsrVZQiq>UBn#QF]"rVuosr;Q^1 +rVQNdjNOs'St)=DT:MOHTXrqOrdk+Hs*t~> +JcG`L/H5P?mG>jKC0FY.5Y4dSEfdTBbKS,Pb/hWEbg+GUbKS)J_o'I4`l?*@b0'P$q8VX>s2Y/Z +,0.dHd)=>Ub.ts$c-4DSbK7cDaihcNc-O_?G]n2RbK%cKh8oE3rlG#[q8iTY.*TiM_oBa=b0.rN +c-=AM`lQ6?_ns!\LhTa`8kVrV:JY)'L9fCoo_nagrr%NMJ,~> +Jc>`Mrr)rrqu$I$f[A^>k3;-oi9gL@s8W$*r;Q`prVZQhqYU9kr;Zfro`*kKs8N +JcG]K(&[n)qtg*AWLKWWTUqjPU:Jb?pAFpgrrN-!rqlcqrVuoss8Duss8N)ur;HTlrr(dSs8N +K)ZtlqY9sdr;?Tnp?]SE@TcDr92APfDMtU*_8jdFbK@oJrl4rX$HgT6aMu6=`P][9p<*BY!6X?C +s2bJ_`lZHNc--(0!m8^-rl,Gl_TC9Qf%.sgaiqlIaN)cN!QrLZ`rF-[b5KO1bKS8Wc-+8O`5Ta8 +a2lEDbIaO$@T#ol:.RoL;HdXQ[F+(&p&"^frr%KLJ,~> +Jc>uQq>1!cqYC-grsJJRg$.V]lg*Ejo(r@krr<#trVc`prVuosrquU?gqu$Hms8Dcn%->mkkNM:)nF#W(ip?RB +rqlcqJcG`LJ,~> +Jc>cNrr)lsr"o2(p\F7%XeDPWR@g:IZH2\2p\Y!jrr)irs8Dus"98>urqucsrVZWmqu$Km!W;oX +rW)utrX&W%qu-Hms8)Zlrr2os#l4N!qu6Khp\t0l"9&8trS@JQs8;lorVlfqrVl]qrVl]o!rVon +rr)ruqu6L1rVlflm_bluVOj +K)YuPq"OU_r;S/An`7f[C0+P4:/OteAq%"n]Yhb0`PfsA`lH-Bb0.rMaMu6=_o9^pb59E^blQ&2 +k/[PErl>Mo`Q-*Ccd^+]bKS,Mrl,Jabf@]Fai_ZLda$%VbJV9Ia90T+`l7nrr5eiZ--Xc`cHab[ +b/_B6^qRY'`4`=$>Zars7nQBK +K)YuQqt^!]rqQQlqYpokg$/%ikihHtj73$Hs8;rsr;Qitqt^3c!;ucps8;ln!W;rps5X(arV?Kn +qZ$Bgs7cKprVulprr30"q#C9js5 +JcGQG+TD?;qtg3Vbb:l2R[KV?UT25lq>L0fs8N#rr;HWnrVlZn"TA8rrr2iqs8E0"r;6Ejj8K>_ +q>^Kks7uQlr;QWo"9/?"r;Q^#r:pm4 +s8;Q`qXW=UW1Tc[U7@dGTqA=$iq`HJq>:0js8W)urVl`pr;chOs*t~> +JcGKE(&dgkjNXKtQ]=l2__un!!b/sG$qoJf[rQP8c!R/^Ha9]i(aj%iPa2uU* +bS84@`l?!;`l6'@`m2rTaMQ0>`Q-!WaT'9WaSEsZaN"2"rlY8^s2P)X,0%dKbg+MXbKJAW_7mXe +P'0r&:fC+Y77^9[E0\2pjR)R9qYpZpqYL/Cs*t~> +K)Z2Wqt^'_p\Oa^rVHBj$iB,Fm-3j%khk.VoCi1crqlNjp]:3grVlotqu-UBop\=^hs8)Zlrs8H#r;ZfqpAb'Mrr;luqu$BkpAb*jqu?]q!;uin!Vu]nrqcX(oAe`h +me-)5iT94Xm.UAWs8E&rp\ssgqtg +JcG`Ls8Dut%K?D(rr2]lrV#^&YG.onSI2$]Upnb6p&4acrr2crrVulns8MusrqcWsrqu]ml2C\] +rVlfp!rN#nrr2umr;QWo!W2iors8K$rVuosp](3Orqufrrr;rqr;HTmrqlWorqudIrr<#trVlis +pA=UFaK_V7S=H1>R@pIS]$BO9r;-0_s8W)us8Mrqrqu`nre(6$~> +JcG]K*W?!:qYp?krV$$]k0KZIB44Y18kVcUC5JXh^;%Sb_un$$b/jG%rPnfWrQ>,`!6XQI!6>&Z +rl5\kcGRcLdDa>Rb0%fF`l5p>_SjRq`sBN*`5g$>^C2gShsL(/qYC-oqtg*arIb-#~> +Jc>fIrVl]o!<2cn%/&W5jlu$un*/]cmf*.as82oprr)iurqu]mrr;oqqu?Qorr)lXrr2j"s82ip +r;$?lqYh$'s8W#lr;Zfpqu?ZpqZ$HPrr;luqu$Bkq#CBns8W,us8;cks82lrrVuoqrVmB'lf.!l +mHsW8n+5\pjRi?L#Q4JsqYC$drqcWsqt^'eL];l~> +Jc>cIrqcZprW)usrYYP2pX?RFTq@dFUSFN^^Y.c>qu6NjrVliqrpp*hrW)oWrr2j"s8;orr;$?l +rr2lr$2so(rV?Els8DlprrW)tqr7VQs8W)urVuforV?Heru(e8rqlTlqXN^r\"oXhSXH"2R&$IO +]\3&Vrr<#ts8N-!rVccprdk*"~> +Jc>`Jr;Qs!qY9d[qu-O3mGlj$LM:-s92&,V;-eC2XLlBi`59I3`r='Y`r*gK`oP5>`u`.?`Q#g? +bKJ&NaMu6=`Pfm?`4s40a2Gj9`Q#d<`Sno<`r4!Qb5]Q_b5]]bblc24aMn.r*6-.C_o'='Xdt/] +=&N$t:f'SR=^,BlY1;LtoD8Cb!<2ut!WDlop]:6iKE$H~> +JcGWIs8E6$s8W#srV?HlrVm8ohra4imHWcliR[8brrr>tqu$HmqZ#^W!<;op$30r&rVulsr;Zfp +rrS(rs\l(qY^6dq>C9mr;-Eirri8qq"XiDs*t~> +K)biLrquiprr)ls"TAAuq>UBn'DhL\^n[ToS=Z=CTVnX%i:QmCr;Q]orVulos8)`os69L_s8)^& +s8Duss8N&rs8W)tr;R9*r;Q`rqZ$Tprr)irqZ#ORrr;usqu$Nor;Qcrp&>I"r;?Eer:o3jWMcJ_ +rga"Xs.9Rk\)@,Zs8W)trr30#rr<#tr.4nFs*t~> +JcGcK!<2ut"oJ)fpA"C\rttY'n)rN=P\jf.:JXe_<*j'dU:%YI^r+(c`W*p[`Q#m:pr2L=!6P)Y +(rsS7`Q?3EaNMcJaMu6=a2uBA`QQ<;`lA"r"3&O'al1>@`r4!RaplG3`lQVbf\#HrP]\q +_nNdiQZlG.9hJ&R;,^V(@:t;&gZJ,'q>L9l%/p)%qt^*cqY9j^qu)0IJ,~> +JcGTH"9/5rrqucur;6BhrVmArm,mHrmd90%i8`_cs82cp!<)lr"8V]bqYL3qrr)ipqu!)cqu?]p +s83K)q"FU_r;$0gs8;iqrVZ]pqu$I(o]kArl/q6slg!j!gZ/&)rri5oq"ap^rrrE"qt^-gL];l~> +K)Z&Sr;6EirVlZn!ri,sr;S/Cp[Q/.Un"$LSslOKVPh;chYR0Ks8;ioq>L?nrr)lrq>(!eqt^0g +s5O%Vs7uZms8Drms5!\Ss8W)prVc`rrr2rtr;Zd@r;HZor;Zfps8DNJgVg@kTqS'LTq7gHTqf*E +l1OoNr;HWls8W(Ls7lVE~> +K)YrOqY9scrri;sq"jmes"F?Jqtg*Wj40ZPC0Oe?;bU1e`Pfs?`l6'@ +`50@3a2R#YrPnZSrPniZ!m&C$rPefV!6G/^iQ2&=s2b,\s2ch7aiMN?_o0O3`QQNKbKe5Nb/V?> +`jhh1G[O<@9hJ&R9M8&[BS`b5i9^(3qu6iuqY9j^qYL'hrIY'"~> +JcGWI"TJ>rr;QWo!<2um!VuZkrs\DVhraCilL=!!j5'2"rri5qqt^6err`2oq#$ibqu6]rqu6Nq +qt^*fr!*-"s8Dlqr;$?sp#G#ik3Va>m0)Y4hWF%rrr`2rqu$ +K)YiMrqccprVlZn!ri/tqu7Q4q>0U4\tbacUR@^NT;/?a`:ETtqu6Wjrr2p%r;6Bfrq?6cs5O%V +s7uZns82fms4mSRs8N#sr;uuqrVHNprVl`p,5_<9rVcQcpYEusVOa6IURe'RUnXT_[cQuRrr)cm +qY^ +K)YrOqtp?hrrE#squ?Trs8Drqs!dg7lIE2!D-U4G7n?KO<*Wg\YHk^P`lc6?aNV`E`lH-C`5]sB +a2Z*;iQ1u;q8iQT#ft04b0%fF`l7qs"j"j/bf\)faT'9[aSj6la2uNHaMu9A`P][:bfRrLrl#eo +_nNdqT8%)a8ju*>85<5h>$PZd][>g/q#1'h#Q=AgpA"L]rV6EmrJ(?&~> +JcGZJ!<2rps8Moq!WN&srW3&urVm?+khkLil/q6kjmM6rq#:9prVu`lrsJ]$s8;Zequ$?fr1a2h +s8Dp%s8Mrnr;6?frr3-"qY'XYqu72ujQPdjlL=91l0INggZA"krrE&tr;ZbIs*t~> +K)YuQrVZTloD\gir;RW1rVZQ\e@;L?nrqu]rrr)ips8;lmrr`9! +rr.ZPJ,~> +JcGBB#6"GrrVc`prVn8@p?gnbWeY7i;GB\T84crcBS;h^^V@n.b0.oNbK.`Lb/VKEbK@rcaSX!O +a90T/b0'_)!64uYs2kDeb0&_bs2P)[rQ5PnbfIcC`Pf[gSo(;YPrqud"rVH +JcGcLs8Vckqu6fur;6 +JcG`KqYpQpqu?]q!r`,trr3r:qYgBjn`@X"V4sfYSXGt=U8PTIhtm0HqY0sfrrE&srrW/us5EtS +s1eR4s8W)ts!%:UEorr2llrr<#qrr2rsrWE,s +r;DBMJ,~> +JcGZJ!<)co#6"DprVuioqu7u@i2HZe+F/q"t!c +qY^?krrrAuqYL*fo`#$lqYL/Gs*t~> +K)Z#Rq>'pcrql]urquZhqtg +JcGWI"oeJur;HWms8W$#r;HTnrqQL*p=I@s8W&np\XpbrVZE`p?LAFX.lDbUS=9MStDIGWOfk$me6JTrV?Hks8N#t!<2ors8E-!s8N&t +rr2rrrW<#rreCH'~> +JcGBB!r_ohrr3-"qtp?krVm<)q"4%?gpiu3DIQUCr_*u(='0*gTAK%\^=_Q9o_7tRqu-KnrVcTg +qu,s_"9&,orJ1E'~> +K)Z#Rq>'pcrql`qrr2cprqcWsrVZQirqQTlpAP"%q!Hu'jQ>Uflg!WkgtC][p&Ccc!rr9!r;?Ys~> +JcGWIs8<#srVcZo#6+T"rr)iqqu7''s8Vurqt]L#\"KSsSeI`kTV&!SUpJD.n+Q_Zs8N#trqtaS +rVuQirr;`lrr:jSqYq];r;Q`opAagGfZL\"S=l=@T:D@?TV.pNWP6sMp\+Oarr30"s8)TirV6Bo +rr)ips8Drqs8N"Os*t~> +JcGBB!r`#nrr<#s"8i&qrql^5\bhqdYq>L?mr;Zfq +rr)iurquWlqu?Wlrs/Gtq"FFVqYPjD!<7Q~> +JcGWIrVlZn#6+PtqYL-hp&>$kqu79-i8Ek`mHNcun*B3'jl,.[q=ajgrqu]WrW)lprr<#Wrqu]g +rq-4(mGQUKjlksnm-X93lL*ruhqI/\q>Up%rVZThqYC$ar;?QnrVlfnrr`8ur;QQm#6+Ptqt^-g +L];l~> +JcGBB!ri/sq#CBns8;lp.K'&?rr2rtrquHYc_mG2StV^CSYD[FUS4Na\'ja@pA4X_r;?Nmrr;uq +s8W&us6fjds8Muts82fms82fns760es7HSY)=BTV%jIX2ON$ +q#('hqu?Qmqu6Zqp&>$kJcGBBJ,~> +K)Z;YqYL*fs8W&rqtg0drV$6pqtg*`r;Q]q-iX/Gr;--Tkh!tNE+3-R$YZ[O06"# +^;%M#_o9U7`qd[U`pUnJ`q[UZ`l5j7`Q#prb5]Nc`l5j5`o"i^`l5d/^::_ZTo+)4@9HH%8kDNE +9h8/`>%(r`Qe:Nio_87\r;Qisrr)`o!<2inrVlrtqtU-`!r2ZkJcC6~> +JcG3=r;Q?gs8Mio(&%%Mio9%^kj%Etlg*cpjm2=-s8W)srs/K#rqcEcr;?Ek!ri/tr;HWms8N&u +s8E0!qu$Bkr;ZfrrqlltrVlfqrrrE%rVZQirr2oqs8EW/rr2inqtg3dqY^?kr;ZfrrqHF,qXES+ +k2kXblgF-,nF,]/l/(.Uq"4U]rq-6jrqQNkreCH'~> +JcFj3s8Mcm$NC#%r;,g6a/,N(rh'4]&>#MfTUqmWZ,G/Mh=pX@s82Zms8E6&rr2rsrVlTl"9/?# +rqcZmrqud#rr)cmrVc`os8W)ts8N#tr;cimrsAZ(s8Dlqs8Dorrqmi7p[7(rf>=kjU84QXTV%mK +SXuIET;/*V\C9g=qYgBl!WW.Ms5!^*~> +JcG`Jr;Z`op&G!hp]!&JqtTaKki:+'TPn@L>$"Tr8OuEH:/FnqBRPu5R^]fA_SX@:aiDKC`l?!< +`l?'=`PojpaTfi/aN2BA`V[[T`W*jS`W*sXb5TQibf\#G`lH*<`W!mS`W*pfaN;THb/q`Gb/_NB +aMl-t`tHJ5]s"&mMfiN0;H?dt;c-:_8H)3k;-dXNO1*[)nFl_Tqu-KTs8W&rqu?VGrrE(L~> +JcF^/q>LEprqZR!pugMjiofUorU:*]m,d +JcFC&$i^)(rr2lmpuKW+Vu<>#TqJ'MSt2FFVP^?0cIh7Srq[-&s8MoqrV? +JcF^/s8Dfo1&UkDq"F@FftF)4De!!O:eaSU8k_lQ;d!@4HAIl]\@fMg_8F.+a2Pm2`Q6*!rl5&Y`;[UZ`lH*=a2c`Q$!=a2Q* +JcFR+s8MTh(&[:Oh;@/PlL+**l0@?mm-EcunFZPCs8W)trr`9!qYgEkrVlrur;6Hg!W2lprri;u +r;-EirrMrmr;Qcro`#d'lJq$kkN1jilKdZumI0H,lL!]oiSWeeJcEsoJ,~> +JcF@%s8FSJs8N#qrVZQ^g:s\bTU_OETUq[IUR[jIT:;[Z[DL>MmI^,Fq>L +JcF^/s8Dfo0`CnEp\+=QoCMG7e[L`eC0ah8:/4AP9i4MU;H$CqAoN6\Trtl:]YVS+_Sa@6`Pqeq +$-103c-4>Qa2c3ub6,l,a2\%rs2G&[rlkDa4ibO\`lQBIbfS)P`5Kg@c-"2Ka2c3<_SX$qZa-j: +S;DK'=B&0p:Jk"g:/+JZ8k)6E=(H,lOfm!nmIU&Mp]:*eJcF@%J,~> +JcFR+s8N&u!<)co!rVrpqYqN*jPSnSj5K4gmIK`9p@7\AlK%$klg=3 +JcFF'5lL`_rr2lorVuoss8Dlhn`JB9XeheeTq%aIS=Q%6S=6"BTqeTn]?8X[io9%fq=j^^r;6Be +p\F[_r;HWps8;lps8E&trqZTor +JcF^/s8Dip48o3Xqtg'\q#C?kqXa(3eAA8HF_5)g=&2Xg92.uL:/+\j>[M,aH]"#XUS=Wi]Y(to +_ns7)_8aO8a8X-\b/sY+s3(MdaSX!WaSs<]`ra5u`W!t8aN)6;_og$:^V%:r^V.1_Un4'AN-T8L +=]/*s:ImlB7Rp*G:f^Y">?b9;B8WIrdH'cjr;QotqY'UZJcF@%J,~> +JcFC&!<2lqs8;rsp\u9-k2G=amH!^$na>i7nEoE"kNUs]gtpuMlL+B@rQY?Sr:p'Wk2PL_hWNq[ +kiqF!l0836$1@ +JcFC&"9/8urql`prr;mMqtg$Ujjh;XVPL,YU7@jFS"6.BR[ThCU8"E\X0B1PbL,&(mIU/Mp@\+P +p%@tMr;HWorrW/urr2rtrr;p\r;HTns8Mrlp\+CYq=sINp@n+>g![gS\?2[-VOs`YTV8'SURn*N +TUq^HT:MUJVR""dkOn]Lr;HTlqYg>Cs3L^q~> +JcF^/s8Dfos8GIbqtg0ap\4LZqY9dUn*K)eag@[sC0tCH=&Dmj9i4ee;H$_&UQ]tCqf\[oGfaihuVd*^7ec2,W_c2YucaT0:'h"8r&nrIP"#s*t~> +JcF7"!ri5tr;Qcrp&?6/fA5-?gu78Skj@d(lLF6,lf[g,oC)#- +jl,(Ng"Y?ElfmTrlg*d$kiqF!m-Nm6j9=N#iS`Pjq#:BorVQU!r;-9eq=sr?s4@:$~> +JcF@%s8<'!rVQKjrr`5tqYU3g1B%+Gk0h)?VP0oYTUq^GTq@mFTq@mJT:DOLT;&*XWN<8+]YDG/ +db*C6o(;SJp%J(Or:U-brq6Edo_&1T1[O5[f@%pb_n!4\WhlMdU7n6OS=Q=GSY)CASt)@BR\6IP +TqA$QTuYaLq"agarVccpr;6HmquH_Is3q!u~> +JcF^/s8Dcns8E)tq=ss_55P0Us8N&urVQ<[n*8cDUQ95/A8#.O=&i7$`N.l_'E+r`b=BJNt9hS&M9he5U +92&)T:f'ng?!UW<@:sACY0,YcmI^2Kqt^*er;Qfsqu6Nor.4n!s*t~> +JcFF'!WE#irrW2urq?@9rq>U +JcFX-rVlis!WE#ps8W&tquH]prW)onrs\i%r:]mNi6K +JcF^/s8DZkrr)ln!<2fo0)PG8naGi/khb.=\YGC@D.-UJ;bfhM7nQBC852lV;,C.g<**+%$+m+;Gg=j;c6UprDW_q!)EMhs%sY3;,gS!>?=s4@r?m^ +U9r,!k3MR/o^_PEqY^@#rVH?`qYL*dqYL,@s474#~> +JcF=$!ri/tqYpTqr;QTnquQcqqu6Zqq>UZmmdKo;j5g1&!p8b4rp0dTlg!d$rp9[N&*iEElKdg* +n*]Q/lg*p(mf)Z!md'0$l0e$/mdBH-lfm^"lg*j$l0.9kkN:gaj5/e^nF?AMrri?!qtg3hrri>t +q>1$_rrE%Ls474#~> +JcFX-rVl`p%0$5)s8Muqr;Q]qs8Mio)ZK[7s8Muss8Mc\jk%Sd[C3$(SYDjOSt)@CrL`tY!2'7^ +#+qQ]T:_dLrgs.^qORe[StDYPT)YE,S=Q:FT:VUESt;RJTqJ*STV\m%\\cbJlLarMqY^?ls8W)s +rrE&trr`6!rql]n!rDioJcF4!J,~> +JcF^/s8D]l#6"Q$s8Doqq#<5Jq=sa^q>:$^naGc&e&A\fN.#hjAQi;D>Zb!';,C%a9heAZ:ej\W +7n,s=84cBC779O68cDB_8H;9Z74Uo$91hW?77Tj>91qlM:/=_`:JXni='Jp7@:a"dH@gm6U9D\k +j65h"q#(-k"TJAsqY^6j$2j\sqY0d^qu-JEs474#~> +JcF4!!<2or#6+Puqu$BklMh=in`o9"lJUX^lg=$)nG_hUn,Dh`n*]W0n*]T6n*^,?,4=pfnaYr5 +kiV*so'u5?n*TT2n+#o7m-j?%h;dhglg=EFoDejip&+dfJcF'rJ,~> +JcFX-qu6$`s8N#rpAPs0rVlfpnE8QO`4WUhV4jNTT:DCAS=H.@SGo)ZSd1g[St)LGStGnOs.(=+ +S"cIGTU_UES=Q4@St)CFT:DC@TqA$W[(*W^bhD.Jq>U6or;HTnrp'O]rdk+#s*t~> +JcF^/s8DQhs82ipp](9ls82ip+TME=qtTgRm-!BdhUK`_Y+Ll7Jo,"&>#e?l:Jak_91f+Qs%*Gd +9LhH@77TlH8MEY'6:41.9M/#Q7Rfm;8kVcN9i"V_9h\;_?=e)'KSbqh[`-hVk3D:$o_/(TqYU5B +s1SG_~> +JcF4!!<2rss8N&r!rMonn,ELgqtp?brs8A`iS`_UjQ5V)l2U#Kk5OZKn)s-,rTkEin+,u +JcFX-qu6Ek!<2`ms8E9%r;HWps8N#srVlfnrt>>/qYBp]pZUMab.FaMTVA-QT`1S`TE_*\T:VUF +Qhm9XSXuIHS"lVTTH0c"T;&$OU8"?RStW3r`RNW'o(VkQqt9d`rr;usjT#8Z!r`,tJcF@%J,~> +JcF^/s8DNgq#0[]-2dW8q=s[TmHNZefYb+nQA'Bn@qB1Z?!:9.;cQdn;,0bX6:jcI9*n0[7Rfm; +:et(krDXY<>?kTG@q&kYE. +JcF:#rr2lrs8N#q"8i#orr)j!qt^-eq#:EprVc'^s8E?$r:p-crVZ0OmeuMPmeuMXq"OX\q>:*g +l2Ue^!rW&srr;ioq>^ +JcFL)rVl]os8;rspAY-lr;Zfrs8;rqrr!!!rVlfprr*o9r;6?hqtg6ep\4LSn*/leeBc=X`4W^l +\$Vs&qORYU*M9"/ZaREV^r4=BfA#9Kme-AOqYU3equ-NnrVQQls8W)js8MutrVc`jrrE&tr;chJ +s4@:$~> +JcEpn!<)oorr)9b9)SP^q"F=Lmd9?(kiLg^iRcJu]s4E'P)P0EG]I_9<_Q1^:/F\Z91MKG:h4Bb +H[^X+P+JVV]>r:Qi8j%bm- +%%EndData +showpage +%%Trailer +end +%%EOF diff --git a/doc/gnupg-badge-openpgp.jpg b/doc/gnupg-badge-openpgp.jpg new file mode 100644 index 000000000..d396b7c38 Binary files /dev/null and b/doc/gnupg-badge-openpgp.jpg differ diff --git a/doc/gnupg.texi b/doc/gnupg.texi index 478bb4395..d74a76ca9 100644 --- a/doc/gnupg.texi +++ b/doc/gnupg.texi @@ -82,8 +82,16 @@ Boston, MA 02111-1307 USA @title Using the GNU Privacy Guard @subtitle Version @value{VERSION} @subtitle @value{UPDATED} + +@sp 6 + +@image{gnupg-badge-openpgp,8cm,,The GnuPG Logo} + +@sp 6 + @author Werner Koch @code{(wk@@gnupg.org)} + @page @vskip 0pt plus 1filll @copyrightnotice{} @@ -111,6 +119,7 @@ the administration and the architecture. Developer information * Assuan:: Description of the Assuan protocol. +* System Notes:: Notes pertaining to certain OSes. Miscellaneous @@ -133,6 +142,7 @@ Indices @include scdaemon.texi @include assuan.texi +@include sysnotes.texi @include tools.texi @include debugging.texi diff --git a/doc/gpg-agent.texi b/doc/gpg-agent.texi index 28d99673d..9d2cdfc46 100644 --- a/doc/gpg-agent.texi +++ b/doc/gpg-agent.texi @@ -135,7 +135,7 @@ below the home directory of the user. Set the name of the home directory to @var{dir}. If his option is not used, the home directory defaults to @file{~/.gnupg}. It is only recognized when given on the command line. It also overrides any home -directory stated through the environment variable @var{GNUPGHOME} or +directory stated through the environment variable @env{GNUPGHOME} or (on W32 systems) by means on the Registry entry @var{HKCU\Software\GNU\GnuPG:HomeDir}. diff --git a/doc/gpgsm.texi b/doc/gpgsm.texi index 4c167ebf5..1e7368041 100644 --- a/doc/gpgsm.texi +++ b/doc/gpgsm.texi @@ -241,7 +241,7 @@ below the home directory of the user. Set the name of the home directory to @var{dir}. If his option is not used, the home directory defaults to @file{~/.gnupg}. It is only recognized when given on the command line. It also overrides any home -directory stated through the environment variable @var{GNUPGHOME} or +directory stated through the environment variable @env{GNUPGHOME} or (on W32 systems) by means on the Registry entry @var{HKCU\Software\GNU\GnuPG:HomeDir}. diff --git a/doc/scdaemon.texi b/doc/scdaemon.texi index 5265ed21e..cb165da35 100644 --- a/doc/scdaemon.texi +++ b/doc/scdaemon.texi @@ -86,7 +86,7 @@ below the home directory of the user. Set the name of the home directory to @var{dir}. If his option is not used, the home directory defaults to @file{~/.gnupg}. It is only recognized when given on the command line. It also overrides any home -directory stated through the environment variable @var{GNUPGHOME} or +directory stated through the environment variable @env{GNUPGHOME} or (on W32 systems) by means on the Registry entry @var{HKCU\Software\GNU\GnuPG:HomeDir}. diff --git a/doc/sysnotes.texi b/doc/sysnotes.texi new file mode 100644 index 000000000..6ca10c195 --- /dev/null +++ b/doc/sysnotes.texi @@ -0,0 +1,107 @@ +@c Copyright (C) 2004 Free Software Foundation, Inc. +@c This is part of the GnuPG manual. +@c For copying conditions, see the file gnupg.texi. + +@node System Notes +@chapter Notes pertaining to certain OSes. + +GnuPG has been developed on GNU/Linux systems and is know to work on +almost all Free OSes. All modern POSIX systems should be supproted +right now, however there are probably a lot of smaller glitches we need +to fix first. The major problem areas are: + +@itemize +@item +For logging to sockets and other internal operations the +@code{fopencookie} function (@code{funopen} under *BSD) is used. This +is a very convient function which makes it possible to create outputs in +a structures and easy maintainable way. The drawback however is that +most proprietary OSes don't support this function. At g10@tie{}Code we +have looked into several ways on how to overcome this limitation but no +sufficiently easy and maintainable way has been found. Porting +@emph{glibc} to a general POSIX system is of course an option and would +make writing portable software much easier; this it has not yet been +done and the system administrator wouldneed to cope with the GNU +specific admin things in addition to the generic ones of his system. + +We have now settled to use explicit stdio wrappers with a functionality +similar to funopen. Although the code for this has already been written +(@emph{libestream}), we have not yet changed GnuPG to use it. + +This means that on systems not supporting either @code{funopen} or +@code{fopencookie}, logging to a socket won't work, prompts are not +formatted as pretty as theyshould be and @command{gpgsm}'s +@code{LISTKEYS} Assuan command does not work. + +@item +We are planning to use file descriptor passing for interprocess +communication. This will allow us save a lot of resources and improve +performance of certain operations a lot. Systems not supporting this +won't gain these benefits but we try to keep them working the satndard +way as it is done today. + +@item +We require more or less full POSIX compatibility. This has been +arround for 15 years now and thus we don't believe it makes sense to +support non POSIX systems anymore. Well, we of course the usual +workarounds for near POSIX systems well be applied. + +There is one exception of this rule: Systems based the Microsoft Windows +API (called here @emph{W32}) will be supported to some extend. + +@end itemize + + +@menu +* W32 Notes:: Microsoft Windows Notes +@end menu + + +@node W32 Notes +@section Microsoft Windows Notes + +The port to Microsoft Windows based OSes is pretty new and has some +limitations we might remove over time. Note, that we have not yet done +any security audit and you should not use any valuable private key. In +particular, @strong{using it on a box with more than one user, might +lead to a key compromise}. + +@noindent +Current limitations are: + +@itemize +@item +The @code{LISTKEYS} Assuan command of @command{gpgsm} is not supported. +Using the command line options @option{--list-keys} or +@option{--list-secret-keys} does however work. + +@item +No support for CRL checks. By default the option +@option{--disable-crl-checks} has been turned on and the log will show +an appropriate warning message. The reason for this is that the +separate CRL checking daemin (@command{dirmngr}) has not been ported to +W32. + +@item +@command{gpgconf} does not create backup files, so in case of trouble +your configuration file might get lost. + +@item +@command{watchgnupg} is not available. Logging to sockets is not +possible. + +@item +The periodical smartcard status checking done by @command{scdaemon} is +not yet supported. + +@item +Detached running of the gpg-agent is not directly supported. It needs +to be started in a console and left alone then. + +@end itemize + + + + + + diff --git a/doc/tools.texi b/doc/tools.texi index 086f71df1..53233cbd0 100644 --- a/doc/tools.texi +++ b/doc/tools.texi @@ -12,10 +12,11 @@ GnuPG comes with a couple of smaller tools: * addgnupghome:: Create .gnupg home directories. * gpgconf:: Modify .gnupg home directories. * gpgsm-gencert.sh:: Generate an X.509 certificate request. +* gpg-preset-passphrase:: Put a passphrase into the cache. @end menu @c -@c WATHCGNUPG +@c WATCHGNUPG @c @node watchgnupg @section Read logs from a socket @@ -593,3 +594,504 @@ whicl will be printed to stdout. +@c +@c GPG-PRESET-PASSPHRASE +@c +@node gpg-preset-passphrase +@section Put a passphrase into the cache. + +XXXX +The @command{gpgconf} is a utility to automatically and reasonable +safely query and modify configuration files in the @file{.gnupg} home +directory. It is designed not to be invoked manually by the user, but +automatically by graphical user interfaces (GUI).@footnote{Please note +that currently no locking is done, so concurrent access should be +avoided. There are some precautions to avoid corruption with +concurrent usage, but results may be inconsistent and some changes may +get lost. The stateless design makes it difficult to provide more +guarantees.} + +@command{gpgconf} provides access to the configuration of one or more +components of the GnuPG system. These components correspond more or +less to the programs that exist in the GnuPG framework, like GnuPG, +GPGSM, DirMngr, etc. But this is not a strict one-to-one +relationship. Not all configuration options are available through +@command{gpgconf}. @command{gpgconf} provides a generic and abstract +method to access the most important configuration options that can +feasibly be controlled via such a mechanism. + +@command{gpgconf} can be used to gather and change the options +available in each component, and can also provide their default +values. @command{gpgconf} will give detailed type information that +can be used to restrict the user's input without making an attempt to +commit the changes. + +@command{gpgconf} provides the backend of a configuration editor. The +configuration editor would usually be a graphical user interface +program, that allows to display the current options, their default +values, and allows the user to make changes to the options. These +changes can then be made active with @command{gpgconf} again. Such a +program that uses @command{gpgconf} in this way will be called GUI +throughout this section. + +@menu +* Invoking gpgconf:: List of all commands and options. +* Format conventions:: Formatting conventions relevant for all commands. +* Listing components:: List all gpgconf components. +* Listing options:: List all options of a component. +* Changing options:: Changing options of a component. +@end menu + + +@node Invoking gpgconf +@subsection Invoking gpgconf + +One of the following commands must be given: + +@table @gnupgtabopt +@item --list-components +List all components. This is the default command used if none is +specified. + +@item --list-options @var{component} +List all options of the component @var{component}. + +@item --change-options @var{component} +Change the options of the component @var{component}. +@end table + +The following options may be used: + +@table @gnupgtabopt +@c FIXME: Not yet supported. +@c @item -o @var{file} +@c @itemx --output @var{file} +@c Use @var{file} as output file. + +@item -v +@itemx --verbose +Outputs additional information while running. Specifically, this +extends numerical field values by human-readable descriptions. + +@c FIXME: Not yet supported. +@c @item -n +@c @itemx --dry-run +@c Do not actually change anything. Useful together with +@c @code{--change-options} for testing purposes. + +@item -r +@itemx --runtime +Only used together with @code{--change-options}. If one of the +modified options can be changed in a running daemon process, signal +the running daemon to ask it to reparse its configuration file after +changing. + +This means that the changes will take effect at run-time, as far as +this is possible. Otherwise, they will take effect at the next start +of the respective backend programs. +@end table + + +@node Format conventions +@subsection Format conventions + +Some lines in the output of @command{gpgconf} contain a list of +colon-separated fields. The following conventions apply: + +@itemize @bullet +@item +The GUI program is required to strip off trailing newline and/or +carriage return characters from the output. + +@item +@command{gpgconf} will never leave out fields. If a certain version +provides a certain field, this field will always be present in all +@command{gpgconf} versions from that time on. + +@item +Future versions of @command{gpgconf} might append fields to the list. +New fields will always be separated from the previously last field by +a colon separator. The GUI should be prepared to parse the last field +it knows about up until a colon or end of line. + +@item +Not all fields are defined under all conditions. You are required to +ignore the content of undefined fields. +@end itemize + +There are several standard types for the content of a field: + +@table @asis +@item verbatim +Some fields contain strings that are not escaped in any way. Such +fields are described to be used @emph{verbatim}. These fields will +never contain a colon character (for obvious reasons). No de-escaping +or other formatting is required to use the field content. This is for +easy parsing of the output, when it is known that the content can +never contain any special characters. + +@item percent-escaped +Some fields contain strings that are described to be +@emph{percent-escaped}. Such strings need to be de-escaped before +their content can be presented to the user. A percent-escaped string +is de-escaped by replacing all occurences of @code{%XY} by the byte +that has the hexadecimal value @code{XY}. @code{X} and @code{Y} are +from the set @code{0-9a-f}. + +@item localised +Some fields contain strings that are described to be @emph{localised}. +Such strings are translated to the active language and formatted in +the active character set. + +@item @w{unsigned number} +Some fields contain an @emph{unsigned number}. This number will +always fit into a 32-bit unsigned integer variable. The number may be +followed by a space, followed by a human readable description of that +value (if the verbose option is used). You should ignore everything +in the field that follows the number. + +@item @w{signed number} +Some fields contain a @emph{signed number}. This number will always +fit into a 32-bit signed integer variable. The number may be followed +by a space, followed by a human readable description of that value (if +the verbose option is used). You should ignore everything in the +field that follows the number. + +@item option +Some fields contain an @emph{option} argument. The format of an +option argument depends on the type of the option and on some flags: + +@table @asis +@item no argument +The simplest case is that the option does not take an argument at all +(@var{type} @code{0}). Then the option argument is an unsigned number +that specifies how often the option occurs. If the @code{list} flag +is not set, then the only valid number is @code{1}. Options that do +not take an argument never have the @code{default} or @code{optional +arg} flag set. + +@item number +If the option takes a number argument (@var{alt-type} is @code{2} or +@code{3}), and it can only occur once (@code{list} flag is not set), +then the option argument is either empty (only allowed if the argument +is optional), or it is a number. A number is a string that begins +with an optional minus character, followed by one or more digits. The +number must fit into an integer variable (unsigned or signed, +depending on @var{alt-type}). + +@item number list +If the option takes a number argument and it can occur more than once, +then the option argument is either empty, or it is a comma-separated +list of numbers as described above. + +@item string +If the option takes a string argument (@var{alt-type} is 1), and it +can only occur once (@code{list} flag is not set) then the option +argument is either empty (only allowed if the argument is optional), +or it starts with a double quote character (@code{"}) followed by a +percent-escaped string that is the argument value. Note that there is +only a leading double quote character, no trailing one. The double +quote character is only needed to be able to differentiate between no +value and the empty string as value. + +@item string list +If the option takes a number argument and it can occur more than once, +then the option argument is either empty, or it is a comma-separated +list of string arguments as described above. +@end table +@end table + +The active language and character set are currently determined from +the locale environment of the @command{gpgconf} program. + +@c FIXME: Document the active language and active character set. Allow +@c to change it via the command line? + + +@node Listing components +@subsection Listing components + +The command @code{--list-components} will list all components that can +be configured with @command{gpgconf}. Usually, one component will +correspond to one GnuPG-related program and contain the options of +that programs configuration file that can be modified using +@command{gpgconf}. However, this is not necessarily the case. A +component might also be a group of selected options from several +programs, or contain entirely virtual options that have a special +effect rather than changing exactly one option in one configuration +file. + +A component is a set of configuration options that semantically belong +together. Furthermore, several changes to a component can be made in +an atomic way with a single operation. The GUI could for example +provide a menu with one entry for each component, or a window with one +tabulator sheet per component. + +The command argument @code{--list-components} lists all available +components, one per line. The format of each line is: + +@code{@var{name}:@var{description}} + +@table @var +@item name +This field contains a name tag of the component. The name tag is used +to specify the component in all communication with @command{gpgconf}. +The name tag is to be used @emph{verbatim}. It is thus not in any +escaped format. + +@item description +The @emph{string} in this field contains a human-readable description +of the component. It can be displayed to the user of the GUI for +informational purposes. It is @emph{percent-escaped} and +@emph{localized}. +@end table + +Example: +@example +$ gpgconf --list-components +gpg:GPG for OpenPGP +gpg-agent:GPG Agent +scdaemon:Smartcard Daemon +gpgsm:GPG for S/MIME +dirmngr:Directory Manager +@end example + + +@node Listing options +@subsection Listing options + +Every component contains one or more options. Options may be gathered +into option groups to allow the GUI to give visual hints to the user +about which options are related. + +The command argument @code{@w{--list-options @var{component}}} lists +all options (and the groups they belong to) in the component +@var{component}, one per line. @var{component} must be the string in +the field @var{name} in the output of the @code{--list-components} +command. + +There is one line for each option and each group. First come all +options that are not in any group. Then comes a line describing a +group. Then come all options that belong into each group. Then comes +the next group and so on. There does not need to be any group (and in +this case the output will stop after the last non-grouped option). + +The format of each line is: + +@code{@var{name}:@var{flags}:@var{level}:@var{description}:@var{type}:@var{alt-type}:@var{argname}:@var{default}:@var{argdef}:@var{value}} + +@table @var +@item name +This field contains a name tag for the group or option. The name tag +is used to specify the group or option in all communication with +@command{gpgconf}. The name tag is to be used @emph{verbatim}. It is +thus not in any escaped format. + +@item flags +The flags field contains an @emph{unsigned number}. Its value is the +OR-wise combination of the following flag values: + +@table @code +@item group (1) +If this flag is set, this is a line describing a group and not an +option. +@end table + +The following flag values are only defined for options (that is, if +the @code{group} flag is not used). + +@table @code +@item optional arg (2) +If this flag is set, the argument is optional. This is never set for +@var{type} @code{0} (none) options. + +@item list (4) +If this flag is set, the option can be given multiple times. + +@item runtime (8) +If this flag is set, the option can be changed at runtime. + +@item default (16) +If this flag is set, a default value is available. + +@item default desc (32) +If this flag is set, a (runtime) default is available. This and the +@code{default} flag are mutually exclusive. + +@item no arg desc (64) +If this flag is set, and the @code{optional arg} flag is set, then the +option has a special meaning if no argument is given. +@end table + +@item level +This field is defined for options and for groups. It contains an +@emph{unsigned number} that specifies the expert level under which +this group or option should be displayed. The following expert levels +are defined for options (they have analogous meaning for groups): + +@table @code +@item basic (0) +This option should always be offered to the user. + +@item advanced (1) +This option may be offered to advanced users. + +@item expert (2) +This option should only be offered to expert users. + +@item invisible (3) +This option should normally never be displayed, not even to expert +users. + +@item internal (4) +This option is for internal use only. Ignore it. +@end table + +The level of a group will always be the lowest level of all options it +contains. + +@item description +This field is defined for options and groups. The @emph{string} in +this field contains a human-readable description of the option or +group. It can be displayed to the user of the GUI for informational +purposes. It is @emph{percent-escaped} and @emph{localized}. + +@item type +This field is only defined for options. It contains an @emph{unsigned +number} that specifies the type of the option's argument, if any. The +following types are defined: + +Basic types: + +@table @code +@item none (0) +No argument allowed. + +@item string (1) +An @emph{unformatted string}. + +@item int32 (2) +A @emph{signed number}. + +@item uint32 (3) +An @emph{unsigned number}. +@end table + +Complex types: + +@table @code +@item pathname (32) +A @emph{string} that describes the pathname of a file. The file does +not necessarily need to exist. + +@item ldap server (33) +A @emph{string} that describes an LDAP server in the format: + +@code{@var{hostname}:@var{port}:@var{username}:@var{password}:@var{base_dn}} +@end table + +More types will be added in the future. Please see the @var{alt-type} +field for information on how to cope with unknown types. + +@item alt-type +This field is identical to @var{type}, except that only the types +@code{0} to @code{31} are allowed. The GUI is expected to present the +user the option in the format specified by @var{type}. But if the +argument type @var{type} is not supported by the GUI, it can still +display the option in the more generic basic type @var{alt-type}. The +GUI must support all the defined basic types to be able to display all +options. More basic types may be added in future versions. If the +GUI encounters a basic type it doesn't support, it should report an +error and abort the operation. + +@item argname +This field is only defined for options with an argument type +@var{type} that is not @code{0}. In this case it may contain a +@emph{percent-escaped} and @emph{localised string} that gives a short +name for the argument. The field may also be empty, though, in which +case a short name is not known. + +@item default +This field is defined only for options. Its format is that of an +@emph{option argument} (@xref{Format conventions}, for details). If +the default value is empty, then no default is known. Otherwise, the +value specifies the default value for this option. Note that this +field is also meaningful if the option itself does not take a real +argument. + +@item argdef +This field is defined only for options for which the @code{optional +arg} flag is set. If the @code{no arg desc} flag is not set, its +format is that of an @emph{option argument} (@xref{Format +conventions}, for details). If the default value is empty, then no +default is known. Otherwise, the value specifies the default value +for this option. If the @code{no arg desc} flag is set, the field is +either empty or contains a description of the effect of this option if +no argument is given. Note that this field is also meaningful if the +option itself does not take a real argument. + +@item value +This field is defined only for options. Its format is that of an +@emph{option argument}. If it is empty, then the option is not +explicitely set in the current configuration, and the default applies +(if any). Otherwise, it contains the current value of the option. +Note that this field is also meaningful if the option itself does not +take a real argument. +@end table + + +@node Changing options +@subsection Changing options + +The command @w{@code{--change-options @var{component}}} will attempt +to change the options of the component @var{component} to the +specified values. @var{component} must be the string in the field +@var{name} in the output of the @code{--list-components} command. You +have to provide the options that shall be changed in the following +format on standard input: + +@code{@var{name}:@var{flags}:@var{new-value}} + +@table @var +@item name +This is the name of the option to change. @var{name} must be the +string in the field @var{name} in the output of the +@code{--list-options} command. + +@item flags +The flags field contains an @emph{unsigned number}. Its value is the +OR-wise combination of the following flag values: + +@table @code +@item default (16) +If this flag is set, the option is deleted and the default value is +used instead (if applicable). +@end table + +@item new-value +The new value for the option. This field is only defined if the +@code{default} flag is not set. The format is that of an @emph{option +argument}. If it is empty (or the field is omitted), the default +argument is used (only allowed if the argument is optional for this +option). Otherwise, the option will be set to the specified value. +@end table + +Examples: + +To set the force option, which is of basic type @code{none (0)}: + +@example +$ echo 'force:0:1' | gpgconf --change-options dirmngr +@end example + +To delete the force option: + +@example +$ echo 'force:16:' | gpgconf --change-options dirmngr +@end example + +The @code{--runtime} option can influence when the changes take +effect. + + + diff --git a/jnlib/w32-afunix.c b/jnlib/w32-afunix.c index eac4f9560..c93d389da 100644 --- a/jnlib/w32-afunix.c +++ b/jnlib/w32-afunix.c @@ -49,17 +49,18 @@ _w32_sock_connect (int sockfd, struct sockaddr * addr, int addrlen) struct sockaddr_in myaddr; struct sockaddr_un * unaddr; FILE * fp; - int port = 0; + int port; unaddr = (struct sockaddr_un *)addr; fp = fopen (unaddr->sun_path, "rb"); if (!fp) - return -1; + return -1; fscanf (fp, "%d", &port); fclose (fp); + /* XXX: set errno in this case */ if (port < 0 || port > 65535) - return -1; + return -1; myaddr.sin_family = AF_INET; myaddr.sin_port = port; diff --git a/sm/ChangeLog b/sm/ChangeLog index 0dcaa9c20..7a16cb570 100644 --- a/sm/ChangeLog +++ b/sm/ChangeLog @@ -1,6 +1,7 @@ 2004-12-21 Werner Koch * gpgsm.c (main): Use default_homedir(). + (main) [W32]: Default to disabled CRL checks. 2004-12-20 Werner Koch diff --git a/sm/gpgsm.c b/sm/gpgsm.c index 935d50474..074027bf2 100644 --- a/sm/gpgsm.c +++ b/sm/gpgsm.c @@ -757,6 +757,9 @@ main ( int argc, char **argv) opt.def_cipher_algoid = "1.2.840.113549.3.7"; /*des-EDE3-CBC*/ opt.homedir = default_homedir (); +#ifdef HAVE_W32_SYSTEM + opt.no_crl_checks = 1; +#endif /* First check whether we have a config file on the commandline */ orig_argc = argc; diff --git a/tools/ChangeLog b/tools/ChangeLog index 34e985947..38b9e9cf4 100644 --- a/tools/ChangeLog +++ b/tools/ChangeLog @@ -1,3 +1,8 @@ +2004-12-21 Werner Koch + + * gpgconf-comp.c (get_config_pathname) [DOSISH]: Detect absolute + pathnames with a drive letter. + 2004-12-15 Werner Koch * Makefile.am (bin_PROGRAMS) [W32]: Do not build watchgnupg. diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index fe696301c..e4758c1e6 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -1272,7 +1272,13 @@ get_config_pathname (gc_component_t component, gc_backend_t backend) else pathname = ""; +#ifdef HAVE_DOSISH_SYSTEM + if (!(pathname[0] + && pathname[1] == ':' + && (pathname[2] == '/' || pathname[2] == '\\'))) +#else if (pathname[0] != '/') +#endif gc_error (1, 0, "Option %s, needed by backend %s, is not absolute", gc_backend[backend].option_config_filename, gc_backend[backend].name); -- cgit From 4e5bf2fd93a175f64aa1ca2e4b35dcf853f7f828 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 23 Feb 2005 21:06:32 +0000 Subject: * command-ssh.c (get_passphrase): Removed. (ssh_identity_register): Partly rewritten. (open_control_file, search_control_file, add_control_entry): New. (ssh_handler_request_identities): Return only files listed in our control file. * findkey.c (unprotect): Check for allocation error. * agent.h (opt): Add fields to record the startup terminal settings. * gpg-agent.c (main): Record them and do not force keep display with --enable-ssh-support. * command-ssh.c (start_command_handler_ssh): Use them here. * gpg-agent.c: Renamed option --ssh-support to --enable-ssh-support. * command.c (cmd_readkey): New. (register_commands): Register new command "READKEY". * command-ssh.c (ssh_request_process): Improved logging. * findkey.c (agent_write_private_key): Always use plain open. Don't depend on an umask for permissions. (agent_key_from_file): Factored file reading code out to .. (read_key_file): .. new function. (agent_public_key_from_file): New. --- agent/ChangeLog | 40 +++++- agent/agent.h | 12 ++ agent/command-ssh.c | 363 +++++++++++++++++++++++++++++++++++++++++----------- agent/command.c | 54 +++++++- agent/findkey.c | 330 +++++++++++++++++++++++++++++++++++++++-------- agent/gpg-agent.c | 30 +++-- agent/keyformat.txt | 6 + agent/protect.c | 2 +- agent/query.c | 2 +- 9 files changed, 690 insertions(+), 149 deletions(-) (limited to 'agent/command.c') diff --git a/agent/ChangeLog b/agent/ChangeLog index 420dc6368..47ca2debf 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,33 @@ +2005-02-23 Werner Koch + + * command-ssh.c (get_passphrase): Removed. + (ssh_identity_register): Partly rewritten. + (open_control_file, search_control_file, add_control_entry): New. + (ssh_handler_request_identities): Return only files listed in our + control file. + + * findkey.c (unprotect): Check for allocation error. + + * agent.h (opt): Add fields to record the startup terminal + settings. + * gpg-agent.c (main): Record them and do not force keep display + with --enable-ssh-support. + * command-ssh.c (start_command_handler_ssh): Use them here. + + * gpg-agent.c: Renamed option --ssh-support to + --enable-ssh-support. + + * command.c (cmd_readkey): New. + (register_commands): Register new command "READKEY". + + * command-ssh.c (ssh_request_process): Improved logging. + + * findkey.c (agent_write_private_key): Always use plain open. + Don't depend on an umask for permissions. + (agent_key_from_file): Factored file reading code out to .. + (read_key_file): .. new function. + (agent_public_key_from_file): New. + 2005-02-22 Werner Koch * command-ssh.c (stream_read_string): Removed call to abort on @@ -1092,21 +1122,21 @@ Mon Aug 21 17:59:17 CEST 2000 Werner Koch - * gpg-agent.c (passphrase_dialog): Cleanup the window and added the + * gpg-agent.c (passphrase_dialog): Cleanup the window and added the user supplied text to the window. (main): Fixed segv in gtk_init when used without a command to start. - * gpg-agent.c: --flush option. + * gpg-agent.c: --flush option. (req_flush): New. (req_clear_passphrase): Implemented. Fri Aug 18 14:27:14 CEST 2000 Werner Koch - * gpg-agent.c: New. - * Makefile.am: New. + * gpg-agent.c: New. + * Makefile.am: New. - Copyright 2001, 2002 Free Software Foundation, Inc. + Copyright 2001, 2002, 2003, 2004, 2005 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 diff --git a/agent/agent.h b/agent/agent.h index a1196bc0b..0661cc4ad 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -53,6 +53,15 @@ struct { int dry_run; /* Don't change any persistent data */ int batch; /* Batch mode */ const char *homedir; /* Configuration directory name */ + + /* Environment setting gathred at program start. */ + const char *startup_display; + const char *startup_ttyname; + const char *startup_ttytype; + const char *startup_lc_ctype; + const char *startup_lc_messages; + + const char *pinentry_program; /* Filename of the program to start as pinentry. */ const char *scdaemon_program; /* Filename of the program to handle @@ -150,6 +159,9 @@ gpg_error_t agent_key_from_file (ctrl_t ctrl, const unsigned char *grip, unsigned char **shadow_info, int ignore_cache, gcry_sexp_t *result); +gpg_error_t agent_public_key_from_file (ctrl_t ctrl, + const unsigned char *grip, + gcry_sexp_t *result); int agent_key_available (const unsigned char *grip); /*-- query.c --*/ diff --git a/agent/command-ssh.c b/agent/command-ssh.c index 1719602f2..8ea042e19 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -23,13 +23,14 @@ #include +#include #include #include #include #include #include #include -#include +#include #include "agent.h" @@ -63,7 +64,22 @@ #define SSH_DSA_SIGNATURE_ELEMS 2 #define SPEC_FLAG_USE_PKCS1V2 (1 << 0) - + +/* The blurb we put into the header of a newly created control file. */ +static const char sshcontrolblurb[] = +"# List of allowed ssh keys. Only keys present in this file are used\n" +"# in the SSH protocol. The ssh-add tool may add new entries to this\n" +"# file to enable them; you may also add them manually. Comment\n" +"# lines, like this one, as well as empty lines are ignored. Lines do\n" +"# have a certain length limit but this is not serious limitation as\n" +"# the format of the entries is fixed and checked by gpg-agent. A\n" +"# non-comment line starts with optional white spaces, followed by the\n" +"# keygrip of the key given as 40 hex digits, optionally followed by a\n" +"# the caching TTL in seconds and another optional field for arbitrary\n" +"# flags. Prepend the keygrip with an '!' mark to disable it.\n" +"\n"; + + /* Macros. */ @@ -626,6 +642,155 @@ file_to_buffer (const char *filename, unsigned char **buffer, size_t *buffer_n) } + + +/* Open the ssh control file and create it if not available. With + APPEND passed as true the file will be opened in append mode, + otherwise in read only mode. On success a file pointer is stored + at the address of R_FP. */ +static gpg_error_t +open_control_file (FILE **r_fp, int append) +{ + gpg_error_t err; + char *fname; + FILE *fp; + + /* Note: As soon as we start to use non blocking functions here + (i.e. where Pth might switch threads) we need to employ a + mutex. */ + *r_fp = NULL; + fname = make_filename (opt.homedir, "sshcontrol.txt", NULL); + fp = fopen (fname, append? "a+":"r"); + if (!fp && errno == ENOENT) + { + /* Fixme: "x" is a GNU extension. We might want to use the es_ + functions here. */ + fp = fopen (fname, "wx"); + if (!fp) + { + err = gpg_error (gpg_err_code_from_errno (errno)); + log_error (_("can't create `%s': %s\n"), fname, gpg_strerror (err)); + xfree (fname); + return err; + } + fputs (sshcontrolblurb, fp); + fclose (fp); + fp = fopen (fname, append? "a+":"r"); + } + + if (!fp) + { + err = gpg_error (gpg_err_code_from_errno (errno)); + log_error (_("can't open `%s': %s\n"), fname, gpg_strerror (err)); + xfree (fname); + return err; + } + + *r_fp = fp; + + return 0; +} + + +/* Search the file at stream FP from the beginning until a matching + HEXGRIP is found; return success in this case and store true at + DISABLED if the found key has been disabled. */ +static gpg_error_t +search_control_file (FILE *fp, const char *hexgrip, int *disabled) +{ + int c, i; + char *p, line[256]; + + assert (strlen (hexgrip) == 40 ); + + rewind (fp); + *disabled = 0; + next_line: + do + { + if (!fgets (line, DIM(line)-1, fp) ) + { + if (feof (fp)) + return gpg_error (GPG_ERR_EOF); + return gpg_error (gpg_err_code_from_errno (errno)); + } + + if (!*line || line[strlen(line)-1] != '\n') + { + /* Eat until end of line */ + while ( (c=getc (fp)) != EOF && c != '\n') + ; + return gpg_error (*line? GPG_ERR_LINE_TOO_LONG + : GPG_ERR_INCOMPLETE_LINE); + } + + /* Allow for empty lines and spaces */ + for (p=line; spacep (p); p++) + ; + } + while (!*p || *p == '\n' || *p == '#'); + + *disabled = 0; + if (*p == '!') + { + *disabled = 1; + for (p++; spacep (p); p++) + ; + } + + for (i=0; hexdigitp (p) && i < 40; p++, i++) + if (hexgrip[i] != (*p >= 'a'? (*p & 0xdf): *p)) + goto next_line; + if (i != 40 || !(spacep (p) || *p == '\n')) + { + log_error ("invalid formatted line in ssh control file\n"); + return gpg_error (GPG_ERR_BAD_DATA); + } + + /* Fixme: Get TTL and flags. */ + + return 0; /* Okay: found it. */ +} + + + +/* Add an entry to the control file to mark the key with the keygrip + HEXGRIP as usable for SSH; i.e. it will be returned when ssh asks + for it. This function is in general used to add a key received + through the ssh-add function. We can assume that the user wants to + allow ssh using this key. */ +static gpg_error_t +add_control_entry (ctrl_t ctrl, const char *hexgrip, int ttl) +{ + gpg_error_t err; + FILE *fp; + int disabled; + + err = open_control_file (&fp, 1); + if (err) + return err; + + err = search_control_file (fp, hexgrip, &disabled); + if (err && gpg_err_code(err) == GPG_ERR_EOF) + { + struct tm *tp; + time_t atime = time (NULL); + + /* Not yet in the file - add it. Becuase the file has been + opened in append mode, we simply need to write to it. */ + tp = localtime (&atime); + fprintf (fp, "# Key added on %04d-%02d-%02d %02d:%02d:%02d\n%s %d\n", + 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, + tp->tm_hour, tp->tm_min, tp->tm_sec, + hexgrip, ttl); + + } + fclose (fp); + return 0; +} + + + /* @@ -1377,6 +1542,7 @@ ssh_handler_request_identities (ctrl_t ctrl, gpg_error_t err; gpg_error_t ret_err; int ret; + FILE *ctrl_fp = NULL; /* Prepare buffer stream. */ @@ -1427,6 +1593,19 @@ ssh_handler_request_identities (ctrl_t ctrl, /* FIXME: make sure that buffer gets deallocated properly. */ + /* Fixme: We should better iterate over the control file and check + whether the key file is there. This is better in resepct to + performance if tehre are a lot of key sin our key storage. */ + + err = open_control_file (&ctrl_fp, 0); + if (err) + goto out; + +#warning Really need to fix this fixme. + /* + FIXME: First check whether a key is currently available in the card reader - this should be allowed even without being listed in sshcontrol.txt. + */ + while (1) { dir_entry = readdir (dir); @@ -1435,6 +1614,19 @@ ssh_handler_request_identities (ctrl_t ctrl, if ((strlen (dir_entry->d_name) == 44) && (! strncmp (dir_entry->d_name + 40, ".key", 4))) { + char hexgrip[41]; + int disabled; + + /* We do only want to return keys listed in our control + file. */ + strncpy (hexgrip, dir_entry->d_name, 40); + hexgrip[40] = 0; + if ( strlen (hexgrip) != 40 ) + continue; + if (search_control_file (ctrl_fp, hexgrip, &disabled) + || disabled) + continue; + strncpy (key_path + key_directory_n + 1, dir_entry->d_name, 40); /* Read file content. */ @@ -1522,6 +1714,9 @@ ssh_handler_request_identities (ctrl_t ctrl, if (dir) closedir (dir); + if (ctrl_fp) + fclose (ctrl_fp); + free (key_directory); xfree (key_path); xfree (buffer); @@ -1802,43 +1997,6 @@ ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response) return ret_err; } -static gpg_error_t -get_passphrase (ctrl_t ctrl, - const char *description, size_t passphrase_n, char *passphrase) -{ - struct pin_entry_info_s *pi; - gpg_error_t err; - - err = 0; - pi = gcry_calloc_secure (1, sizeof (*pi) + passphrase_n + 1); - if (! pi) - { - err = gpg_error (GPG_ERR_ENOMEM); - goto out; - } - - pi->min_digits = 0; /* We want a real passphrase. */ - pi->max_digits = 8; - pi->max_tries = 1; - pi->failed_tries = 0; - pi->check_cb = NULL; - pi->check_cb_arg = NULL; - pi->cb_errtext = NULL; - pi->max_length = 100; - - err = agent_askpin (ctrl, description, NULL, pi); - if (err) - goto out; - - memcpy (passphrase, pi->pin, passphrase_n); - passphrase[passphrase_n] = 0; - - out: - - xfree (pi); - - return err; -} static gpg_error_t ssh_key_extract_comment (gcry_sexp_t key, char **comment) @@ -1929,76 +2087,100 @@ ssh_key_to_buffer (gcry_sexp_t key, const char *passphrase, return err; } + + +/* Store the ssh KEY into our local key storage and protect him after + asking for a passphrase. Cache that passphrase. TTL is the + maximum caching time for that key. If the key already exists in + our key storage, don't do anything. When entering a new key also + add an entry to the sshcontrol file. */ static gpg_error_t ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl) { + gpg_error_t err; unsigned char key_grip_raw[21]; - unsigned char *buffer; - unsigned int buffer_n; - char passphrase[100]; - char *description; char key_grip[41]; - char *comment; - gpg_error_t err; + unsigned char *buffer = NULL; + unsigned int buffer_n; + char *description = NULL; + char *comment = NULL; unsigned int i; - int ret; - - description = NULL; - comment = NULL; - buffer = NULL; + struct pin_entry_info_s *pi = NULL; err = ssh_key_grip (key, key_grip_raw); if (err) goto out; - key_grip_raw[sizeof (key_grip_raw) - 1] = 0; - ret = agent_key_available (key_grip_raw); - if (! ret) - goto out; + key_grip_raw[sizeof (key_grip_raw) - 1] = 0; /* FIXME: Why?? */ + /* Check whether the key is alread in our key storage. Don't do + anything then. */ + if ( !agent_key_available (key_grip_raw) ) + goto out; /* Yes, key is available. */ + + err = ssh_key_extract_comment (key, &comment); if (err) goto out; - ret = asprintf (&description, - "Please provide the passphrase, which should be used " - "for protecting the received secret key `%s':", - comment ? comment : ""); - if (ret < 0) + if ( asprintf (&description, + _("Please enter a passphrase to protect%%0A" + "the received secret key%%0A" + " %s%%0A" + "within gpg-agent's key storage"), + comment ? comment : "?") < 0) { - err = gpg_err_code_from_errno (errno); + err = gpg_error_from_errno (errno); goto out; } - err = get_passphrase (ctrl, description, sizeof (passphrase), passphrase); + + pi = gcry_calloc_secure (1, sizeof (*pi) + 100 + 1); + if (!pi) + { + err = gpg_error_from_errno (errno); + goto out; + } + pi->max_length = 100; + pi->max_tries = 1; + err = agent_askpin (ctrl, description, NULL, pi); if (err) goto out; - err = ssh_key_to_buffer (key, passphrase, &buffer, &buffer_n); + err = ssh_key_to_buffer (key, pi->pin, &buffer, &buffer_n); if (err) goto out; + /* Store this key to our key storage. */ err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0); if (err) goto out; + /* Cache this passphrase. */ for (i = 0; i < 20; i++) sprintf (key_grip + 2 * i, "%02X", key_grip_raw[i]); - err = agent_put_cache (key_grip, passphrase, ttl); + err = agent_put_cache (key_grip, pi->pin, ttl); if (err) goto out; - out: + /* And add an entry to the sshcontrol file. */ + err = add_control_entry (ctrl, key_grip, ttl); + + out: + if (pi && pi->max_length) + wipememory (pi->pin, pi->max_length); + xfree (pi); xfree (buffer); xfree (comment); - free (description); - /* FIXME: verify xfree vs free. */ + free (description); /* (asprintf allocated, thus regular free.) */ return err; } + + static gpg_error_t ssh_identity_drop (gcry_sexp_t key) { @@ -2234,12 +2416,9 @@ ssh_request_process (ctrl_t ctrl, estream_t stream_sock) if (err) goto out; - if (opt.verbose) /* FIXME: using log_debug is not good with - verbose. log_debug should only be used in - debugging mode or in sitattions which are - unexpected. */ - log_debug ("received request of length: %u\n", - request_data_size); + if (opt.verbose > 1) + log_info ("received ssh request of length %u\n", + (unsigned int)request_data_size); request = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+"); if (! request) @@ -2277,17 +2456,28 @@ ssh_request_process (ctrl_t ctrl, estream_t stream_sock) break; if (i == DIM (request_specs)) { - log_debug ("request %u is not supported\n", - request_type); + log_info ("ssh request %u is not supported\n", request_type); send_err = 1; goto out; } if (opt.verbose) - log_debug ("executing request handler: %s (%u)\n", + log_info ("ssh request handler for %s (%u) started\n", request_specs[i].identifier, request_specs[i].type); err = (*request_specs[i].handler) (ctrl, request, response); + + if (opt.verbose) + { + if (err) + log_info ("ssh request handler for %s (%u) failed: %s\n", + request_specs[i].identifier, request_specs[i].type, + gpg_strerror (err)); + else + log_info ("ssh request handler for %s (%u) ready\n", + request_specs[i].identifier, request_specs[i].type); + } + if (err) { send_err = 1; @@ -2295,6 +2485,10 @@ ssh_request_process (ctrl_t ctrl, estream_t stream_sock) } response_size = es_ftell (response); + if (opt.verbose > 1) + log_info ("sending ssh response of length %u\n", + (unsigned int)response_size); + err = es_fseek (response, 0, SEEK_SET); if (err) { @@ -2325,6 +2519,8 @@ ssh_request_process (ctrl_t ctrl, estream_t stream_sock) if (send_err) { + if (opt.verbose > 1) + log_info ("sending ssh error response\n"); err = stream_write_uint32 (stream_sock, 1); if (err) goto leave; @@ -2341,7 +2537,7 @@ ssh_request_process (ctrl_t ctrl, estream_t stream_sock) es_fclose (response); xfree (request_data); /* FIXME? */ - return !! err; + return !!err; } void @@ -2359,6 +2555,21 @@ start_command_handler_ssh (int sock_client) agent_init_default_ctrl (&ctrl); ctrl.connection_fd = sock_client; + /* Because the ssh protocol does not send us information about the + the current TTY setting, we resort here to use those from startup + or those explictly set. */ + if (!ctrl.display && opt.startup_display) + ctrl.display = strdup (opt.startup_display); + if (!ctrl.ttyname && opt.startup_ttyname) + ctrl.ttyname = strdup (opt.startup_ttyname); + if (!ctrl.ttytype && opt.startup_ttytype) + ctrl.ttytype = strdup (opt.startup_ttytype); + if (!ctrl.lc_ctype && opt.startup_lc_ctype) + ctrl.lc_ctype = strdup (opt.startup_lc_ctype); + if (!ctrl.lc_messages && opt.startup_lc_messages) + ctrl.lc_messages = strdup (opt.startup_lc_messages); + + /* Create stream from socket. */ stream_sock = es_fdopen (sock_client, "r+"); if (!stream_sock) diff --git a/agent/command.c b/agent/command.c index dc8a4a158..997140207 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1,5 +1,5 @@ /* command.c - gpg-agent command handler - * Copyright (C) 2001, 2002, 2003, 2004 Free Software Foundation, Inc. + * Copyright (C) 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -22,12 +22,14 @@ some buffering in secure mempory to protect session keys etc. */ #include + #include #include #include #include #include #include +#include #include @@ -504,6 +506,55 @@ cmd_genkey (ASSUAN_CONTEXT ctx, char *line) } + + +/* READKEY + + Return the public key for the given keygrip. */ +static int +cmd_readkey (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + unsigned char grip[20]; + gcry_sexp_t s_pkey = NULL; + + rc = parse_keygrip (ctx, line, grip); + if (rc) + return rc; /* Return immediately as this is already an Assuan error code.*/ + + rc = agent_public_key_from_file (ctrl, grip, &s_pkey); + if (!rc) + { + size_t len; + unsigned char *buf; + + len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0); + assert (len); + buf = xtrymalloc (len); + if (!buf) + rc = gpg_error_from_errno (errno); + else + { + len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, buf, len); + assert (len); + rc = assuan_send_data (ctx, buf, len); + rc = map_assuan_err (rc); + xfree (buf); + } + gcry_sexp_release (s_pkey); + } + + if (rc) + log_error ("command readkey failed: %s\n", gpg_strerror (rc)); + return map_to_assuan_status (rc); +} + + + + + + /* GET_PASSPHRASE [ ] This function is usually used to ask for a passphrase to be used @@ -894,6 +945,7 @@ register_commands (ASSUAN_CONTEXT ctx) { "PKSIGN", cmd_pksign }, { "PKDECRYPT", cmd_pkdecrypt }, { "GENKEY", cmd_genkey }, + { "READKEY", cmd_readkey }, { "GET_PASSPHRASE", cmd_get_passphrase }, { "PRESET_PASSPHRASE", cmd_preset_passphrase }, { "CLEAR_PASSPHRASE", cmd_clear_passphrase }, diff --git a/agent/findkey.c b/agent/findkey.c index 1ac57ad07..86a28d511 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -39,7 +39,9 @@ struct try_unprotect_arg_s { }; - +/* Write an S-expression formatted key to our key storage. With FORCE + pased as true an existsing key with the given GRIP will get + overwritten. */ int agent_write_private_key (const unsigned char *grip, const void *buffer, size_t length, int force) @@ -48,51 +50,44 @@ agent_write_private_key (const unsigned char *grip, char *fname; FILE *fp; char hexgrip[40+4+1]; + int fd; for (i=0; i < 20; i++) sprintf (hexgrip+2*i, "%02X", grip[i]); strcpy (hexgrip+40, ".key"); fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); - if (force) - fp = fopen (fname, "wb"); - else - { - int fd; - - if (!access (fname, F_OK)) - { - log_error ("secret key file `%s' already exists\n", fname); - xfree (fname); - return gpg_error (GPG_ERR_GENERAL); - } - /* We would like to create FNAME but only if it does not already - exist. We cannot make this guarantee just using POSIX (GNU - provides the "x" opentype for fopen, however, this is not - portable). Thus, we use the more flexible open function and - then use fdopen to obtain a stream. + if (!force && !access (fname, F_OK)) + { + log_error ("secret key file `%s' already exists\n", fname); + xfree (fname); + return gpg_error (GPG_ERR_GENERAL); + } - The mode parameter to open is what fopen uses. It will be - combined with the process' umask automatically. */ - fd = open (fname, O_CREAT | O_EXCL | O_RDWR, - S_IRUSR | S_IWUSR + /* In FORCE mode we would like to create FNAME but only if it does + not already exist. We cannot make this guarantee just using + POSIX (GNU provides the "x" opentype for fopen, however, this is + not portable). Thus, we use the more flexible open function and + then use fdopen to obtain a stream. */ + fd = open (fname, force? (O_CREAT | O_TRUNC | O_WRONLY) + : (O_CREAT | O_EXCL | O_WRONLY), + S_IRUSR | S_IWUSR #ifndef HAVE_W32_SYSTEM - | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH + | S_IRGRP #endif ); - if (fd < 0) - fp = 0; - else - { - fp = fdopen (fd, "wb"); - if (!fp) - { - int save_e = errno; - close (fd); - errno = save_e; - } - } + if (fd < 0) + fp = NULL; + else + { + fp = fdopen (fd, "wb"); + if (!fp) + { + int save_e = errno; + close (fd); + errno = save_e; + } } if (!fp) @@ -263,6 +258,8 @@ unprotect (CTRL ctrl, const char *desc_text, } pi = gcry_calloc_secure (1, sizeof (*pi) + 100); + if (!pi) + return gpg_error_from_errno (errno); pi->max_length = 100; pi->min_digits = 0; /* we want a real passphrase */ pi->max_digits = 8; @@ -285,32 +282,22 @@ unprotect (CTRL ctrl, const char *desc_text, } - -/* Return the secret key as an S-Exp in RESULT after locating it using - the grip. Returns NULL in RESULT if the operation should be - diverted to a token; SHADOW_INFO will point then to an allocated - S-Expression with the shadow_info part from the file. With - IGNORE_CACHE passed as true the passphrase is not taken from the - cache. DESC_TEXT may be set to present a custom description for the - pinentry. */ -gpg_error_t -agent_key_from_file (CTRL ctrl, const char *desc_text, - const unsigned char *grip, unsigned char **shadow_info, - int ignore_cache, gcry_sexp_t *result) +/* Read the key identified by GRIP from the private key directory and + return it as an gcrypt S-expression object in RESULT. On failure + returns an error code and stores NULL at RESULT. */ +static gpg_error_t +read_key_file (const unsigned char *grip, gcry_sexp_t *result) { int i, rc; char *fname; FILE *fp; struct stat st; unsigned char *buf; - size_t len, buflen, erroff; + size_t buflen, erroff; gcry_sexp_t s_skey; char hexgrip[40+4+1]; - int got_shadow_info = 0; *result = NULL; - if (shadow_info) - *shadow_info = NULL; for (i=0; i < 20; i++) sprintf (hexgrip+2*i, "%02X", grip[i]); @@ -336,8 +323,8 @@ agent_key_from_file (CTRL ctrl, const char *desc_text, } buflen = st.st_size; - buf = xmalloc (buflen+1); - if (fread (buf, buflen, 1, fp) != 1) + buf = xtrymalloc (buflen+1); + if (!buf || fread (buf, buflen, 1, fp) != 1) { rc = gpg_error_from_errno (errno); log_error ("error reading `%s': %s\n", fname, strerror (errno)); @@ -347,6 +334,7 @@ agent_key_from_file (CTRL ctrl, const char *desc_text, return rc; } + /* Convert the file into a gcrypt S-expression object. */ rc = gcry_sexp_sscan (&s_skey, &erroff, buf, buflen); xfree (fname); fclose (fp); @@ -357,18 +345,52 @@ agent_key_from_file (CTRL ctrl, const char *desc_text, (unsigned int)erroff, gpg_strerror (rc)); return rc; } + *result = s_skey; + return 0; +} + + +/* Return the secret key as an S-Exp in RESULT after locating it using + the grip. Returns NULL in RESULT if the operation should be + diverted to a token; SHADOW_INFO will point then to an allocated + S-Expression with the shadow_info part from the file. With + IGNORE_CACHE passed as true the passphrase is not taken from the + cache. DESC_TEXT may be set to present a custom description for the + pinentry. */ +gpg_error_t +agent_key_from_file (ctrl_t ctrl, const char *desc_text, + const unsigned char *grip, unsigned char **shadow_info, + int ignore_cache, gcry_sexp_t *result) +{ + int rc; + unsigned char *buf; + size_t len, buflen, erroff; + gcry_sexp_t s_skey; + int got_shadow_info = 0; + + *result = NULL; + if (shadow_info) + *shadow_info = NULL; + + rc = read_key_file (grip, &s_skey); + if (rc) + return rc; + + /* For use with the protection functions we also need the key as an + canonical encoded S-expression in abuffer. Create this buffer + now. */ len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); assert (len); buf = xtrymalloc (len); if (!buf) { - rc = out_of_core (); + rc = gpg_error_from_errno (errno); gcry_sexp_release (s_skey); return rc; } len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, buf, len); assert (len); - gcry_sexp_release (s_skey); + switch (agent_private_key_type (buf)) { @@ -381,7 +403,7 @@ agent_key_from_file (CTRL ctrl, const char *desc_text, char *desc_text_final; const char *comment = NULL; - /* Note, that we will take the comment as a C styring for + /* Note, that we will take the comment as a C string for display purposes; i.e. all stuff beyond a Nul character is ignored. */ comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0); @@ -460,6 +482,8 @@ agent_key_from_file (CTRL ctrl, const char *desc_text, rc = gpg_error (GPG_ERR_BAD_SECKEY); break; } + gcry_sexp_release (s_skey); + s_skey = NULL; if (rc || got_shadow_info) { xfree (buf); @@ -481,6 +505,200 @@ agent_key_from_file (CTRL ctrl, const char *desc_text, return 0; } + + +/* Return the public key for the keygrip GRIP. The result is stored + at RESULT. This function extracts the public key from the private + key database. On failure an error code is returned and NULL stored + at RESULT. */ +gpg_error_t +agent_public_key_from_file (ctrl_t ctrl, + const unsigned char *grip, + gcry_sexp_t *result) +{ + int i, idx, rc; + gcry_sexp_t s_skey; + const char *algoname; + gcry_sexp_t uri_sexp, comment_sexp; + const char *uri, *comment; + size_t uri_length, comment_length; + char *format, *p; + void *args[4+2+2+1]; /* Size is max. # of elements + 2 for uri + 2 + for comment + end-of-list. */ + int argidx; + gcry_sexp_t list, l2; + const char *name; + const char *s; + size_t n; + const char *elems; + gcry_mpi_t *array; + + *result = NULL; + + rc = read_key_file (grip, &s_skey); + if (rc) + return rc; + + list = gcry_sexp_find_token (s_skey, "shadowed-private-key", 0 ); + if (!list) + list = gcry_sexp_find_token (s_skey, "protected-private-key", 0 ); + if (!list) + list = gcry_sexp_find_token (s_skey, "private-key", 0 ); + if (!list) + { + log_error ("invalid private key format\n"); + gcry_sexp_release (s_skey); + return gpg_error (GPG_ERR_BAD_SECKEY); + } + + l2 = gcry_sexp_cadr (list); + gcry_sexp_release (list); + list = l2; + name = gcry_sexp_nth_data (list, 0, &n); + if (n==3 && !memcmp (name, "rsa", 3)) + { + algoname = "rsa"; + elems = "ne"; + } + else if (n==3 && !memcmp (name, "dsa", 3)) + { + algoname = "dsa"; + elems = "pqgy"; + } + else if (n==3 && !memcmp (name, "elg", 3)) + { + algoname = "elg"; + elems = "pgy"; + } + else + { + log_error ("unknown private key algorithm\n"); + gcry_sexp_release (list); + gcry_sexp_release (s_skey); + return gpg_error (GPG_ERR_BAD_SECKEY); + } + + /* Allocate an array for the parameters and copy them out of the + secret key. FIXME: We should have a generic copy function. */ + array = xtrycalloc (strlen(elems) + 1, sizeof *array); + if (!array) + { + rc = gpg_error_from_errno (errno); + gcry_sexp_release (list); + gcry_sexp_release (s_skey); + return rc; + } + + for (idx=0, s=elems; *s; s++, idx++ ) + { + l2 = gcry_sexp_find_token (list, s, 1); + if (!l2) + { + /* Required parameter not found. */ + for (i=0; i Date: Wed, 18 May 2005 10:48:06 +0000 Subject: Changed the scdaemon to handle concurrent sessions. Adjusted gpg-agent accordingly. Code cleanups. --- ChangeLog | 5 + NEWS | 4 +- TODO | 14 +- agent/ChangeLog | 16 ++ agent/agent.h | 9 + agent/call-scd.c | 397 +++++++++++++++++++++++-------------- agent/command.c | 1 + agent/divert-scd.c | 7 - agent/gpg-agent.c | 96 ++------- configure.ac | 13 +- doc/ChangeLog | 4 + doc/gpg-agent.texi | 5 - doc/tools.texi | 10 + scd/ChangeLog | 36 ++++ scd/apdu.c | 4 + scd/app-common.h | 101 +++++----- scd/app-dinsig.c | 20 +- scd/app-nks.c | 24 +-- scd/app-openpgp.c | 58 +++--- scd/app-p15.c | 8 +- scd/app.c | 341 ++++++++++++++++++++++---------- scd/ccid-driver.c | 9 +- scd/command.c | 43 +++- scd/sc-copykeys.c | 2 +- scd/scdaemon.c | 491 ++++++++++++++++++++++++++++++---------------- scd/scdaemon.h | 3 +- tools/gpg-connect-agent.c | 20 +- 27 files changed, 1095 insertions(+), 646 deletions(-) (limited to 'agent/command.c') diff --git a/ChangeLog b/ChangeLog index 4f58b9198..154ae07e9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2005-05-15 Werner Koch + + * configure.ac: Remove option --disable-threads; require the use + of GNU Pth. + 2005-04-27 Werner Koch * configure.ac: Removed OpenSC detection and options. diff --git a/NEWS b/NEWS index 2d43d70ff..c25bbe08e 100644 --- a/NEWS +++ b/NEWS @@ -1,8 +1,10 @@ Noteworthy changes in version 1.9.17 ------------------------------------------------- + * GNU Pth is now a hard requirement. + * [scdaemon] Support for OpenSC has been removed. Instead a new and - starightforward pkcs#15 modules has been written. As of now it + straightforward pkcs#15 modules has been written. As of now it does allows only signing using TCOS cards but we are going to enhance it to match all the old capabilities. diff --git a/TODO b/TODO index 6a0e9b18e..6e8951f03 100644 --- a/TODO +++ b/TODO @@ -35,6 +35,9 @@ might want to have an agent context for each service request to do that while changing gpgsm to allow concurrent operations. ** support the anyPolicy semantic ** Check that we are really following the verification procedures in rfc3280. +** Implement a --card-status command. + This is useful to check whether a card is supported at all. + * sm/keydb.c ** Check file permissions @@ -54,10 +57,6 @@ might want to have an agent context for each service request ** Don't use stdio to return results. ** Support DSA -* agent/divert-scd.c - Remove the agent_reset_scd kludge. We will do this after Scdaemon - has been changed to allow multiple sessions. Currently in progress. - * Move pkcs-1 encoding into libgcrypt. * Use a MAC to protect sensitive files. @@ -90,10 +89,3 @@ might want to have an agent context for each service request This means we can't reread a configuration ** No card status notifications. - -* scd/ -** Release the card after use so that gpg 1.4 is able to access it - This won't be a sufficient change. we need to change gpg 1.4 to make - use of the agent. Work is underway. - - diff --git a/agent/ChangeLog b/agent/ChangeLog index f5dbeb9e3..00f019ddc 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,19 @@ +2005-05-18 Werner Koch + + * divert-scd.c (ask_for_card): Removed the card reset kludge. + +2005-05-17 Werner Koch + + * call-scd.c (unlock_scd): Add new arg CTRL. Changed all callers. + (start_scd): Reoworked to allow for additional connections. + * agent.h (ctrl_t): Add local data for the SCdaemon. + * command.c (start_command_handler): Release SERVER_LOCAL. + + * gpg-agent.c (create_server_socket): Use xmalloc. + (main): Removed option --disable-pth a dummy. Removed non-pth + code path. + (cleanup_sh): Removed. Not needed anymore. + 2005-05-05 Moritz Schulte * command-ssh.c (ssh_key_to_buffer): Rename to ... diff --git a/agent/agent.h b/agent/agent.h index 298b5b142..6ab65eeba 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -99,10 +99,19 @@ struct { #define DBG_ASSUAN (opt.debug & DBG_ASSUAN_VALUE) struct server_local_s; +struct scd_local_s; +/* Collection of data per session (aka connection). */ struct server_control_s { + + /* Private data of the server (command.c). */ struct server_local_s *server_local; + + /* Private data of the SCdaemon (call-scd.c). */ + struct scd_local_s *scd_local; + int connection_fd; /* -1 or an identifier for the current connection. */ + char *display; char *ttyname; char *ttytype; diff --git a/agent/call-scd.c b/agent/call-scd.c index 8373fd46d..58dd412f0 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -18,12 +18,6 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ -/* Fixme: For now we have serialized all access to the scdaemon which - make sense becuase the scdaemon can't handle concurrent connections - right now. We should however keep a list of connections and lock - just that connection - it migth make sense to implemtn parts of - this in Assuan.*/ - #include #include #include @@ -37,9 +31,7 @@ #ifndef HAVE_W32_SYSTEM #include #endif -#ifdef USE_GNU_PTH -# include -#endif +#include #include "agent.h" #include @@ -50,24 +42,20 @@ #define MAX_OPEN_FDS 20 #endif -static ASSUAN_CONTEXT scd_ctx = NULL; -#ifdef USE_GNU_PTH -static pth_mutex_t scd_lock; -#endif -/* We need to keep track of the connection currently using the SCD. - For a pipe server this is all a NOP because the connection will - always have the connection indicator -1. agent_reset_scd releases - the active connection; i.e. sets it back to -1, so that a new - connection can start using the SCD. If we eventually allow - multiple SCD session we will either make scdaemon multi-threaded or - fork of a new scdaemon and let it see how it can get access to a - reader. -*/ -static int active_connection_fd = -1; -static int active_connection = 0; +/* Definition of module local data of the CTRL structure. */ +struct scd_local_s +{ + assuan_context_t ctx; /* NULL or session context for the SCdaemon + used with this connection. */ + int locked; /* This flag is used to assert proper use of + start_scd and unlock_scd. */ + +}; + /* Callback parameter for learn card */ -struct learn_parm_s { +struct learn_parm_s +{ void (*kpinfo_cb)(void*, const char *); void *kpinfo_cb_arg; void (*certinfo_cb)(void*, const char *); @@ -76,13 +64,39 @@ struct learn_parm_s { void *sinfo_cb_arg; }; -struct inq_needpin_s { - ASSUAN_CONTEXT ctx; +struct inq_needpin_s +{ + assuan_context_t ctx; int (*getpin_cb)(void *, const char *, char*, size_t); void *getpin_cb_arg; }; +/* A Mutex used inside the start_scd function. */ +static pth_mutex_t start_scd_lock; + +/* A malloced string with the name of the socket to be used for + additional connections. May be NULL if not provided by + SCdaemon. */ +static char *socket_name; + +/* The context of the primary connection. This is also used as a flag + to indicate whether the scdaemon has been started. */ +static assuan_context_t primary_scd_ctx; + +/* To allow reuse of the primary connection, the following flag is set + to true if the primary context has been reset and is not in use by + any connection. */ +static int primary_scd_ctx_reusable; + + + +/* Local prototypes. */ +static assuan_error_t membuf_data_cb (void *opaque, + const void *buffer, size_t length); + + + /* This function must be called once to initialize this module. This has to be done before a second thread is spawned. We can't do the @@ -91,27 +105,35 @@ struct inq_needpin_s { void initialize_module_call_scd (void) { -#ifdef USE_GNU_PTH static int initialized; if (!initialized) - if (pth_mutex_init (&scd_lock)) + { + if (!pth_mutex_init (&start_scd_lock)) + log_fatal ("error initializing mutex: %s\n", strerror (errno)); initialized = 1; -#endif /*USE_GNU_PTH*/ + } } +/* The unlock_scd function shall be called after having accessed the + SCD. It is currently not very useful but gives an opportunity to + keep track of connections currently calling SCD. Note that the + "lock" operation is done by the start_scd() function which must be + called and error checked before any SCD operation. CTRL is the + usual connection context and RC the error code to be passed trhough + the function. */ static int -unlock_scd (int rc) +unlock_scd (ctrl_t ctrl, int rc) { -#ifdef USE_GNU_PTH - if (!pth_mutex_release (&scd_lock)) + if (ctrl->scd_local->locked != 1) { - log_error ("failed to release the SCD lock\n"); + log_error ("unlock_scd: invalid lock count (%d)\n", + ctrl->scd_local->locked); if (!rc) rc = gpg_error (GPG_ERR_INTERNAL); } -#endif /*USE_GNU_PTH*/ + ctrl->scd_local->locked = 0; return rc; } @@ -125,68 +147,115 @@ atfork_cb (void *opaque, int where) } -/* Fork off the SCdaemon if this has not already been done. Note that - this fucntion alos locks the daemon. */ +/* Fork off the SCdaemon if this has not already been done. Lock the + daemon and make sure that a proper context has been setup in CTRL. + Thsi fucntion might also lock the daemon, which means that the + caller must call unlock_scd after this fucntion has returned + success and the actual Assuan transaction been done. */ static int start_scd (ctrl_t ctrl) { - int rc; + gpg_error_t err = 0; const char *pgmname; - ASSUAN_CONTEXT ctx; - const char *argv[3]; + assuan_context_t ctx; + const char *argv[4]; int no_close_list[3]; int i; + int rc; if (opt.disable_scdaemon) return gpg_error (GPG_ERR_NOT_SUPPORTED); -#ifdef USE_GNU_PTH - if (!pth_mutex_acquire (&scd_lock, 0, NULL)) + /* If this is the first call for this session, setup the local data + structure. */ + if (!ctrl->scd_local) + { + ctrl->scd_local = xtrycalloc (1, sizeof *ctrl->scd_local); + if (!ctrl->scd_local) + return gpg_error_from_errno (errno); + } + + + /* Assert that the lock count is as expected. */ + if (ctrl->scd_local->locked) { - log_error ("failed to acquire the SCD lock\n"); + log_error ("start_scd: invalid lock count (%d)\n", + ctrl->scd_local->locked); return gpg_error (GPG_ERR_INTERNAL); } -#endif + ctrl->scd_local->locked++; - if (scd_ctx) + /* If we already have a context, we better do a sanity check now to + see whether it has accidently died. This avoids annoying + timeouts and hung connections. */ + if (ctrl->scd_local->ctx) { pid_t pid; - - /* If we are not the connection currently using the SCD, return - an error. */ - if (!active_connection) - { - active_connection_fd = ctrl->connection_fd; - active_connection = 1; - } - else if (ctrl->connection_fd != active_connection_fd) - return unlock_scd (gpg_error (GPG_ERR_CONFLICT)); - - /* Okay, we already started the scdaemon and it is used by us.*/ - - /* We better do a sanity check now to see whether it has - accidently died. */ #ifndef HAVE_W32_SYSTEM - pid = assuan_get_pid (scd_ctx); + pid = assuan_get_pid (ctrl->scd_local->ctx); if (pid != (pid_t)(-1) && pid && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) ) { - assuan_disconnect (scd_ctx); - scd_ctx = NULL; + assuan_disconnect (ctrl->scd_local->ctx); + ctrl->scd_local->ctx = NULL; } else #endif - return 0; + return 0; /* Okay, the context is fine. */ + } + + /* We need to protect the lowwing code. */ + if (!pth_mutex_acquire (&start_scd_lock, 0, NULL)) + { + log_error ("failed to acquire the start_scd lock: %s\n", + strerror (errno)); + return gpg_error (GPG_ERR_INTERNAL); + } + + /* Check whether the pipe server has already been started and in + this case either reuse a lingering pipe connection or establish a + new socket based one. */ + if (primary_scd_ctx && primary_scd_ctx_reusable) + { + ctx = primary_scd_ctx; + primary_scd_ctx_reusable = 0; + if (opt.verbose) + log_info ("new connection to SCdaemon established (reusing)\n"); + goto leave; } + if (socket_name) + { + rc = assuan_socket_connect (&ctx, socket_name, 0); + if (rc) + { + log_error ("can't connect to socket `%s': %s\n", + socket_name, assuan_strerror (rc)); + err = gpg_error (GPG_ERR_NO_SCDAEMON); + goto leave; + } + + if (opt.verbose) + log_info ("new connection to SCdaemon established\n"); + goto leave; + } + + if (primary_scd_ctx) + { + log_info ("SCdaemon is running but won't accept further connections\n"); + err = gpg_error (GPG_ERR_NO_SCDAEMON); + goto leave; + } + + /* Nope, it has not been started. Fire it up now. */ if (opt.verbose) log_info ("no running SCdaemon - starting it\n"); if (fflush (NULL)) { - gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); + err = gpg_error (gpg_err_code_from_errno (errno)); log_error ("error flushing pending output: %s\n", strerror (errno)); - return unlock_scd (tmperr); + goto leave; } if (!opt.scdaemon_program || !*opt.scdaemon_program) @@ -198,7 +267,8 @@ start_scd (ctrl_t ctrl) argv[0] = pgmname; argv[1] = "--server"; - argv[2] = NULL; + argv[2] = "--multi-server"; + argv[3] = NULL; i=0; if (!opt.running_detached) @@ -216,30 +286,68 @@ start_scd (ctrl_t ctrl) { log_error ("can't connect to the SCdaemon: %s\n", assuan_strerror (rc)); - return unlock_scd (gpg_error (GPG_ERR_NO_SCDAEMON)); + err = gpg_error (GPG_ERR_NO_SCDAEMON); + goto leave; } - scd_ctx = ctx; - active_connection_fd = ctrl->connection_fd; - active_connection = 1; - - if (DBG_ASSUAN) - log_debug ("connection to SCdaemon established\n"); - - /* Tell the scdaemon that we want him to send us an event signal. - But only do this if we are running as a regular sever and not - simply as a pipe server. */ - /* Fixme: gpg-agent does not use this signal yet. */ -/* if (ctrl->connection_fd != -1) */ -/* { */ -/* #ifndef HAVE_W32_SYSTEM */ -/* char buf[100]; */ - -/* sprintf (buf, "OPTION event-signal=%d", SIGUSR2); */ -/* assuan_transact (scd_ctx, buf, NULL, NULL, NULL, NULL, NULL, NULL); */ -/* #endif */ -/* } */ - return 0; + if (opt.verbose) + log_debug ("first connection to SCdaemon established\n"); + + /* Get the name of the additional socket opened by scdaemon. */ + { + membuf_t data; + unsigned char *databuf; + size_t datalen; + + xfree (socket_name); + socket_name = NULL; + init_membuf (&data, 256); + assuan_transact (ctx, "GETINFO socket_name", + membuf_data_cb, &data, NULL, NULL, NULL, NULL); + + databuf = get_membuf (&data, &datalen); + if (databuf && datalen) + { + socket_name = xtrymalloc (datalen + 1); + if (!socket_name) + log_error ("warning: can't store socket name: %s\n", + strerror (errno)); + else + { + memcpy (socket_name, databuf, datalen); + socket_name[datalen] = 0; + if (DBG_ASSUAN) + log_debug ("additional connections at `%s'\n", socket_name); + } + } + xfree (databuf); + } + + /* Tell the scdaemon we want him to send us an event signal. */ +#ifndef HAVE_W32_SYSTEM + { + char buf[100]; + + sprintf (buf, "OPTION event-signal=%d", SIGUSR2); + assuan_transact (ctx, buf, NULL, NULL, NULL, NULL, NULL, NULL); + } +#endif + + primary_scd_ctx = ctx; + primary_scd_ctx_reusable = 0; + + leave: + if (err) + { + unlock_scd (ctrl, err); + } + else + { + ctrl->scd_local->ctx = ctx; + } + if (!pth_mutex_release (&start_scd_lock)) + log_error ("failed to release the start_scd lock: %s\n", strerror (errno)); + return err; } @@ -248,25 +356,28 @@ start_scd (ctrl_t ctrl) int agent_reset_scd (ctrl_t ctrl) { - int rc = 0; - -#ifdef USE_GNU_PTH - if (!pth_mutex_acquire (&scd_lock, 0, NULL)) - { - log_error ("failed to acquire the SCD lock for reset\n"); - return gpg_error (GPG_ERR_INTERNAL); - } -#endif - if (active_connection && active_connection_fd == ctrl->connection_fd) + if (ctrl->scd_local) { - if (scd_ctx) - rc = assuan_transact (scd_ctx, "RESET", NULL, NULL, - NULL, NULL, NULL, NULL); - active_connection_fd = -1; - active_connection = 0; + if (ctrl->scd_local->ctx) + { + /* We can't disconnect the primary context becuase libassuan + does a waitpid on it and thus the system would hang. + Instead we send a reset and keep that connection for + reuse. */ + if (ctrl->scd_local->ctx == primary_scd_ctx) + { + if (!assuan_transact (primary_scd_ctx, "RESET", + NULL, NULL, NULL, NULL, NULL, NULL)) + primary_scd_ctx_reusable = 1; + } + else + assuan_disconnect (ctrl->scd_local->ctx); + } + xfree (ctrl->scd_local); + ctrl->scd_local = NULL; } - return unlock_scd (map_assuan_err (rc)); + return 0; } @@ -360,13 +471,13 @@ agent_card_learn (ctrl_t ctrl, parm.certinfo_cb_arg = certinfo_cb_arg; parm.sinfo_cb = sinfo_cb; parm.sinfo_cb_arg = sinfo_cb_arg; - rc = assuan_transact (scd_ctx, "LEARN --force", + rc = assuan_transact (ctrl->scd_local->ctx, "LEARN --force", NULL, NULL, NULL, NULL, learn_status_cb, &parm); if (rc) - return unlock_scd (map_assuan_err (rc)); + return unlock_scd (ctrl, map_assuan_err (rc)); - return unlock_scd (0); + return unlock_scd (ctrl, 0); } @@ -414,16 +525,16 @@ agent_card_serialno (ctrl_t ctrl, char **r_serialno) if (rc) return rc; - rc = assuan_transact (scd_ctx, "SERIALNO", + rc = assuan_transact (ctrl->scd_local->ctx, "SERIALNO", NULL, NULL, NULL, NULL, get_serialno_cb, &serialno); if (rc) { xfree (serialno); - return unlock_scd (map_assuan_err (rc)); + return unlock_scd (ctrl, map_assuan_err (rc)); } *r_serialno = serialno; - return unlock_scd (0); + return unlock_scd (ctrl, 0); } @@ -495,31 +606,32 @@ agent_card_pksign (ctrl_t ctrl, return rc; if (indatalen*2 + 50 > DIM(line)) - return unlock_scd (gpg_error (GPG_ERR_GENERAL)); + return unlock_scd (ctrl, gpg_error (GPG_ERR_GENERAL)); sprintf (line, "SETDATA "); p = line + strlen (line); for (i=0; i < indatalen ; i++, p += 2 ) sprintf (p, "%02X", indata[i]); - rc = assuan_transact (scd_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + rc = assuan_transact (ctrl->scd_local->ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); if (rc) - return unlock_scd (map_assuan_err (rc)); + return unlock_scd (ctrl, map_assuan_err (rc)); init_membuf (&data, 1024); - inqparm.ctx = scd_ctx; + inqparm.ctx = ctrl->scd_local->ctx; inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; snprintf (line, DIM(line)-1, ctrl->use_auth_call? "PKAUTH %s":"PKSIGN %s", keyid); line[DIM(line)-1] = 0; - rc = assuan_transact (scd_ctx, line, + rc = assuan_transact (ctrl->scd_local->ctx, line, membuf_data_cb, &data, inq_needpin, &inqparm, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); - return unlock_scd (map_assuan_err (rc)); + return unlock_scd (ctrl, map_assuan_err (rc)); } sigbuf = get_membuf (&data, &sigbuflen); @@ -531,7 +643,7 @@ agent_card_pksign (ctrl_t ctrl, { gpg_error_t tmperr = out_of_core (); xfree (*r_buf); - return unlock_scd (tmperr); + return unlock_scd (ctrl, tmperr); } p = stpcpy (*r_buf, "(7:sig-val(3:rsa(1:s" ); sprintf (p, "%u:", (unsigned int)sigbuflen); @@ -542,7 +654,7 @@ agent_card_pksign (ctrl_t ctrl, xfree (sigbuf); assert (gcry_sexp_canon_len (*r_buf, *r_buflen, NULL, NULL)); - return unlock_scd (0); + return unlock_scd (ctrl, 0); } /* Decipher INDATA using the current card. Note that the returned value is */ @@ -567,36 +679,37 @@ agent_card_pkdecrypt (ctrl_t ctrl, /* FIXME: use secure memory where appropriate */ if (indatalen*2 + 50 > DIM(line)) - return unlock_scd (gpg_error (GPG_ERR_GENERAL)); + return unlock_scd (ctrl, gpg_error (GPG_ERR_GENERAL)); sprintf (line, "SETDATA "); p = line + strlen (line); for (i=0; i < indatalen ; i++, p += 2 ) sprintf (p, "%02X", indata[i]); - rc = assuan_transact (scd_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + rc = assuan_transact (ctrl->scd_local->ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); if (rc) - return unlock_scd (map_assuan_err (rc)); + return unlock_scd (ctrl, map_assuan_err (rc)); init_membuf (&data, 1024); - inqparm.ctx = scd_ctx; + inqparm.ctx = ctrl->scd_local->ctx; inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; snprintf (line, DIM(line)-1, "PKDECRYPT %s", keyid); line[DIM(line)-1] = 0; - rc = assuan_transact (scd_ctx, line, + rc = assuan_transact (ctrl->scd_local->ctx, line, membuf_data_cb, &data, inq_needpin, &inqparm, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); - return unlock_scd (map_assuan_err (rc)); + return unlock_scd (ctrl, map_assuan_err (rc)); } *r_buf = get_membuf (&data, r_buflen); if (!*r_buf) - return unlock_scd (gpg_error (GPG_ERR_ENOMEM)); + return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM)); - return unlock_scd (0); + return unlock_scd (ctrl, 0); } @@ -619,20 +732,20 @@ agent_card_readcert (ctrl_t ctrl, init_membuf (&data, 1024); snprintf (line, DIM(line)-1, "READCERT %s", id); line[DIM(line)-1] = 0; - rc = assuan_transact (scd_ctx, line, + rc = assuan_transact (ctrl->scd_local->ctx, line, membuf_data_cb, &data, NULL, NULL, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); - return unlock_scd (map_assuan_err (rc)); + return unlock_scd (ctrl, map_assuan_err (rc)); } *r_buf = get_membuf (&data, r_buflen); if (!*r_buf) - return unlock_scd (gpg_error (GPG_ERR_ENOMEM)); + return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM)); - return unlock_scd (0); + return unlock_scd (ctrl, 0); } @@ -655,26 +768,26 @@ agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf) init_membuf (&data, 1024); snprintf (line, DIM(line)-1, "READKEY %s", id); line[DIM(line)-1] = 0; - rc = assuan_transact (scd_ctx, line, + rc = assuan_transact (ctrl->scd_local->ctx, line, membuf_data_cb, &data, NULL, NULL, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); - return unlock_scd (map_assuan_err (rc)); + return unlock_scd (ctrl, map_assuan_err (rc)); } *r_buf = get_membuf (&data, &buflen); if (!*r_buf) - return unlock_scd (gpg_error (GPG_ERR_ENOMEM)); + return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM)); if (!gcry_sexp_canon_len (*r_buf, buflen, NULL, NULL)) { xfree (*r_buf); *r_buf = NULL; - return unlock_scd (gpg_error (GPG_ERR_INV_VALUE)); + return unlock_scd (ctrl, gpg_error (GPG_ERR_INV_VALUE)); } - return unlock_scd (0); + return unlock_scd (ctrl, 0); } @@ -744,7 +857,7 @@ agent_card_getattr (ctrl_t ctrl, const char *name, char **result) if (err) return err; - err = map_assuan_err (assuan_transact (scd_ctx, line, + err = map_assuan_err (assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, NULL, NULL, card_getattr_cb, &parm)); if (!err && parm.error) @@ -758,7 +871,7 @@ agent_card_getattr (ctrl_t ctrl, const char *name, char **result) else xfree (parm.data); - return unlock_scd (err); + return unlock_scd (ctrl, err); } @@ -810,19 +923,19 @@ agent_card_scd (ctrl_t ctrl, const char *cmdline, if (rc) return rc; - inqparm.ctx = scd_ctx; + inqparm.ctx = ctrl->scd_local->ctx; inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; - rc = assuan_transact (scd_ctx, cmdline, + rc = assuan_transact (ctrl->scd_local->ctx, cmdline, pass_data_thru, assuan_context, inq_needpin, &inqparm, pass_status_thru, assuan_context); if (rc) { - return unlock_scd (map_assuan_err (rc)); + return unlock_scd (ctrl, map_assuan_err (rc)); } - return unlock_scd (0); + return unlock_scd (ctrl, 0); } diff --git a/agent/command.c b/agent/command.c index 997140207..8af159f6d 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1061,5 +1061,6 @@ start_command_handler (int listen_fd, int fd) free (ctrl.lc_ctype); if (ctrl.lc_messages) free (ctrl.lc_messages); + xfree (ctrl.server_local); } diff --git a/agent/divert-scd.c b/agent/divert-scd.c index f2ec2f051..f460ffe0c 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -108,13 +108,6 @@ ask_for_card (CTRL ctrl, const unsigned char *shadow_info, char **r_kid) if (!rc) { - /* We better reset the SCD now. This is kludge required - because the scdaemon is currently not always able to - detect the presence of a card. With a fully working - scdaemon this would not be required; i.e. the pkcs#15 - support does not require it because OpenSC correclty - detects a present card. */ - agent_reset_scd (ctrl); if (asprintf (&desc, "%s:%%0A%%0A" " \"%.*s\"", diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 7c682ada7..4ac995c26 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -37,9 +37,7 @@ #endif /*HAVE_W32_SYSTEM*/ #include #include -#ifdef USE_GNU_PTH -# include -#endif +#include #define JNLIB_NEED_LOG_LOGV #include "agent.h" @@ -83,7 +81,6 @@ enum cmd_and_opt_values oLCctype, oLCmessages, oScdaemonProgram, - oDisablePth, oDefCacheTTL, oMaxCacheTTL, oUseStandardSocket, @@ -120,7 +117,6 @@ static ARGPARSE_OPTS opts[] = { { oNoDetach, "no-detach" ,0, N_("do not detach from the console")}, { oNoGrab, "no-grab" ,0, N_("do not grab keyboard and mouse")}, { oLogFile, "log-file" ,2, N_("use a log file for the server")}, - { oDisablePth, "disable-pth", 0, N_("do not allow multiple connections")}, { oUseStandardSocket, "use-standard-socket", 0, N_("use a standard location for the socket")}, { oNoUseStandardSocket, "no-use-standard-socket", 0, "@"}, @@ -157,7 +153,6 @@ static ARGPARSE_OPTS opts[] = { #define DEFAULT_CACHE_TTL (10*60) /* 10 minutes */ #define MAX_CACHE_TTL (120*60) /* 2 hours */ -static volatile int caught_fatal_sig = 0; /* flag to indicate that a shutdown was requested */ static int shutdown_pending; @@ -190,10 +185,11 @@ static const char *debug_level; static char *current_logfile; /* The handle_tick() function may test whether a parent is still - runing. We record the PID of the parent here or -1 if it should be + running. We record the PID of the parent here or -1 if it should be watched. */ static pid_t parent_pid = (pid_t)(-1); + /* Local prototypes. */ @@ -203,17 +199,15 @@ static char *create_socket_name (int use_standard_socket, static int create_server_socket (int is_standard_name, const char *name); static void create_directories (void); -#ifdef USE_GNU_PTH static void handle_connections (int listen_fd, int listen_fd_ssh); -/* Pth wrapper function definitions. */ -GCRY_THREAD_OPTION_PTH_IMPL; -#endif /*USE_GNU_PTH*/ - static int check_for_running_agent (int); +/* Pth wrapper function definitions. */ +GCRY_THREAD_OPTION_PTH_IMPL; + /* Functions. */ @@ -351,28 +345,6 @@ cleanup (void) } -static RETSIGTYPE -cleanup_sh (int sig) -{ - if (caught_fatal_sig) - raise (sig); - caught_fatal_sig = 1; - - /* gcry_control( GCRYCTL_TERM_SECMEM );*/ - cleanup (); - -#ifndef HAVE_DOSISH_SYSTEM - { /* reset action to default action and raise signal again */ - struct sigaction nact; - nact.sa_handler = SIG_DFL; - sigemptyset( &nact.sa_mask ); - nact.sa_flags = 0; - sigaction( sig, &nact, NULL); - } -#endif - raise( sig ); -} - /* Handle options which are allowed to be reset after program start. Return true when the current option in PARGS could be handled and @@ -462,7 +434,6 @@ main (int argc, char **argv ) int csh_style = 0; char *logfile = NULL; int debug_wait = 0; - int disable_pth = 0; int gpgconf_list = 0; int standard_socket = 0; gpg_error_t err; @@ -481,14 +452,12 @@ main (int argc, char **argv ) /* Libgcrypt requires us to register the threading model first. Note that this will also do the pth_init. */ -#ifdef USE_GNU_PTH err = gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pth); if (err) { log_fatal ("can't register GNU Pth with Libgcrypt: %s\n", gpg_strerror (err)); } -#endif /*USE_GNU_PTH*/ /* Check that the libraries are suitable. Do it here because @@ -634,7 +603,6 @@ main (int argc, char **argv ) case oSh: csh_style = 0; break; case oServer: pipe_server = 1; break; case oDaemon: is_daemon = 1; break; - case oDisablePth: disable_pth = 1; break; case oDisplay: default_display = xstrdup (pargs.r.ret_str); break; case oTTYname: default_ttyname = xstrdup (pargs.r.ret_str); break; @@ -983,45 +951,17 @@ main (int argc, char **argv ) exit (1); } + { + struct sigaction sa; + + sa.sa_handler = SIG_IGN; + sigemptyset (&sa.sa_mask); + sa.sa_flags = 0; + sigaction (SIGPIPE, &sa, NULL); + } #endif /*!HAVE_W32_SYSTEM*/ - -#ifdef USE_GNU_PTH - if (!disable_pth) - { -#ifndef HAVE_W32_SYSTEM /* FIXME */ - struct sigaction sa; - - sa.sa_handler = SIG_IGN; - sigemptyset (&sa.sa_mask); - sa.sa_flags = 0; - sigaction (SIGPIPE, &sa, NULL); -#endif - handle_connections (fd, opt.ssh_support ? fd_ssh : -1); - } - else -#endif /*!USE_GNU_PTH*/ - /* setup signals */ - { -#ifndef HAVE_W32_SYSTEM /* FIXME */ - struct sigaction oact, nact; - - nact.sa_handler = cleanup_sh; - sigemptyset (&nact.sa_mask); - nact.sa_flags = 0; - - sigaction (SIGHUP, NULL, &oact); - if (oact.sa_handler != SIG_IGN) - sigaction (SIGHUP, &nact, NULL); - sigaction( SIGTERM, NULL, &oact ); - if (oact.sa_handler != SIG_IGN) - sigaction (SIGTERM, &nact, NULL); - nact.sa_handler = SIG_IGN; - sigaction (SIGPIPE, &nact, NULL); - sigaction (SIGINT, &nact, NULL); -#endif - start_command_handler (fd, -1); - } + handle_connections (fd, opt.ssh_support ? fd_ssh : -1); close (fd); } @@ -1127,7 +1067,7 @@ reread_configuration (void) /* Create a name for the socket. With USE_STANDARD_SOCKET given as - true ising STANDARD_NAME in the home directory or if given has + true using STANDARD_NAME in the home directory or if given has false from the mkdir type name TEMPLATE. In the latter case a unique name in a unique new directory will be created. In both cases check for valid characters as well as against a maximum @@ -1195,7 +1135,7 @@ create_server_socket (int is_standard_name, const char *name) agent_exit (2); } - serv_addr = malloc (sizeof (*serv_addr)); /* FIXME. */ + serv_addr = xmalloc (sizeof (*serv_addr)); memset (serv_addr, 0, sizeof *serv_addr); serv_addr->sun_family = AF_UNIX; assert (strlen (name) + 1 < sizeof (serv_addr->sun_path)); @@ -1325,7 +1265,6 @@ create_directories (void) -#ifdef USE_GNU_PTH /* This is the worker for the ticker. It is called every few seconds and may only do fast operations. */ static void @@ -1581,7 +1520,6 @@ handle_connections (int listen_fd, int listen_fd_ssh) cleanup (); log_info (_("%s %s stopped\n"), strusage(11), strusage(13)); } -#endif /*USE_GNU_PTH*/ /* Figure out whether an agent is available and running. Prints an diff --git a/configure.ac b/configure.ac index dee0a9f09..369762b02 100644 --- a/configure.ac +++ b/configure.ac @@ -541,10 +541,6 @@ fi AC_SUBST(PTH_CFLAGS) AC_SUBST(PTH_LIBS) -AC_ARG_ENABLE(threads, - AC_HELP_STRING([--disable-threads],[allow building without Pth support]) - ) - dnl Must check for network library requirements before doing link tests dnl for ldap, for example. If ldap libs are static (or dynamic and without @@ -1106,17 +1102,14 @@ fi if test "$missing_pth" = "yes"; then AC_MSG_NOTICE([[ *** -*** It is strongly suggested to build with support for the +*** It is now required to build with support for the *** GNU Portable Threads Library (Pth). Please install this -*** library first or use --disable-threads to allow building -*** anyway. The library is for example available at +*** library first. The library is for example available at *** ftp://ftp.gnu.org/gnu/pth/ *** On a Debian GNU/Linux system you can install it using *** apt-get install libpth-dev ***]]) - if test "$enable_threads" != "no"; then - die=yes - fi + die=yes fi if test "$die" = "yes"; then diff --git a/doc/ChangeLog b/doc/ChangeLog index b1f5e8037..25840a5b1 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,7 @@ +2005-05-17 Werner Koch + + * gpg-agent.texi (Agent Options): Removed --disable-pth. + 2005-04-27 Werner Koch * tools.texi (symcryptrun): Added. diff --git a/doc/gpg-agent.texi b/doc/gpg-agent.texi index 33c8c148c..fa005c3b7 100644 --- a/doc/gpg-agent.texi +++ b/doc/gpg-agent.texi @@ -253,11 +253,6 @@ should in general not be used to avaoid X-sniffing attacks. Append all logging output to @var{file}. This is very helpful in seeing what the agent actually does. -@item --disable-pth -@opindex disable-pth -Don't allow multiple connections. This option is in general not very -useful. - @anchor{option --allow-mark-trusted} @item --allow-mark-trusted @opindex allow-mark-trusted diff --git a/doc/tools.texi b/doc/tools.texi index 805a17e6c..b2463c351 100644 --- a/doc/tools.texi +++ b/doc/tools.texi @@ -722,6 +722,16 @@ directory stated through the environment variable @env{GNUPGHOME} or (on W32 systems) by means on the Registry entry @var{HKCU\Software\GNU\GnuPG:HomeDir}. + +@item -S +@itemx --raw-socket @var{name} +@opindex S +@opindex raw-socket +Connect to socket @var{name} assuming this is an Assuan style server. +Do not run any special initializations or environment checks. This may +be used to directly connect to any Assuan style socket server. + + @end table @c diff --git a/scd/ChangeLog b/scd/ChangeLog index d82e92904..19bba2bf4 100644 --- a/scd/ChangeLog +++ b/scd/ChangeLog @@ -1,3 +1,39 @@ +2005-05-17 Werner Koch + + * scdaemon.c: Removed non-pth code paths. + (create_socket_name, create_server_socket): New. Taken from + ../agent/gpg-agent. + (cleanup): Changed to adjust for SOCKET_NAME now being malloced. + (ticker_thread): Always use pth_event_occurred; it is again + defined for all decent PTH versions. + (handle_connections): New. Based on the gpg-agent code. + (start_connection_thread): Ditto. + (ticker_thread): Removed. + (cleanup_sh): Removed. + (main): Run the handler for the pipe server in a separate + thread. This replaces the old ticker thread. + (scd_get_socket_name): New. + * command.c (cmd_getinfo): New command GETINFO. + (scd_command_handler): Renamed argument and changed code to use an + already connected FD. + +2005-05-15 Werner Koch + + * app.c, app-common.h, app-nks.c, app-p15.c, app-dinsig.c + * app-openpgp.c: Change most function return types from int to + gpg_error_t. + * command.c (pin_cb): Ditto. + * sc-copykeys.c (pincb): Ditto. + + * app.c (lock_reader, unlock_reader): New. Changed call handler + wrappers to make use of these functions. + +2005-05-07 Werner Koch + + * ccid-driver.c (do_close_reader): Don't do a reset before close. + Some folks reported that it makes the SCR335 hang less often. + Look at the source on how to re-enable it. + 2005-04-27 Werner Koch * app-p15.c (micardo_mse): New. diff --git a/scd/apdu.c b/scd/apdu.c index d23a4adc9..212b9df24 100644 --- a/scd/apdu.c +++ b/scd/apdu.c @@ -20,6 +20,10 @@ * $Id$ */ +/* NOTE: This module is also used by other software, thus the use of + the macro USE_GNU_PTH is mandatory. For GnuPG this macro is + guaranteed to be defined true. */ + #include #include #include diff --git a/scd/app-common.h b/scd/app-common.h index 594f93850..517286c49 100644 --- a/scd/app-common.h +++ b/scd/app-common.h @@ -46,56 +46,56 @@ struct app_ctx_s { struct app_local_s *app_local; /* Local to the application. */ struct { void (*deinit) (app_t app); - int (*learn_status) (app_t app, ctrl_t ctrl); - int (*readcert) (app_t app, const char *certid, + gpg_error_t (*learn_status) (app_t app, ctrl_t ctrl); + gpg_error_t (*readcert) (app_t app, const char *certid, unsigned char **cert, size_t *certlen); - int (*readkey) (app_t app, const char *certid, + gpg_error_t (*readkey) (app_t app, const char *certid, unsigned char **pk, size_t *pklen); - int (*getattr) (app_t app, ctrl_t ctrl, const char *name); - int (*setattr) (app_t app, const char *name, - int (*pincb)(void*, const char *, char **), + gpg_error_t (*getattr) (app_t app, ctrl_t ctrl, const char *name); + gpg_error_t (*setattr) (app_t app, const char *name, + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *value, size_t valuelen); - int (*sign) (app_t app, + gpg_error_t (*sign) (app_t app, const char *keyidstr, int hashalgo, - int (pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ); - int (*auth) (app_t app, const char *keyidstr, - int (*pincb)(void*, const char *, char **), + gpg_error_t (*auth) (app_t app, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen); - int (*decipher) (app_t app, const char *keyidstr, - int (pincb)(void*, const char *, char **), + gpg_error_t (*decipher) (app_t app, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen); - int (*genkey) (app_t app, ctrl_t ctrl, + gpg_error_t (*genkey) (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, - int (*pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); - int (*change_pin) (app_t app, ctrl_t ctrl, + gpg_error_t (*change_pin) (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, - int (*pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); - int (*check_pin) (app_t app, const char *keyidstr, - int (pincb)(void*, const char *, char **), + gpg_error_t (*check_pin) (app_t app, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); } fnc; }; #if GNUPG_MAJOR_VERSION == 1 -int app_select_openpgp (app_t app); -int app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp); -int app_openpgp_storekey (app_t app, int keyno, +gpg_error_t app_select_openpgp (app_t app); +gpg_error_t app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp); +gpg_error_t app_openpgp_storekey (app_t app, int keyno, unsigned char *template, size_t template_len, time_t created_at, const unsigned char *m, size_t mlen, const unsigned char *e, size_t elen, - int (*pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); #else /*-- app-help.c --*/ @@ -107,72 +107,73 @@ size_t app_help_read_length_of_cert (int slot, int fid, size_t *r_certoff); gpg_error_t select_application (ctrl_t ctrl, int slot, const char *name, app_t *r_app); void release_application (app_t app); -int app_munge_serialno (app_t app); -int app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp); -int app_write_learn_status (app_t app, ctrl_t ctrl); -int app_readcert (app_t app, const char *certid, +gpg_error_t app_munge_serialno (app_t app); +gpg_error_t app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp); +gpg_error_t app_write_learn_status (app_t app, ctrl_t ctrl); +gpg_error_t app_readcert (app_t app, const char *certid, unsigned char **cert, size_t *certlen); -int app_readkey (app_t app, const char *keyid, +gpg_error_t app_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen); -int app_getattr (app_t app, ctrl_t ctrl, const char *name); -int app_setattr (app_t app, const char *name, - int (*pincb)(void*, const char *, char **), +gpg_error_t app_getattr (app_t app, ctrl_t ctrl, const char *name); +gpg_error_t app_setattr (app_t app, const char *name, + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *value, size_t valuelen); -int app_sign (app_t app, const char *keyidstr, int hashalgo, - int (pincb)(void*, const char *, char **), +gpg_error_t app_sign (app_t app, const char *keyidstr, int hashalgo, + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ); -int app_auth (app_t app, const char *keyidstr, - int (*pincb)(void*, const char *, char **), +gpg_error_t app_auth (app_t app, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen); -int app_decipher (app_t app, const char *keyidstr, - int (pincb)(void*, const char *, char **), +gpg_error_t app_decipher (app_t app, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ); -int app_genkey (app_t app, ctrl_t ctrl, +gpg_error_t app_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, - int (*pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); -int app_get_challenge (app_t app, size_t nbytes, unsigned char *buffer); -int app_change_pin (app_t app, ctrl_t ctrl, +gpg_error_t app_get_challenge (app_t app, size_t nbytes, + unsigned char *buffer); +gpg_error_t app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, - int (*pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); -int app_check_pin (app_t app, const char *keyidstr, - int (*pincb)(void*, const char *, char **), +gpg_error_t app_check_pin (app_t app, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); /*-- app-openpgp.c --*/ -int app_select_openpgp (app_t app); +gpg_error_t app_select_openpgp (app_t app); -int app_openpgp_cardinfo (app_t app, +gpg_error_t app_openpgp_cardinfo (app_t app, char **serialno, char **disp_name, char **pubkey_url, unsigned char **fpr1, unsigned char **fpr2, unsigned char **fpr3); -int app_openpgp_storekey (app_t app, int keyno, +gpg_error_t app_openpgp_storekey (app_t app, int keyno, unsigned char *template, size_t template_len, time_t created_at, const unsigned char *m, size_t mlen, const unsigned char *e, size_t elen, - int (*pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg); -int app_openpgp_readkey (app_t app, int keyno, +gpg_error_t app_openpgp_readkey (app_t app, int keyno, unsigned char **m, size_t *mlen, unsigned char **e, size_t *elen); /*-- app-nks.c --*/ -int app_select_nks (app_t app); +gpg_error_t app_select_nks (app_t app); /*-- app-dinsig.c --*/ -int app_select_dinsig (app_t app); +gpg_error_t app_select_dinsig (app_t app); /*-- app-p15.c --*/ gpg_error_t app_select_p15 (app_t app); diff --git a/scd/app-dinsig.c b/scd/app-dinsig.c index 38fbc79ee..28b70c466 100644 --- a/scd/app-dinsig.c +++ b/scd/app-dinsig.c @@ -85,7 +85,7 @@ #include "tlv.h" -static int +static gpg_error_t do_learn_status (app_t app, ctrl_t ctrl) { gpg_error_t err; @@ -162,7 +162,7 @@ do_learn_status (app_t app, ctrl_t ctrl) FIXME: This needs some cleanups and caching with do_learn_status. */ -static int +static gpg_error_t do_readcert (app_t app, const char *certid, unsigned char **cert, size_t *certlen) { @@ -273,9 +273,9 @@ do_readcert (app_t app, const char *certid, /* Verify the PIN if required. */ -static int +static gpg_error_t verify_pin (app_t app, - int (pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { if (!app->did_chv1 || app->force_chv1 ) @@ -326,12 +326,12 @@ verify_pin (app_t app, If a PIN is required the PINCB will be used to ask for the PIN; that callback should return the PIN in an allocated buffer and store that in the 3rd argument. */ -static int +static gpg_error_t do_sign (app_t app, const char *keyidstr, int hashalgo, - int (pincb)(void*, const char *, char **), - void *pincb_arg, - const void *indata, size_t indatalen, - unsigned char **outdata, size_t *outdatalen ) + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) { static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, @@ -397,7 +397,7 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, /* Select the DINSIG application on the card in SLOT. This function must be used before any other DINSIG application functions. */ -int +gpg_error_t app_select_dinsig (APP app) { static char const aid[] = { 0xD2, 0x76, 0x00, 0x00, 0x66, 0x01 }; diff --git a/scd/app-nks.c b/scd/app-nks.c index f14b67972..b6a3037ed 100644 --- a/scd/app-nks.c +++ b/scd/app-nks.c @@ -117,7 +117,7 @@ keygripstr_from_pk_file (int slot, int fid, char *r_gripstr) -static int +static gpg_error_t do_learn_status (APP app, CTRL ctrl) { gpg_error_t err; @@ -175,7 +175,7 @@ do_learn_status (APP app, CTRL ctrl) the CERTINFO status lines) and return it in the freshly allocated buffer put into CERT and the length of the certificate put into CERTLEN. */ -static int +static gpg_error_t do_readcert (app_t app, const char *certid, unsigned char **cert, size_t *certlen) { @@ -299,9 +299,9 @@ do_readcert (app_t app, const char *certid, /* Verify the PIN if required. */ -static int +static gpg_error_t verify_pin (app_t app, - int (pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { /* Note that force_chv1 is never set but we do it here anyway so @@ -357,12 +357,12 @@ verify_pin (app_t app, If a PIN is required the PINCB will be used to ask for the PIN; that callback should return the PIN in an allocated buffer and store that in the 3rd argument. */ -static int +static gpg_error_t do_sign (app_t app, const char *keyidstr, int hashalgo, - int (pincb)(void*, const char *, char **), - void *pincb_arg, - const void *indata, size_t indatalen, - unsigned char **outdata, size_t *outdatalen ) + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) { static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, @@ -435,9 +435,9 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, /* Decrypt the data in INDATA and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ -static int +static gpg_error_t do_decipher (app_t app, const char *keyidstr, - int (pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) @@ -485,7 +485,7 @@ do_decipher (app_t app, const char *keyidstr, /* Select the NKS 2.0 application on the card in SLOT. */ -int +gpg_error_t app_select_nks (APP app) { static char const aid[] = { 0xD2, 0x76, 0x00, 0x00, 0x03, 0x01, 0x02 }; diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index ca0e2501b..b8060df03 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -547,7 +547,7 @@ parse_login_data (app_t app) } /* Note, that FPR must be at least 20 bytes. */ -static int +static gpg_error_t store_fpr (int slot, int keynumber, u32 timestamp, const unsigned char *m, size_t mlen, const unsigned char *e, size_t elen, @@ -671,7 +671,7 @@ send_key_data (ctrl_t ctrl, const char *name, /* Implement the GETATTR command. This is similar to the LEARN command but returns just one value via the status interface. */ -static int +static gpg_error_t do_getattr (app_t app, ctrl_t ctrl, const char *name) { static struct { @@ -1168,7 +1168,7 @@ send_keypair_info (app_t app, ctrl_t ctrl, int keyno) /* Handle the LEARN command for OpenPGP. */ -static int +static gpg_error_t do_learn_status (app_t app, ctrl_t ctrl) { do_getattr (app, ctrl, "EXTCAP"); @@ -1204,7 +1204,7 @@ do_learn_status (app_t app, ctrl_t ctrl) its length (for assertions) at PKLEN; the caller must release that buffer. On error PK and PKLEN are not changed and an error code is returned. */ -static int +static gpg_error_t do_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen) { gpg_error_t err; @@ -1236,9 +1236,9 @@ do_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen) /* Verify CHV2 if required. Depending on the configuration of the card CHV1 will also be verified. */ -static int +static gpg_error_t verify_chv2 (app_t app, - int (*pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { int rc = 0; @@ -1292,9 +1292,9 @@ verify_chv2 (app_t app, } /* Verify CHV3 if required. */ -static int +static gpg_error_t verify_chv3 (app_t app, - int (*pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { int rc = 0; @@ -1366,9 +1366,9 @@ verify_chv3 (app_t app, /* Handle the SETATTR operation. All arguments are already basically checked. */ -static int +static gpg_error_t do_setattr (app_t app, const char *name, - int (*pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *value, size_t valuelen) { @@ -1434,9 +1434,9 @@ do_setattr (app_t app, const char *name, /* Handle the PASSWD command. */ -static int +static gpg_error_t do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, - int (*pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { int rc = 0; @@ -1525,9 +1525,9 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, /* Handle the GENKEY command. */ -static int +static gpg_error_t do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, - int (*pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { int rc; @@ -1691,7 +1691,7 @@ get_sig_counter (app_t app) return ul; } -static int +static gpg_error_t compare_fingerprint (app_t app, int keyno, unsigned char *sha1fpr) { const unsigned char *fpr; @@ -1731,7 +1731,7 @@ compare_fingerprint (app_t app, int keyno, unsigned char *sha1fpr) the key on the card has been replaced but the shadow information known to gpg was not updated. If there is no fingerprint we assume that this is okay. */ -static int +static gpg_error_t check_against_given_fingerprint (app_t app, const char *fpr, int keyno) { unsigned char tmp[20]; @@ -1762,9 +1762,9 @@ check_against_given_fingerprint (app_t app, const char *fpr, int keyno) GPG_ERR_WRONG_CARD to indicate that the card currently present does not match the one required for the requested action (e.g. the serial number does not match). */ -static int +static gpg_error_t do_sign (app_t app, const char *keyidstr, int hashalgo, - int (*pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) @@ -1911,9 +1911,9 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, GPG_ERR_WRONG_CARD to indicate that the card currently present does not match the one required for the requested action (e.g. the serial number does not match). */ -static int +static gpg_error_t do_auth (app_t app, const char *keyidstr, - int (*pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) @@ -1974,9 +1974,9 @@ do_auth (app_t app, const char *keyidstr, } -static int +static gpg_error_t do_decipher (app_t app, const char *keyidstr, - int (pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) @@ -2040,9 +2040,9 @@ do_decipher (app_t app, const char *keyidstr, There is a special mode if the keyidstr is "[CHV3]" with the "[CHV3]" being a literal string: The Admin Pin is checked if and only if the retry counter is still at 3. */ -static int +static gpg_error_t do_check_pin (app_t app, const char *keyidstr, - int (pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { unsigned char tmp_sn[20]; @@ -2124,7 +2124,7 @@ do_check_pin (app_t app, const char *keyidstr, /* Select the OpenPGP application on the card in SLOT. This function must be used before any other OpenPGP application functions. */ -int +gpg_error_t app_select_openpgp (app_t app) { static char const aid[] = { 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01 }; @@ -2237,7 +2237,7 @@ leave: LEARN command returns. All parameters return allocated strings or buffers or NULL if the data object is not available. All returned values are sanitized. */ -int +gpg_error_t app_openpgp_cardinfo (app_t app, char **serialno, char **disp_name, @@ -2327,13 +2327,13 @@ app_openpgp_cardinfo (app_t app, create the fingerprint. M, MLEN is the RSA modulus and E, ELEN the RSA public exponent. This function silently overwrites an existing key.*/ -int +gpg_error_t app_openpgp_storekey (app_t app, int keyno, unsigned char *template, size_t template_len, time_t created_at, const unsigned char *m, size_t mlen, const unsigned char *e, size_t elen, - int (*pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { int rc; @@ -2377,7 +2377,7 @@ app_openpgp_storekey (app_t app, int keyno, /* Utility function for external tools: Read the public RSA key at KEYNO and return modulus and exponent in (M,MLEN) and (E,ELEN). */ -int +gpg_error_t app_openpgp_readkey (app_t app, int keyno, unsigned char **m, size_t *mlen, unsigned char **e, size_t *elen) { diff --git a/scd/app-p15.c b/scd/app-p15.c index d2ed15a59..831f0d1f4 100644 --- a/scd/app-p15.c +++ b/scd/app-p15.c @@ -2366,7 +2366,7 @@ send_keypairinfo (app_t app, ctrl_t ctrl, prkdf_object_t keyinfo) /* This is the handler for the LEARN command. */ -static int /* FIXME: change this to gpg_error_t */ +static gpg_error_t do_learn_status (app_t app, ctrl_t ctrl) { gpg_error_t err; @@ -2513,7 +2513,7 @@ readcert_by_cdf (app_t app, cdf_object_t cdf, buffer to be stored at R_CERT and its length at R_CERTLEN. A error code will be returned on failure and R_CERT and R_CERTLEN will be set to NULL/0. */ -static int /* FIXME: change this to gpg_error_t */ +static gpg_error_t do_readcert (app_t app, const char *certid, unsigned char **r_cert, size_t *r_certlen) { @@ -2629,9 +2629,9 @@ micardo_mse (app_t app, unsigned short fid) If a PIN is required, the PINCB will be used to ask for the PIN; that callback should return the PIN in an allocated buffer and store that as the 3rd argument. */ -static int +static gpg_error_t do_sign (app_t app, const char *keyidstr, int hashalgo, - int (pincb)(void*, const char *, char **), + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) diff --git a/scd/app.c b/scd/app.c index 8e95ef7ef..0a1960267 100644 --- a/scd/app.c +++ b/scd/app.c @@ -23,7 +23,7 @@ #include #include #include - +# include #include "scdaemon.h" #include "app-common.h" @@ -31,7 +31,72 @@ #include "iso7816.h" #include "tlv.h" +/* This table is used to keep track of locks on a per reader base. + The index into the table is the slot number of the reader. The + mutex will be initialized on demand (one of the advantages of a + userland threading system). */ +static struct +{ + int initialized; + pth_mutex_t lock; +} lock_table[10]; + + +/* Lock the reader associated with the APP context. This function + shall be used right before calling any of the actual application + functions to serialize access to the reader. We do this always + even if the reader is not actually used. This allows an actual + application to assume that it never shares a reader (while + performing one command). Returns 0 on success; only then the + unlock_reader function must be called after returning from the + handler. */ +static gpg_error_t +lock_reader (app_t app) +{ + gpg_error_t err; + int slot = app->slot; + + if (slot < 0 || slot >= DIM (lock_table)) + return gpg_error (app->slot<0? GPG_ERR_INV_VALUE : GPG_ERR_RESOURCE_LIMIT); + + if (!lock_table[slot].initialized) + { + if (!pth_mutex_init (&lock_table[slot].lock)) + { + err = gpg_error_from_errno (errno); + log_error ("error initializing mutex: %s\n", strerror (errno)); + return err; + } + lock_table[slot].initialized = 1; + } + + if (!pth_mutex_acquire (&lock_table[slot].lock, 0, NULL)) + { + err = gpg_error_from_errno (errno); + log_error ("failed to acquire APP lock for slot %d: %s\n", + slot, strerror (errno)); + return err; + } + + return 0; +} + +/* Release a lock on the reader. See lock_reader(). */ +static void +unlock_reader (app_t app) +{ + int slot = app->slot; + + if (slot < 0 || slot >= DIM (lock_table) + || !lock_table[slot].initialized) + log_bug ("unlock_reader called for invalid slot %d\n", slot); + if (!pth_mutex_release (&lock_table[slot].lock)) + log_error ("failed to release APP lock for slot %d: %s\n", + slot, strerror (errno)); + +} + /* Check wether the application NAME is allowed. This does not mean we have support for it though. */ static int @@ -54,7 +119,7 @@ is_app_allowed (const char *name) gpg_error_t select_application (ctrl_t ctrl, int slot, const char *name, app_t *r_app) { - int rc; + gpg_error_t err; app_t app; unsigned char *result = NULL; size_t resultlen; @@ -63,22 +128,26 @@ select_application (ctrl_t ctrl, int slot, const char *name, app_t *r_app) app = xtrycalloc (1, sizeof *app); if (!app) { - rc = gpg_error_from_errno (errno); - log_info ("error allocating context: %s\n", gpg_strerror (rc)); - return rc; + err = gpg_error_from_errno (errno); + log_info ("error allocating context: %s\n", gpg_strerror (err)); + return err; } app->slot = slot; + err = lock_reader (app); + if (err) + return err; + /* Fixme: We should now first check whether a card is at all present. */ /* Try to read the GDO file first to get a default serial number. */ - rc = iso7816_select_file (slot, 0x3F00, 1, NULL, NULL); - if (!rc) - rc = iso7816_select_file (slot, 0x2F02, 0, NULL, NULL); - if (!rc) - rc = iso7816_read_binary (slot, 0, 0, &result, &resultlen); - if (!rc) + err = iso7816_select_file (slot, 0x3F00, 1, NULL, NULL); + if (!err) + err = iso7816_select_file (slot, 0x2F02, 0, NULL, NULL); + if (!err) + err = iso7816_read_binary (slot, 0, 0, &result, &resultlen); + if (!err) { size_t n; const unsigned char *p; @@ -104,8 +173,8 @@ select_application (ctrl_t ctrl, int slot, const char *name, app_t *r_app) memmove (result, p, n); app->serialno = result; app->serialnolen = n; - rc = app_munge_serialno (app); - if (rc) + err = app_munge_serialno (app); + if (err) goto leave; } else @@ -114,38 +183,40 @@ select_application (ctrl_t ctrl, int slot, const char *name, app_t *r_app) } /* For certain error codes, there is no need to try more. */ - if (gpg_err_code (rc) == GPG_ERR_CARD_NOT_PRESENT) + if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT) goto leave; /* Figure out the application to use. */ - rc = gpg_error (GPG_ERR_NOT_FOUND); - - if (rc && is_app_allowed ("openpgp") && (!name || !strcmp (name, "openpgp"))) - rc = app_select_openpgp (app); - if (rc && is_app_allowed ("nks") && (!name || !strcmp (name, "nks"))) - rc = app_select_nks (app); - if (rc && is_app_allowed ("p15") && (!name || !strcmp (name, "p15"))) - rc = app_select_p15 (app); - if (rc && is_app_allowed ("dinsig") && (!name || !strcmp (name, "dinsig"))) - rc = app_select_dinsig (app); - if (rc && name) - rc = gpg_error (GPG_ERR_NOT_SUPPORTED); + err = gpg_error (GPG_ERR_NOT_FOUND); + + if (err && is_app_allowed ("openpgp") + && (!name || !strcmp (name, "openpgp"))) + err = app_select_openpgp (app); + if (err && is_app_allowed ("nks") && (!name || !strcmp (name, "nks"))) + err = app_select_nks (app); + if (err && is_app_allowed ("p15") && (!name || !strcmp (name, "p15"))) + err = app_select_p15 (app); + if (err && is_app_allowed ("dinsig") && (!name || !strcmp (name, "dinsig"))) + err = app_select_dinsig (app); + if (err && name) + err = gpg_error (GPG_ERR_NOT_SUPPORTED); leave: - if (rc) + if (err) { if (name) log_info ("can't select application `%s': %s\n", - name, gpg_strerror (rc)); + name, gpg_strerror (err)); else log_info ("no supported card application found: %s\n", - gpg_strerror (rc)); + gpg_strerror (err)); xfree (app); - return rc; + return err; } app->initialized = 1; + unlock_reader (app); *r_app = app; return 0; } @@ -181,7 +252,7 @@ release_application (app_t app) All other serial number not starting with FF are used as they are. */ -int +gpg_error_t app_munge_serialno (app_t app) { if (app->serialnolen && app->serialno[0] == 0xff) @@ -208,7 +279,7 @@ app_munge_serialno (app_t app) no update time is available the returned value is 0. Caller must free SERIAL unless the function returns an error. If STAMP is not of interest, NULL may be passed. */ -int +gpg_error_t app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp) { unsigned char *buf, *p; @@ -234,9 +305,11 @@ app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp) /* Write out the application specifig status lines for the LEARN command. */ -int -app_write_learn_status (APP app, CTRL ctrl) +gpg_error_t +app_write_learn_status (app_t app, CTRL ctrl) { + gpg_error_t err; + if (!app) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) @@ -247,8 +320,12 @@ app_write_learn_status (APP app, CTRL ctrl) if (app->apptype) send_status_info (ctrl, "APPTYPE", app->apptype, strlen (app->apptype), NULL, 0); - - return app->fnc.learn_status (app, ctrl); + err = lock_reader (app); + if (err) + return err; + err = app->fnc.learn_status (app, ctrl); + unlock_reader (app); + return err; } @@ -256,18 +333,24 @@ app_write_learn_status (APP app, CTRL ctrl) the CERTINFO status lines) and return it in the freshly allocated buffer put into CERT and the length of the certificate put into CERTLEN. */ -int +gpg_error_t app_readcert (app_t app, const char *certid, unsigned char **cert, size_t *certlen) { + gpg_error_t err; + if (!app) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.readcert) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - - return app->fnc.readcert (app, certid, cert, certlen); + err = lock_reader (app); + if (err) + return err; + err = app->fnc.readcert (app, certid, cert, certlen); + unlock_reader (app); + return err; } @@ -278,9 +361,11 @@ app_readcert (app_t app, const char *certid, code returned. This function might not be supported by all applications. */ -int +gpg_error_t app_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen) { + gpg_error_t err; + if (pk) *pk = NULL; if (pklen) @@ -292,15 +377,21 @@ app_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.readkey) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - - return app->fnc.readkey (app, keyid, pk, pklen); + err = lock_reader (app); + if (err) + return err; + err= app->fnc.readkey (app, keyid, pk, pklen); + unlock_reader (app); + return err; } /* Perform a GETATTR operation. */ -int -app_getattr (APP app, CTRL ctrl, const char *name) +gpg_error_t +app_getattr (app_t app, CTRL ctrl, const char *name) { + gpg_error_t err; + if (!app || !name || !*name) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) @@ -328,36 +419,48 @@ app_getattr (APP app, CTRL ctrl, const char *name) if (!app->fnc.getattr) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - return app->fnc.getattr (app, ctrl, name); + err = lock_reader (app); + if (err) + return err; + err = app->fnc.getattr (app, ctrl, name); + unlock_reader (app); + return err; } /* Perform a SETATTR operation. */ -int -app_setattr (APP app, const char *name, - int (*pincb)(void*, const char *, char **), +gpg_error_t +app_setattr (app_t app, const char *name, + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *value, size_t valuelen) { + gpg_error_t err; + if (!app || !name || !*name || !value) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.setattr) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - return app->fnc.setattr (app, name, pincb, pincb_arg, value, valuelen); + err = lock_reader (app); + if (err) + return err; + err = app->fnc.setattr (app, name, pincb, pincb_arg, value, valuelen); + unlock_reader (app); + return err; } /* Create the signature and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ -int -app_sign (APP app, const char *keyidstr, int hashalgo, - int (pincb)(void*, const char *, char **), +gpg_error_t +app_sign (app_t app, const char *keyidstr, int hashalgo, + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { - int rc; + gpg_error_t err; if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) return gpg_error (GPG_ERR_INV_VALUE); @@ -365,27 +468,31 @@ app_sign (APP app, const char *keyidstr, int hashalgo, return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.sign) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - rc = app->fnc.sign (app, keyidstr, hashalgo, - pincb, pincb_arg, - indata, indatalen, - outdata, outdatalen); + err = lock_reader (app); + if (err) + return err; + err = app->fnc.sign (app, keyidstr, hashalgo, + pincb, pincb_arg, + indata, indatalen, + outdata, outdatalen); + unlock_reader (app); if (opt.verbose) - log_info ("operation sign result: %s\n", gpg_strerror (rc)); - return rc; + log_info ("operation sign result: %s\n", gpg_strerror (err)); + return err; } /* Create the signature using the INTERNAL AUTHENTICATE command and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ -int -app_auth (APP app, const char *keyidstr, - int (pincb)(void*, const char *, char **), +gpg_error_t +app_auth (app_t app, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { - int rc; + gpg_error_t err; if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) return gpg_error (GPG_ERR_INV_VALUE); @@ -393,27 +500,31 @@ app_auth (APP app, const char *keyidstr, return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.auth) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - rc = app->fnc.auth (app, keyidstr, - pincb, pincb_arg, - indata, indatalen, - outdata, outdatalen); + err = lock_reader (app); + if (err) + return err; + err = app->fnc.auth (app, keyidstr, + pincb, pincb_arg, + indata, indatalen, + outdata, outdatalen); + unlock_reader (app); if (opt.verbose) - log_info ("operation auth result: %s\n", gpg_strerror (rc)); - return rc; + log_info ("operation auth result: %s\n", gpg_strerror (err)); + return err; } /* Decrypt the data in INDATA and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ -int -app_decipher (APP app, const char *keyidstr, - int (pincb)(void*, const char *, char **), +gpg_error_t +app_decipher (app_t app, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { - int rc; + gpg_error_t err; if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) return gpg_error (GPG_ERR_INV_VALUE); @@ -421,23 +532,27 @@ app_decipher (APP app, const char *keyidstr, return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.decipher) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - rc = app->fnc.decipher (app, keyidstr, - pincb, pincb_arg, - indata, indatalen, - outdata, outdatalen); + err = lock_reader (app); + if (err) + return err; + err = app->fnc.decipher (app, keyidstr, + pincb, pincb_arg, + indata, indatalen, + outdata, outdatalen); + unlock_reader (app); if (opt.verbose) - log_info ("operation decipher result: %s\n", gpg_strerror (rc)); - return rc; + log_info ("operation decipher result: %s\n", gpg_strerror (err)); + return err; } /* Perform a SETATTR operation. */ -int -app_genkey (APP app, CTRL ctrl, const char *keynostr, unsigned int flags, - int (*pincb)(void*, const char *, char **), +gpg_error_t +app_genkey (app_t app, CTRL ctrl, const char *keynostr, unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { - int rc; + gpg_error_t err; if (!app || !keynostr || !*keynostr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); @@ -445,35 +560,46 @@ app_genkey (APP app, CTRL ctrl, const char *keynostr, unsigned int flags, return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.genkey) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - rc = app->fnc.genkey (app, ctrl, keynostr, flags, pincb, pincb_arg); + err = lock_reader (app); + if (err) + return err; + err = app->fnc.genkey (app, ctrl, keynostr, flags, pincb, pincb_arg); + unlock_reader (app); if (opt.verbose) - log_info ("operation genkey result: %s\n", gpg_strerror (rc)); - return rc; + log_info ("operation genkey result: %s\n", gpg_strerror (err)); + return err; } /* Perform a GET CHALLENGE operation. This fucntion is special as it directly accesses the card without any application specific wrapper. */ -int -app_get_challenge (APP app, size_t nbytes, unsigned char *buffer) +gpg_error_t +app_get_challenge (app_t app, size_t nbytes, unsigned char *buffer) { + gpg_error_t err; + if (!app || !nbytes || !buffer) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); - return iso7816_get_challenge (app->slot, nbytes, buffer); + err = lock_reader (app); + if (err) + return err; + err = iso7816_get_challenge (app->slot, nbytes, buffer); + unlock_reader (app); + return err; } /* Perform a CHANGE REFERENCE DATA or RESET RETRY COUNTER operation. */ -int -app_change_pin (APP app, CTRL ctrl, const char *chvnostr, int reset_mode, - int (*pincb)(void*, const char *, char **), +gpg_error_t +app_change_pin (app_t app, CTRL ctrl, const char *chvnostr, int reset_mode, + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { - int rc; + gpg_error_t err; if (!app || !chvnostr || !*chvnostr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); @@ -481,22 +607,27 @@ app_change_pin (APP app, CTRL ctrl, const char *chvnostr, int reset_mode, return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.change_pin) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - rc = app->fnc.change_pin (app, ctrl, chvnostr, reset_mode, pincb, pincb_arg); + err = lock_reader (app); + if (err) + return err; + err = app->fnc.change_pin (app, ctrl, chvnostr, reset_mode, + pincb, pincb_arg); + unlock_reader (app); if (opt.verbose) - log_info ("operation change_pin result: %s\n", gpg_strerror (rc)); - return rc; + log_info ("operation change_pin result: %s\n", gpg_strerror (err)); + return err; } /* Perform a VERIFY operation without doing anything lese. This may be used to initialze a the PIN cache for long lasting other operations. Its use is highly application dependent. */ -int -app_check_pin (APP app, const char *keyidstr, - int (*pincb)(void*, const char *, char **), +gpg_error_t +app_check_pin (app_t app, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { - int rc; + gpg_error_t err; if (!app || !keyidstr || !*keyidstr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); @@ -504,9 +635,13 @@ app_check_pin (APP app, const char *keyidstr, return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.check_pin) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - rc = app->fnc.check_pin (app, keyidstr, pincb, pincb_arg); + err = lock_reader (app); + if (err) + return err; + err = app->fnc.check_pin (app, keyidstr, pincb, pincb_arg); + unlock_reader (app); if (opt.verbose) - log_info ("operation check_pin result: %s\n", gpg_strerror (rc)); - return rc; + log_info ("operation check_pin result: %s\n", gpg_strerror (err)); + return err; } diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c index e9666ee17..b817452b1 100644 --- a/scd/ccid-driver.c +++ b/scd/ccid-driver.c @@ -986,7 +986,8 @@ do_close_reader (ccid_driver_t handle) } if (handle->idev) { - usb_reset (handle->idev); + if (getenv ("GNUPG_CCID_DRIVER_RESET_BEFORE_CLOSE")) + usb_reset (handle->idev); usb_release_interface (handle->idev, handle->ifc_no); usb_close (handle->idev); handle->idev = NULL; @@ -1274,7 +1275,7 @@ ccid_poll (ccid_driver_t handle) } -/* Note that this function won't return the error codes NO_CARD or +/* Note that this fucntion won't return the error codes NO_CARD or CARD_INACTIVE */ int ccid_slot_status (ccid_driver_t handle, int *statusbits) @@ -1303,12 +1304,12 @@ ccid_slot_status (ccid_driver_t handle, int *statusbits) { if (!retries) { - fprintf (stderr, "CALLING USB_CLEAR_HALT\n"); + DEBUGOUT ("USB: CALLING USB_CLEAR_HALT\n"); usb_clear_halt (handle->idev, handle->ep_bulk_in); usb_clear_halt (handle->idev, handle->ep_bulk_out); } else - fprintf (stderr, "RETRYING AGIAN\n"); + DEBUGOUT ("USB: RETRYING bulk_in AGAIN\n"); retries++; goto retry; } diff --git a/scd/command.c b/scd/command.c index c8eebaac0..5ea3e01db 100644 --- a/scd/command.c +++ b/scd/command.c @@ -122,7 +122,7 @@ has_option (const char *line, const char *name) /* Reset the card and free the application context. With DO_CLOSE set - to true and this is the last session with a reference to teh + to true and this is the last session with a reference to the reader, close the reader and don't do just a reset. */ static void do_reset (ctrl_t ctrl, int do_close) @@ -647,7 +647,7 @@ cmd_setdata (assuan_context_t ctx, char *line) -static int +static gpg_error_t pin_cb (void *opaque, const char *info, char **retstr) { assuan_context_t ctx = opaque; @@ -1171,6 +1171,34 @@ cmd_unlock (assuan_context_t ctx, char *line) } +/* GETINFO + + Multi purpose command to return certain information. + Supported values of WHAT are: + + socket_name - Return the name of the socket. + +*/ + +static int +cmd_getinfo (assuan_context_t ctx, char *line) +{ + int rc = 0; + + if (!strcmp (line, "socket_name")) + { + const char *s = scd_get_socket_name (); + + if (s) + rc = assuan_send_data (ctx, s, strlen (s)); + else + rc = gpg_error (GPG_ERR_NO_DATA); + } + else + rc = set_error (Parameter_Error, "unknown value for WHAT"); + return rc; +} + @@ -1200,6 +1228,7 @@ register_commands (assuan_context_t ctx) { "CHECKPIN", cmd_checkpin }, { "LOCK", cmd_lock }, { "UNLOCK", cmd_unlock }, + { "GETINFO", cmd_getinfo }, { NULL } }; int i, rc; @@ -1218,10 +1247,10 @@ register_commands (assuan_context_t ctx) } -/* Startup the server. If LISTEN_FD is given as -1, this is simple - piper server, otherwise it is a regular server */ +/* Startup the server. If FD is given as -1 this is simple pipe + server, otherwise it is a regular server. */ void -scd_command_handler (int listen_fd) +scd_command_handler (int fd) { int rc; assuan_context_t ctx; @@ -1230,7 +1259,7 @@ scd_command_handler (int listen_fd) memset (&ctrl, 0, sizeof ctrl); scd_init_default_ctrl (&ctrl); - if (listen_fd == -1) + if (fd == -1) { int filedes[2]; @@ -1240,7 +1269,7 @@ scd_command_handler (int listen_fd) } else { - rc = assuan_init_socket_server (&ctx, listen_fd); + rc = assuan_init_connected_socket_server (&ctx, fd); } if (rc) { diff --git a/scd/sc-copykeys.c b/scd/sc-copykeys.c index 78cb2acc8..66b6894e0 100644 --- a/scd/sc-copykeys.c +++ b/scd/sc-copykeys.c @@ -483,7 +483,7 @@ query_card (APP app) /* Callback function to ask for a PIN. */ -static int +static gpg_error_t pincb (void *arg, const char *prompt, char **pinvalue) { char *pin = xstrdup ("12345678"); diff --git a/scd/scdaemon.c b/scd/scdaemon.c index 7b0f31cdb..9a8b31ac5 100644 --- a/scd/scdaemon.c +++ b/scd/scdaemon.c @@ -1,5 +1,5 @@ /* scdaemon.c - The GnuPG Smartcard Daemon - * Copyright (C) 2001, 2002, 2004 Free Software Foundation, Inc. + * Copyright (C) 2001, 2002, 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -35,9 +35,7 @@ #endif /*HAVE_W32_SYSTEM*/ #include #include -#ifdef USE_GNU_PTH -# include -#endif +#include #define JNLIB_NEED_LOG_LOGV #include "scdaemon.h" @@ -76,6 +74,7 @@ enum cmd_and_opt_values oNoGrab, oLogFile, oServer, + oMultiServer, oDaemon, oBatch, oReaderPort, @@ -110,6 +109,8 @@ static ARGPARSE_OPTS opts[] = { { oDebugWait,"debug-wait",1, "@"}, { oNoDetach, "no-detach" ,0, N_("do not detach from the console")}, { oLogFile, "log-file" ,2, N_("use a log file for the server")}, + { oMultiServer, "multi-server", 0, + N_("allow additional connections in server mode")}, { oReaderPort, "reader-port", 2, N_("|N|connect to reader at port N")}, { octapiDriver, "ctapi-driver", 2, N_("|NAME|use NAME as ct-API driver")}, { opcscDriver, "pcsc-driver", 2, N_("|NAME|use NAME as PC/SC driver")}, @@ -140,8 +141,6 @@ static ARGPARSE_OPTS opts[] = { #endif -static volatile int caught_fatal_sig = 0; - /* Flag to indicate that a shutdown was requested. */ static int shutdown_pending; @@ -149,16 +148,21 @@ static int shutdown_pending; static int maybe_setuid = 1; /* Name of the communication socket */ -static char socket_name[128]; +static char *socket_name; + + +static char *create_socket_name (int use_standard_socket, + char *standard_name, char *template); +static int create_server_socket (int is_standard_name, const char *name); +static void *start_connection_thread (void *arg); +static void handle_connections (int listen_fd); -#ifdef USE_GNU_PTH /* Pth wrapper function definitions. */ GCRY_THREAD_OPTION_PTH_IMPL; -static void *ticker_thread (void *arg); -#endif /*USE_GNU_PTH*/ + static const char * my_strusage (int level) { @@ -265,7 +269,7 @@ set_debug (const char *level) static void cleanup (void) { - if (*socket_name) + if (socket_name && *socket_name) { char *p; @@ -282,27 +286,6 @@ cleanup (void) } -static RETSIGTYPE -cleanup_sh (int sig) -{ - if (caught_fatal_sig) - raise (sig); - caught_fatal_sig = 1; - - /* gcry_control( GCRYCTL_TERM_SECMEM );*/ - cleanup (); - -#ifndef HAVE_DOSISH_SYSTEM - { /* reset action to default action and raise signal again */ - struct sigaction nact; - nact.sa_handler = SIG_DFL; - sigemptyset( &nact.sa_mask ); - nact.sa_flags = 0; - sigaction( sig, &nact, NULL); - } -#endif - raise( sig ); -} int main (int argc, char **argv ) @@ -322,6 +305,7 @@ main (int argc, char **argv ) int greeting = 0; int nogreeting = 0; int pipe_server = 0; + int multi_server = 0; int is_daemon = 0; int nodetach = 0; int csh_style = 0; @@ -343,14 +327,12 @@ main (int argc, char **argv ) /* Libgcrypt requires us to register the threading model first. Note that this will also do the pth_init. */ -#ifdef USE_GNU_PTH err = gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pth); if (err) { log_fatal ("can't register GNU Pth with Libgcrypt: %s\n", gpg_strerror (err)); } -#endif /*USE_GNU_PTH*/ /* Check that the libraries are suitable. Do it here because the option parsing may need services of the library */ @@ -481,6 +463,7 @@ main (int argc, char **argv ) case oCsh: csh_style = 1; break; case oSh: csh_style = 0; break; case oServer: pipe_server = 1; break; + case oMultiServer: multi_server = 1; break; case oDaemon: is_daemon = 1; break; case oReaderPort: opt.reader_port = pargs.r.ret_str; break; @@ -598,24 +581,49 @@ main (int argc, char **argv ) log_set_prefix (NULL, 1|2|4); } - if (pipe_server) - { /* This is the simple pipe based server */ -#ifdef USE_GNU_PTH + { + /* This is the simple pipe based server */ pth_attr_t tattr; - + int fd = -1; + + { + struct sigaction sa; + + sa.sa_handler = SIG_IGN; + sigemptyset (&sa.sa_mask); + sa.sa_flags = 0; + sigaction (SIGPIPE, &sa, NULL); + } + + /* In multi server mode we need to listen on an additional + socket. Create that socket now before starting the handler + for the pipe connection. This allows that handler to send + back the name of that socket. */ + if (multi_server) + { + socket_name = create_socket_name (0, + "S.scdaemon", + "/tmp/gpg-XXXXXX/S.scdaemon"); + + fd = create_server_socket (0, socket_name); + } + tattr = pth_attr_new(); pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0); pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 512*1024); - pth_attr_set (tattr, PTH_ATTR_NAME, "ticker"); + pth_attr_set (tattr, PTH_ATTR_NAME, "pipe-connection"); - if (!pth_spawn (tattr, ticker_thread, NULL)) + if (!pth_spawn (tattr, start_connection_thread, (void*)(-1))) { - log_error ("error spawning ticker thread: %s\n", strerror (errno)); + log_error ("error spawning pipe connection handler: %s\n", + strerror (errno) ); scd_exit (2); } -#endif /*USE_GNU_PTH*/ - scd_command_handler (-1); + + handle_connections (fd); + if (fd != -1) + close (fd); } else if (!is_daemon) { @@ -623,87 +631,17 @@ main (int argc, char **argv ) " to run the program in the background\n")); } else - { /* regular server mode */ + { /* Regular server mode */ int fd; pid_t pid; int i; - int len; - struct sockaddr_un serv_addr; - char *p; - - /* fixme: if there is already a running gpg-agent we should - share the same directory - and vice versa */ - *socket_name = 0; - snprintf (socket_name, DIM(socket_name)-1, - "/tmp/gpg-XXXXXX/S.scdaemon"); - socket_name[DIM(socket_name)-1] = 0; - p = strrchr (socket_name, '/'); - if (!p) - BUG (); - *p = 0;; - -#ifndef HAVE_W32_SYSTEM - if (!mkdtemp(socket_name)) - { - log_error ("can't create directory `%s': %s\n", - socket_name, strerror(errno) ); - exit (1); - } -#endif - *p = '/'; - - if (strchr (socket_name, ':') ) - { - log_error ("colons are not allowed in the socket name\n"); - exit (1); - } - if (strlen (socket_name)+1 >= sizeof serv_addr.sun_path ) - { - log_error ("name of socket to long\n"); - exit (1); - } - - -#ifdef HAVE_W32_SYSTEM - fd = _w32_sock_new (AF_UNIX, SOCK_STREAM, 0); -#else - fd = socket (AF_UNIX, SOCK_STREAM, 0); -#endif - if (fd == -1) - { - log_error ("can't create socket: %s\n", strerror(errno) ); - exit (1); - } - - memset (&serv_addr, 0, sizeof serv_addr); - serv_addr.sun_family = AF_UNIX; - strcpy (serv_addr.sun_path, socket_name); - len = (offsetof (struct sockaddr_un, sun_path) - + strlen(serv_addr.sun_path) + 1); - if ( -#ifdef HAVE_W32_SYSTEM - _w32_sock_bind -#else - bind -#endif - (fd, (struct sockaddr*)&serv_addr, len) == -1) - { - log_error ("error binding socket to `%s': %s\n", - serv_addr.sun_path, strerror (errno) ); - close (fd); - exit (1); - } - - if (listen (fd, 5 ) == -1) - { - log_error ("listen() failed: %s\n", strerror (errno)); - close (fd); - exit (1); - } + /* Create the socket. */ + socket_name = create_socket_name (0, + "S.scdaemon", + "/tmp/gpg-XXXXXX/S.scdaemon"); - if (opt.verbose) - log_info ("listening on socket `%s'\n", socket_name ); + fd = create_server_socket (0, socket_name); fflush (NULL); @@ -746,7 +684,7 @@ main (int argc, char **argv ) } else { - /* print the environment string, so that the caller can use + /* Print the environment string, so that the caller can use shell's eval to set it */ if (csh_style) { @@ -763,14 +701,15 @@ main (int argc, char **argv ) /* NOTREACHED */ } /* end parent */ - /* this is the child */ + /* This is the child. */ - /* detach from tty and put process into a new session */ + /* Detach from tty and put process into a new session. */ if (!nodetach ) - { /* close stdin, stdout and stderr unless it is the log stream */ + { + /* Close stdin, stdout and stderr unless it is the log stream. */ for (i=0; i <= 2; i++) { - if ( log_get_fd () != i) + if ( log_test_fd (i) && i != fd) close (i); } if (setsid() == -1) @@ -781,23 +720,13 @@ main (int argc, char **argv ) } } - /* setup signals */ { - struct sigaction oact, nact; + struct sigaction sa; - nact.sa_handler = cleanup_sh; - sigemptyset (&nact.sa_mask); - nact.sa_flags = 0; - - sigaction (SIGHUP, NULL, &oact); - if (oact.sa_handler != SIG_IGN) - sigaction (SIGHUP, &nact, NULL); - sigaction( SIGTERM, NULL, &oact ); - if (oact.sa_handler != SIG_IGN) - sigaction (SIGTERM, &nact, NULL); - nact.sa_handler = SIG_IGN; - sigaction (SIGPIPE, &nact, NULL); - sigaction (SIGINT, &nact, NULL); + sa.sa_handler = SIG_IGN; + sigemptyset (&sa.sa_mask); + sa.sa_flags = 0; + sigaction (SIGPIPE, &sa, NULL); } if (chdir("/")) @@ -808,7 +737,7 @@ main (int argc, char **argv ) #endif /*!HAVE_W32_SYSTEM*/ - scd_command_handler (fd); + handle_connections (fd); close (fd); } @@ -840,13 +769,22 @@ scd_exit (int rc) void -scd_init_default_ctrl (CTRL ctrl) +scd_init_default_ctrl (ctrl_t ctrl) { ctrl->reader_slot = -1; } -#ifdef USE_GNU_PTH +/* Return the name of the socket to be used to connect to this + process. If no socket is available, return NULL. */ +const char * +scd_get_socket_name () +{ + if (socket_name && *socket_name) + return socket_name; + return NULL; +} + static void handle_signal (int signo) @@ -897,18 +835,175 @@ handle_signal (int signo) } } + static void handle_tick (void) { scd_update_reader_status_file (); } + +/* Create a name for the socket. With USE_STANDARD_SOCKET given as + true using STANDARD_NAME in the home directory or if given has + false from the mkdir type name TEMPLATE. In the latter case a + unique name in a unique new directory will be created. In both + cases check for valid characters as well as against a maximum + allowed length for a unix domain socket is done. The function + terminates the process in case of an error. Retunrs: Pointer to an + allcoated string with the absolute name of the socket used. */ +static char * +create_socket_name (int use_standard_socket, + char *standard_name, char *template) +{ + char *name, *p; + + if (use_standard_socket) + name = make_filename (opt.homedir, standard_name, NULL); + else + { + name = xstrdup (template); + p = strrchr (name, '/'); + if (!p) + BUG (); + *p = 0; + if (!mkdtemp (name)) + { + log_error (_("can't create directory `%s': %s\n"), + name, strerror (errno)); + scd_exit (2); + } + *p = '/'; + } + + if (strchr (name, PATHSEP_C)) + { + log_error (("`%s' are not allowed in the socket name\n"), PATHSEP_S); + scd_exit (2); + } + if (strlen (name) + 1 >= DIMof (struct sockaddr_un, sun_path) ) + { + log_error (_("name of socket too long\n")); + scd_exit (2); + } + return name; +} + + + +/* Create a Unix domain socket with NAME. IS_STANDARD_NAME indicates + whether a non-random socket is used. Returns the file descriptor + or terminates the process in case of an error. */ +static int +create_server_socket (int is_standard_name, const char *name) +{ + struct sockaddr_un *serv_addr; + socklen_t len; + int fd; + int rc; + +#ifdef HAVE_W32_SYSTEM + fd = _w32_sock_new (AF_UNIX, SOCK_STREAM, 0); +#else + fd = socket (AF_UNIX, SOCK_STREAM, 0); +#endif + if (fd == -1) + { + log_error (_("can't create socket: %s\n"), strerror (errno)); + scd_exit (2); + } + + serv_addr = xmalloc (sizeof (*serv_addr)); + memset (serv_addr, 0, sizeof *serv_addr); + serv_addr->sun_family = AF_UNIX; + assert (strlen (name) + 1 < sizeof (serv_addr->sun_path)); + strcpy (serv_addr->sun_path, name); + len = (offsetof (struct sockaddr_un, sun_path) + + strlen (serv_addr->sun_path) + 1); + +#ifdef HAVE_W32_SYSTEM + rc = _w32_sock_bind (fd, (struct sockaddr*) serv_addr, len); + if (is_standard_name && rc == -1 ) + { + remove (name); + rc = bind (fd, (struct sockaddr*) serv_addr, len); + } +#else + rc = bind (fd, (struct sockaddr*) serv_addr, len); + if (is_standard_name && rc == -1 && errno == EADDRINUSE) + { + remove (name); + rc = bind (fd, (struct sockaddr*) serv_addr, len); + } +#endif + if (rc == -1) + { + log_error (_("error binding socket to `%s': %s\n"), + serv_addr->sun_path, strerror (errno)); + close (fd); + scd_exit (2); + } + + if (listen (fd, 5 ) == -1) + { + log_error (_("listen() failed: %s\n"), strerror (errno)); + close (fd); + scd_exit (2); + } + + if (opt.verbose) + log_info (_("listening on socket `%s'\n"), serv_addr->sun_path); + + return fd; +} + + + +/* This is the standard connection thread's main function. */ static void * -ticker_thread (void *dummy_arg) +start_connection_thread (void *arg) { - pth_event_t sigs_ev, time_ev = NULL; + int fd = (int)arg; + + if (opt.verbose) + log_info (_("handler for fd %d started\n"), fd); + + scd_command_handler (fd); + + if (opt.verbose) + log_info (_("handler for fd %d terminated\n"), fd); + + /* If this thread is the pipe connection thread, flag that a + shutdown is required. With the next ticker event and given that + no other connections are running the shutdown will then + happen. */ + if (fd == -1) + shutdown_pending = 1; + + return NULL; +} + + +/* Connection handler loop. Wait for connection requests and spawn a + thread after accepting a connection. LISTEN_FD is allowed to be -1 + in which case this code will only do regular timeouts and handle + signals. */ +static void +handle_connections (int listen_fd) +{ + pth_attr_t tattr; + pth_event_t ev, time_ev; sigset_t sigs; int signo; + struct sockaddr_un paddr; + socklen_t plen; + fd_set fdset, read_fdset; + int ret; + int fd; + + tattr = pth_attr_new(); + pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0); + pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 512*1024); + pth_attr_set (tattr, PTH_ATTR_NAME, "scd-connections"); #ifndef HAVE_W32_SYSTEM /* fixme */ sigemptyset (&sigs ); @@ -917,43 +1012,101 @@ ticker_thread (void *dummy_arg) sigaddset (&sigs, SIGUSR2); sigaddset (&sigs, SIGINT); sigaddset (&sigs, SIGTERM); - sigs_ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); + ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); #else - sigs_ev = NULL; + ev = NULL; #endif - - while (!shutdown_pending) + time_ev = NULL; + + FD_ZERO (&fdset); + if (listen_fd != -1) + FD_SET (listen_fd, &fdset); + + for (;;) { - if (!time_ev) + if (shutdown_pending) { - time_ev = pth_event (PTH_EVENT_TIME, pth_timeout (2, 0)); - if (time_ev) - pth_event_concat (sigs_ev, time_ev, NULL); - } + if (pth_ctrl (PTH_CTRL_GETTHREADS) == 1) + break; /* ready */ + + /* Do not accept anymore connections but wait for existing + connections to terminate. We do this by clearing out all + file descriptors to wait for, so that the select will be + used to just wait on a signal or timeout event. */ + FD_ZERO (&fdset); + } - if (pth_wait (sigs_ev) < 1) - continue; + /* Create a timeout event if needed. */ + if (!time_ev) + time_ev = pth_event (PTH_EVENT_TIME, pth_timeout (2, 0)); + + /* POSIX says that fd_set should be implemented as a structure, + thus a simple assignment is fine to copy the entire set. */ + read_fdset = fdset; + + if (time_ev) + pth_event_concat (ev, time_ev, NULL); + ret = pth_select_ev (FD_SETSIZE, &read_fdset, NULL, NULL, NULL, ev); + if (time_ev) + pth_event_isolate (time_ev); + + if (ret == -1) + { + if (pth_event_occurred (ev) + || (time_ev && pth_event_occurred (time_ev))) + { + if (pth_event_occurred (ev)) + handle_signal (signo); + if (time_ev && pth_event_occurred (time_ev)) + { + pth_event_free (time_ev, PTH_FREE_ALL); + time_ev = NULL; + handle_tick (); + } + continue; + } + log_error (_("pth_select failed: %s - waiting 1s\n"), + strerror (errno)); + pth_sleep (1); + continue; + } - if ( -#ifdef PTH_STATUS_OCCURRED /* This is Pth 2 */ - pth_event_status (sigs_ev) == PTH_STATUS_OCCURRED -#else - pth_event_occurred (sigs_ev) -#endif - ) - handle_signal (signo); + if (pth_event_occurred (ev)) + { + handle_signal (signo); + } - /* Always run the ticker. */ - if (!shutdown_pending) + if (time_ev && pth_event_occurred (time_ev)) { - pth_event_isolate (sigs_ev); pth_event_free (time_ev, PTH_FREE_ALL); time_ev = NULL; handle_tick (); } + + if (listen_fd != -1 && FD_ISSET (listen_fd, &read_fdset)) + { + plen = sizeof paddr; + fd = pth_accept (listen_fd, (struct sockaddr *)&paddr, &plen); + if (fd == -1) + { + log_error ("accept failed: %s\n", strerror (errno)); + } + else if (!pth_spawn (tattr, start_connection_thread, (void*)fd)) + { + log_error ("error spawning connection handler: %s\n", + strerror (errno) ); + close (fd); + } + fd = -1; + } + } - pth_event_free (sigs_ev, PTH_FREE_ALL); - return NULL; + pth_event_free (ev, PTH_FREE_ALL); + if (time_ev) + pth_event_free (time_ev, PTH_FREE_ALL); + cleanup (); + log_info (_("%s %s stopped\n"), strusage(11), strusage(13)); } -#endif /*USE_GNU_PTH*/ + + diff --git a/scd/scdaemon.h b/scd/scdaemon.h index 5e49f3ae5..eaa9abd35 100644 --- a/scd/scdaemon.h +++ b/scd/scdaemon.h @@ -99,7 +99,8 @@ typedef struct app_ctx_s *app_t; /*-- scdaemon.c --*/ void scd_exit (int rc); -void scd_init_default_ctrl (CTRL ctrl); +void scd_init_default_ctrl (ctrl_t ctrl); +const char *scd_get_socket_name (void); /*-- command.c --*/ void scd_command_handler (int); diff --git a/tools/gpg-connect-agent.c b/tools/gpg-connect-agent.c index 6372954f6..403fa2c45 100644 --- a/tools/gpg-connect-agent.c +++ b/tools/gpg-connect-agent.c @@ -39,6 +39,7 @@ enum cmd_and_opt_values aNull = 0, oQuiet = 'q', oVerbose = 'v', + oRawSocket = 'S', oNoVerbose = 500, oHomedir, @@ -55,6 +56,7 @@ static ARGPARSE_OPTS opts[] = { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("quiet") }, { oHex, "hex", 0, N_("print data out hex encoded") }, + { oRawSocket, "raw-socket", 2, N_("|NAME|connect to Assuan socket NAME")}, /* hidden options */ { oNoVerbose, "no-verbose", 0, "@"}, @@ -70,6 +72,7 @@ struct int quiet; /* Be extra quiet. */ const char *homedir; /* Configuration directory name */ int hex; /* Print data lines in hex format. */ + const char *raw_socket; /* Name of socket to connect in raw mode. */ } opt; @@ -159,6 +162,7 @@ main (int argc, char **argv) case oNoVerbose: opt.verbose = 0; break; case oHomedir: opt.homedir = pargs.r.ret_str; break; case oHex: opt.hex = 1; break; + case oRawSocket: opt.raw_socket = pargs.r.ret_str; break; default: pargs.err = 2; break; } @@ -169,7 +173,21 @@ main (int argc, char **argv) fname = argc ? *argv : NULL; - ctx = start_agent (); + if (opt.raw_socket) + { + rc = assuan_socket_connect (&ctx, opt.raw_socket, 0); + if (rc) + { + log_error ("can't connect to socket `%s': %s\n", + opt.raw_socket, assuan_strerror (rc)); + exit (1); + } + + if (opt.verbose) + log_info ("connection to socket `%s' established\n", opt.raw_socket); + } + else + ctx = start_agent (); line = NULL; linesize = 0; for (;;) -- cgit From f1dac8851d02a0cb63fc7379ee74692856d0cf39 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 3 Jun 2005 13:57:24 +0000 Subject: * command.c (cmd_updatestartuptty): New. * gpg-agent.c: New option --write-env-file. * gpg-agent.c (handle_connections): Make sure that the signals we are handling are not blocked.Block signals while creating new threads. * estream.c: Use HAVE_CONFIG_H and not USE_CONFIG_H! (es_func_fd_read, es_func_fd_write): Protect against EINTR. * gpg-agent.texi (Agent UPDATESTARTUPTTY): New. * scdaemon.c (handle_connections): Make sure that the signals we are handling are not blocked.Block signals while creating new threads. (handle_connections): Include the file descriptor into the name of the thread. --- ChangeLog | 5 ++ NEWS | 3 + TODO | 7 +++ agent/ChangeLog | 17 ++++++ agent/agent.h | 14 +++-- agent/call-scd.c | 30 ++++++++++ agent/command.c | 34 +++++++++++ agent/gpg-agent.c | 104 ++++++++++++++++++++++++++------- common/ChangeLog | 6 ++ common/estream.c | 10 +++- configure.ac | 3 + doc/ChangeLog | 4 ++ doc/gpg-agent.texi | 60 +++++++++++++++---- scd/ChangeLog | 29 ++++++++++ scd/app-common.h | 6 ++ scd/app-openpgp.c | 2 +- scd/app.c | 167 ++++++++++++++++++++++++++++++++++++++--------------- scd/command.c | 6 +- scd/scdaemon.c | 33 ++++++++--- scd/scdaemon.h | 23 +++++--- 20 files changed, 459 insertions(+), 104 deletions(-) (limited to 'agent/command.c') diff --git a/ChangeLog b/ChangeLog index fbd9ad79d..f7efcee89 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2005-06-02 Werner Koch + + * configure.ac (HAVE_PTH): Define as alias for USE_GNU_PTH. It is + used by common/estream.c. + 2005-06-01 Werner Koch * configure.ac (gl_INIT): Add gnulib stuff. diff --git a/NEWS b/NEWS index 79a74cbe4..e28f1284a 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,9 @@ Noteworthy changes in version 1.9.17 does allows only signing using TCOS cards but we are going to enhance it to match all the old capabilities. + * [gpg-agent] New option --rite-env-file and Assuan command + UPDATESTARTUPTTY. + Noteworthy changes in version 1.9.16 (2005-04-21) ------------------------------------------------- diff --git a/TODO b/TODO index 74763a71f..5f1b57a0f 100644 --- a/TODO +++ b/TODO @@ -72,6 +72,13 @@ might want to have an agent context for each service request we can change all S-expression handling code to make use of this function. +* scd +** Application context vs. reader slot + We have 2 concurrent method of tracking whether a read is in use: + Using the session_list in command.c and the lock_table in app.c. IT + would be better to do this just at one place. First we need to see + how we can support cards with multiple applications. + * tests ** Makefile.am We use printf(1) to setup the library path, this is not portable. diff --git a/agent/ChangeLog b/agent/ChangeLog index 9c57ad43e..9621e5de0 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,20 @@ +2005-06-03 Werner Koch + + * command.c (cmd_updatestartuptty): New. + + * gpg-agent.c: New option --write-env-file. + + * gpg-agent.c (handle_connections): Make sure that the signals we + are handling are not blocked.Block signals while creating new + threads. + +2005-06-02 Werner Koch + + * call-scd.c (agent_scd_dump_state, dump_mutex_state): New. + * gpg-agent.c (handle_signal): Print it on SIGUSR1. + (handle_connections): Include the file descriptor into the + threadnames. + 2005-06-01 Werner Koch * gpg-agent.c: Include setenv.h. diff --git a/agent/agent.h b/agent/agent.h index a667c0d46..51e66abee 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -54,12 +54,13 @@ struct { int batch; /* Batch mode */ const char *homedir; /* Configuration directory name */ - /* Environment setting gathred at program start. */ - const char *startup_display; - const char *startup_ttyname; - const char *startup_ttytype; - const char *startup_lc_ctype; - const char *startup_lc_messages; + /* Environment setting gathered at program start or hanged using the + Assuan command UPDATESTARTUPTTY. */ + char *startup_display; + char *startup_ttyname; + char *startup_ttytype; + char *startup_lc_ctype; + char *startup_lc_messages; const char *pinentry_program; /* Filename of the program to start as @@ -248,6 +249,7 @@ int divert_generic_cmd (ctrl_t ctrl, /*-- call-scd.c --*/ void initialize_module_call_scd (void); +void agent_scd_dump_state (void); void agent_scd_check_aliveness (void); int agent_reset_scd (ctrl_t ctrl); int agent_card_learn (ctrl_t ctrl, diff --git a/agent/call-scd.c b/agent/call-scd.c index 78e28fe97..00c9df2a7 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -116,6 +116,35 @@ initialize_module_call_scd (void) } +static void +dump_mutex_state (pth_mutex_t *m) +{ + if (!(m->mx_state & PTH_MUTEX_INITIALIZED)) + log_printf ("not_initialized"); + else if (!(m->mx_state & PTH_MUTEX_LOCKED)) + log_printf ("not_locked"); + else + log_printf ("locked tid=0x%lx count=%lu", (long)m->mx_owner, m->mx_count); +} + + +/* This function may be called to print infromation pertaining to the + current state of this module to the log. */ +void +agent_scd_dump_state (void) +{ + log_info ("agent_scd_dump_state: scd_lock="); + dump_mutex_state (&start_scd_lock); + log_printf ("\n"); + log_info ("agent_scd_dump_state: primary_scd_ctx=%p pid=%ld reusable=%d\n", + primary_scd_ctx, + (long)assuan_get_pid (primary_scd_ctx), + primary_scd_ctx_reusable); + if (socket_name) + log_info ("agent_scd_dump_state: socket=`%s'\n", socket_name); +} + + /* The unlock_scd function shall be called after having accessed the SCD. It is currently not very useful but gives an opportunity to keep track of connections currently calling SCD. Note that the @@ -384,6 +413,7 @@ agent_scd_check_aliveness (void) } + /* Reset the SCD if it has been used. */ int agent_reset_scd (ctrl_t ctrl) diff --git a/agent/command.c b/agent/command.c index 8af159f6d..56167118d 100644 --- a/agent/command.c +++ b/agent/command.c @@ -867,6 +867,39 @@ cmd_scd (ASSUAN_CONTEXT ctx, char *line) } + +/* UPDATESTARTUPTTY + + Set startup TTY and X DISPLAY variables to the values of this + session. This command is useful to pull future pinentries to + another screen. It is only required because there is no way in the + ssh-agent protocol to convey this information. */ +static int +cmd_updatestartuptty (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + + xfree (opt.startup_display); opt.startup_display = NULL; + xfree (opt.startup_ttyname); opt.startup_ttyname = NULL; + xfree (opt.startup_ttytype); opt.startup_ttytype = NULL; + xfree (opt.startup_lc_ctype); opt.startup_lc_ctype = NULL; + xfree (opt.startup_lc_messages); opt.startup_lc_messages = NULL; + + if (ctrl->display) + opt.startup_display = xtrystrdup (ctrl->display); + if (ctrl->ttyname) + opt.startup_ttyname = xtrystrdup (ctrl->ttyname); + if (ctrl->ttytype) + opt.startup_ttytype = xtrystrdup (ctrl->ttytype); + if (ctrl->lc_ctype) + opt.startup_lc_ctype = xtrystrdup (ctrl->lc_ctype); + if (ctrl->lc_messages) + opt.startup_lc_messages = xtrystrdup (ctrl->lc_messages); + + return 0; +} + + static int option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value) @@ -957,6 +990,7 @@ register_commands (ASSUAN_CONTEXT ctx) { "INPUT", NULL }, { "OUTPUT", NULL }, { "SCD", cmd_scd }, + { "UPDATESTARTUPTTY", cmd_updatestartuptty }, { NULL } }; int i, rc; diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 3537b07f0..90b071d5e 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -93,7 +93,8 @@ enum cmd_and_opt_values oKeepTTY, oKeepDISPLAY, oSSHSupport, - oDisableScdaemon + oDisableScdaemon, + oWriteEnvFile }; @@ -147,6 +148,8 @@ static ARGPARSE_OPTS opts[] = { { oAllowPresetPassphrase, "allow-preset-passphrase", 0, N_("allow presetting passphrase")}, { oSSHSupport, "enable-ssh-support", 0, N_("enable ssh-agent emulation") }, + { oWriteEnvFile, "write-env-file", 2, + N_("|FILE|write environment settings also to FILE")}, {0} }; @@ -438,6 +441,7 @@ main (int argc, char **argv ) int gpgconf_list = 0; int standard_socket = 0; gpg_error_t err; + const char *env_file_name = NULL; set_strusage (my_strusage); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); @@ -501,7 +505,7 @@ main (int argc, char **argv ) opt.startup_ttytype = getenv ("TERM"); if (opt.startup_ttytype) opt.startup_ttytype = xstrdup (opt.startup_ttytype); - /* Fixme: Neen to use the locale fucntion here. */ + /* Fixme: Better use the locale function here. */ opt.startup_lc_ctype = getenv ("LC_CTYPE"); if (opt.startup_lc_ctype) opt.startup_lc_ctype = xstrdup (opt.startup_lc_ctype); @@ -619,6 +623,7 @@ main (int argc, char **argv ) case oKeepDISPLAY: opt.keep_display = 1; break; case oSSHSupport: opt.ssh_support = 1; break; + case oWriteEnvFile: env_file_name = pargs.r.ret_str; break; default : pargs.err = configfp? 1:2; break; } @@ -855,6 +860,29 @@ main (int argc, char **argv ) if (opt.ssh_support) *socket_name_ssh = 0; + if (env_file_name) + { + FILE *fp; + + fp = fopen (env_file_name, "w"); + if (!fp) + log_error (_("error creating `%s': %s\n"), + env_file_name, strerror (errno)); + else + { + fputs (infostr, fp); + putc ('\n', fp); + if (opt.ssh_support) + { + fputs (infostr_ssh_sock, fp); + putc ('\n', fp); + fputs (infostr_ssh_pid, fp); + putc ('\n', fp); + } + fclose (fp); + } + } + if (argc) { /* Run the program given on the commandline. */ @@ -1273,7 +1301,7 @@ create_directories (void) static void handle_tick (void) { - /* Check whether the scdaemon has dies and cleanup in this case. */ + /* Check whether the scdaemon has died and cleanup in this case. */ agent_scd_check_aliveness (); /* If we are running as a child of another process, check whether @@ -1311,6 +1339,7 @@ handle_signal (int signo) case SIGUSR1: log_info ("SIGUSR1 received - printing internal information:\n"); pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); + agent_scd_dump_state (); break; case SIGUSR2: @@ -1353,7 +1382,8 @@ start_connection_thread (void *arg) int fd = (int)arg; if (opt.verbose) - log_info (_("handler for fd %d started\n"), fd); + log_info (_("handler 0x%lx for fd %d started\n"), + (long)pth_self (), fd); /* FIXME: Move this housekeeping into a ticker function. Calling it for each connection should work but won't work anymore if our @@ -1362,7 +1392,8 @@ start_connection_thread (void *arg) start_command_handler (-1, fd); if (opt.verbose) - log_info (_("handler for fd %d terminated\n"), fd); + log_info (_("handler 0x%lx for fd %d terminated\n"), + (long)pth_self (), fd); return NULL; } @@ -1375,13 +1406,15 @@ start_connection_thread_ssh (void *arg) int fd = (int)arg; if (opt.verbose) - log_info (_("ssh handler for fd %d started\n"), fd); + log_info (_("ssh handler 0x%lx for fd %d started\n"), + (long)pth_self (), fd); agent_trustlist_housekeeping (); start_command_handler_ssh (fd); if (opt.verbose) - log_info (_("ssh handler for fd %d terminated\n"), fd); + log_info (_("ssh handler 0x%lx for fd %d terminated\n"), + (long)pth_self (), fd); return NULL; } @@ -1405,15 +1438,17 @@ handle_connections (int listen_fd, int listen_fd_ssh) tattr = pth_attr_new(); pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0); pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024); - pth_attr_set (tattr, PTH_ATTR_NAME, "gpg-agent"); #ifndef HAVE_W32_SYSTEM /* fixme */ + /* Make sure that the signals we are going to handle are not blocked + and create an event object for them. */ sigemptyset (&sigs ); sigaddset (&sigs, SIGHUP); sigaddset (&sigs, SIGUSR1); sigaddset (&sigs, SIGUSR2); sigaddset (&sigs, SIGINT); sigaddset (&sigs, SIGTERM); + pth_sigmask (SIG_UNBLOCK, &sigs, NULL); ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); #else ev = NULL; @@ -1427,6 +1462,8 @@ handle_connections (int listen_fd, int listen_fd_ssh) for (;;) { + sigset_t oldsigs; + if (shutdown_pending) { if (pth_ctrl (PTH_CTRL_GETTHREADS) == 1) @@ -1488,6 +1525,12 @@ handle_connections (int listen_fd, int listen_fd_ssh) handle_tick (); } + + /* We now might create new threads and because we don't want any + signals - we are handling here - to be delivered to a new + thread. Thus we need to block those signals. */ + pth_sigmask (SIG_BLOCK, &sigs, &oldsigs); + if (FD_ISSET (listen_fd, &read_fdset)) { plen = sizeof paddr; @@ -1496,12 +1539,20 @@ handle_connections (int listen_fd, int listen_fd_ssh) { log_error ("accept failed: %s\n", strerror (errno)); } - else if (!pth_spawn (tattr, start_connection_thread, (void*)fd)) - { - log_error ("error spawning connection handler: %s\n", - strerror (errno) ); - close (fd); - } + else + { + char threadname[50]; + snprintf (threadname, sizeof threadname-1, + "conn fd=%d (gpg)", fd); + threadname[sizeof threadname -1] = 0; + pth_attr_set (tattr, PTH_ATTR_NAME, threadname); + if (!pth_spawn (tattr, start_connection_thread, (void*)fd)) + { + log_error ("error spawning connection handler: %s\n", + strerror (errno) ); + close (fd); + } + } fd = -1; } @@ -1513,14 +1564,27 @@ handle_connections (int listen_fd, int listen_fd_ssh) { log_error ("accept failed for ssh: %s\n", strerror (errno)); } - else if (!pth_spawn (tattr, start_connection_thread_ssh, (void*)fd)) - { - log_error ("error spawning ssh connection handler: %s\n", - strerror (errno) ); - close (fd); - } + else + { + char threadname[50]; + snprintf (threadname, sizeof threadname-1, + "conn fd=%d (ssh)", fd); + threadname[sizeof threadname -1] = 0; + pth_attr_set (tattr, PTH_ATTR_NAME, threadname); + + if (!pth_spawn (tattr, start_connection_thread_ssh, (void*)fd)) + { + log_error ("error spawning ssh connection handler: %s\n", + strerror (errno) ); + close (fd); + } + } fd = -1; } + + /* Restore the signal mask. */ + pth_sigmask (SIG_SETMASK, &oldsigs, NULL); + } pth_event_free (ev, PTH_FREE_ALL); diff --git a/common/ChangeLog b/common/ChangeLog index fccc71d49..08fb06775 100644 --- a/common/ChangeLog +++ b/common/ChangeLog @@ -1,3 +1,9 @@ +2005-06-03 Werner Koch + + * estream.c: Use HAVE_CONFIG_H and not USE_CONFIG_H! + (es_func_fd_read, es_func_fd_write): Protect against EINTR. + + 2005-06-01 Werner Koch * Makefile.am (AM_CPPFLAGS): Added. diff --git a/common/estream.c b/common/estream.c index 00cb749e8..bf5b02001 100644 --- a/common/estream.c +++ b/common/estream.c @@ -22,7 +22,7 @@ # include #endif -#ifdef USE_CONFIG_H +#ifdef HAVE_CONFIG_H # include #endif @@ -597,7 +597,9 @@ es_func_fd_read (void *cookie, char *buffer, size_t size) estream_cookie_fd_t file_cookie = cookie; ssize_t bytes_read; - bytes_read = ESTREAM_SYS_READ (file_cookie->fd, buffer, size); + do + bytes_read = ESTREAM_SYS_READ (file_cookie->fd, buffer, size); + while (bytes_read == -1 && errno == EINTR); return bytes_read; } @@ -610,7 +612,9 @@ es_func_fd_write (void *cookie, const char *buffer, size_t size) estream_cookie_fd_t file_cookie = cookie; ssize_t bytes_written; - bytes_written = ESTREAM_SYS_WRITE (file_cookie->fd, buffer, size); + do + bytes_written = ESTREAM_SYS_WRITE (file_cookie->fd, buffer, size); + while (bytes_written == -1 && errno == EINTR); return bytes_written; } diff --git a/configure.ac b/configure.ac index c1a3d77ea..17465c520 100644 --- a/configure.ac +++ b/configure.ac @@ -531,6 +531,8 @@ if test "$have_w32_system" = no; then PTH_LIBS="$PTH_LIBS `$PTH_CONFIG --libs`" AC_DEFINE(USE_GNU_PTH, 1, [Defined if the GNU Portable Thread Library should be used]) + AC_DEFINE(HAVE_PTH, 1, + [Defined if the GNU Pth is available]) fi fi else @@ -538,6 +540,7 @@ else PTH_CFLAGS="" PTH_LIBS="" AC_DEFINE(USE_GNU_PTH, 1) + AC_DEFINE(HAVE_PTH, 1) fi AC_SUBST(PTH_CFLAGS) AC_SUBST(PTH_LIBS) diff --git a/doc/ChangeLog b/doc/ChangeLog index 25840a5b1..f353bdf03 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,7 @@ +2005-06-03 Werner Koch + + * gpg-agent.texi (Agent UPDATESTARTUPTTY): New. + 2005-05-17 Werner Koch * gpg-agent.texi (Agent Options): Removed --disable-pth. diff --git a/doc/gpg-agent.texi b/doc/gpg-agent.texi index fa005c3b7..5e8c19468 100644 --- a/doc/gpg-agent.texi +++ b/doc/gpg-agent.texi @@ -25,11 +25,11 @@ eval `gpg-agent --daemon` @noindent If you don't use an X server, you can also put this into your regular startup file @code{~/.profile} or @code{.bash_profile}. It is best not -to run multiple instance of the @command{gpg-agent}, so you should make sure that -only is running: @command{gpg-agent} uses an environment variable to inform -clients about the communication parameters. You can write the -content of this environment variable to a file so that you can test for -a running agent. This short script may do the job: +to run multiple instance of the @command{gpg-agent}, so you should make +sure that only one is running: @command{gpg-agent} uses an environment +variable to inform clients about the communication parameters. You can +write the content of this environment variable to a file so that you can +test for a running agent. This short script may do the job: @smallexample if test -f $HOME/.gpg-agent-info && \ @@ -42,6 +42,9 @@ else fi @end smallexample +The new option @option{--write-env-file} may be used instead. + + @noindent You should always add the following lines to your @code{.bashrc} or whatever initialization file is used for all shell invocations: @@ -243,6 +246,21 @@ shell respective the C-shell . The default ist to guess it based on the environment variable @code{SHELL} which is in almost all cases sufficient. +@item --write-env-file @var{file} +@opindex write-env-file +Often it is required to connect to the agent from a process not being an +inferior of @command{gpg-agent} and thus the environment variable with +the socket name is not available. To help setting up those variables in +other sessions, this option may be used to write the information into +@var{file}. The format is suitable to be evaluated by a Bourne shell +like in this simple example: + +@example +eval `cat @var{file}` +eval `cut -d= -f 1 < @var{file} | xargs echo export` +@end example + + @item --no-grab @opindex no-grab Tell the pinentryo not to grab the keyboard and mouse. This option @@ -353,12 +371,19 @@ directory. Once, a key has been added to the gpg-agent this way, the gpg-agent will be ready to use the key. -Note: in case the gpg-agent receives a signature request, the user -might need to be prompted for a passphrase, which is necessary for -decrypting the stored key. Since the ssh-agent protocol does not -contain a mechanism for telling the agent on which display/terminal it -is running, gpg-agent's ssh-support will use the TTY or X display where -gpg-agent has been started. +Note: in case the gpg-agent receives a signature request, the user might +need to be prompted for a passphrase, which is necessary for decrypting +the stored key. Since the ssh-agent protocol does not contain a +mechanism for telling the agent on which display/terminal it is running, +gpg-agent's ssh-support will use the TTY or X display where gpg-agent +has been started. To switch this display to the current one, the +follwing command may be used: + +@smallexample +echo UPDATESTARTUPTTY | gpg-connect-agent +@end smallexample + + @end table @@ -544,6 +569,7 @@ secret keys. * Agent HAVEKEY:: Check whether a key is available * Agent LEARN:: Register a smartcard * Agent PASSWD:: Change a Passphrase +* Agent UPDATESTARTUPTTY:: Change the Standard Display @end menu @node Agent PKDECRYPT @@ -944,4 +970,16 @@ This command is used to interactively change the passphrase of the key indentified by the hex string @var{keygrip}. +@node Agent UPDATESTARTUPTTY +@subsection Change the standard display + +@example + UPDATESTARTUPTTY +@end example + +Set the startup TTY and X-DISPLAY variables to the values of this +session. This command is useful to direct future pinentry invocations +to another screen. It is only required because there is no way in the +ssh-agent protocol to convey this information. + diff --git a/scd/ChangeLog b/scd/ChangeLog index 136ed5618..da433e2f8 100644 --- a/scd/ChangeLog +++ b/scd/ChangeLog @@ -1,3 +1,32 @@ +2005-06-03 Werner Koch + + * scdaemon.c (handle_connections): Make sure that the signals we + are handling are not blocked.Block signals while creating new + threads. + (handle_connections): Include the file descriptor into the name of + the thread. + +2005-06-02 Werner Koch + + * app.c (app_dump_state, dump_mutex_state): New. + * scdaemon.c (handle_signal): Print it on SIGUSR1. + + * app-openpgp.c (do_writekey): Typo fix. + + * command.c (open_card): Check for locked state even if an + application context is available. + + * app-common.h: Add REF_COUNT field. + * app.c (release_application, select_application): Implement + reference counting to share the context beween connections. + + * app.c (lock_reader, unlock_reader): Take SLOT instead of APP as + argument. Changed all callers. + (select_application): Unlock the reader on error. This should fix + the hangs I noticed last week. + + * scdaemon.h: Removed card_ctx_t cruft. + 2005-06-01 Werner Koch * scdaemon.c: Include mkdtemp.h. diff --git a/scd/app-common.h b/scd/app-common.h index 812736ece..94087f221 100644 --- a/scd/app-common.h +++ b/scd/app-common.h @@ -39,6 +39,11 @@ struct app_ctx_s { function pointers may be used. Note that for unsupported operations the particular function pointer is set to NULL */ + + int ref_count; /* Number of connections currently using this + application context. fixme: We might want to + merg this witghn INITIALIZED above. */ + int slot; /* Used reader. */ /* If this is used by GnuPG 1.4 we need to know the assuan context @@ -123,6 +128,7 @@ size_t app_help_read_length_of_cert (int slot, int fid, size_t *r_certoff); /*-- app.c --*/ +void app_dump_state (void); gpg_error_t select_application (ctrl_t ctrl, int slot, const char *name, app_t *r_app); void release_application (app_t app); diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index 14483869b..1ff096138 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -1745,7 +1745,7 @@ do_writekey (app_t app, ctrl_t ctrl, nbits = rsa_e? count_bits (rsa_e, rsa_e_len) : 0; if (nbits < 2 || nbits > 32) { - log_error (_("RSA public exponent missing or largerr than %d bits\n"), + log_error (_("RSA public exponent missing or larger than %d bits\n"), 32); err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; diff --git a/scd/app.c b/scd/app.c index f2c427f5b..2c8c915d7 100644 --- a/scd/app.c +++ b/scd/app.c @@ -39,25 +39,24 @@ static struct { int initialized; pth_mutex_t lock; + app_t app; /* Application context in use or NULL. */ } lock_table[10]; -/* Lock the reader associated with the APP context. This function - shall be used right before calling any of the actual application - functions to serialize access to the reader. We do this always - even if the reader is not actually used. This allows an actual - application to assume that it never shares a reader (while - performing one command). Returns 0 on success; only then the - unlock_reader function must be called after returning from the - handler. */ +/* Lock the reader SLOT. This function shall be used right before + calling any of the actual application functions to serialize access + to the reader. We do this always even if the reader is not + actually used. This allows an actual connection to assume that it + never shares a reader (while performing one command). Returns 0 on + success; only then the unlock_reader function must be called after + returning from the handler. */ static gpg_error_t -lock_reader (app_t app) +lock_reader (int slot) { gpg_error_t err; - int slot = app->slot; if (slot < 0 || slot >= DIM (lock_table)) - return gpg_error (app->slot<0? GPG_ERR_INV_VALUE : GPG_ERR_RESOURCE_LIMIT); + return gpg_error (slot<0? GPG_ERR_INV_VALUE : GPG_ERR_RESOURCE_LIMIT); if (!lock_table[slot].initialized) { @@ -68,6 +67,7 @@ lock_reader (app_t app) return err; } lock_table[slot].initialized = 1; + lock_table[slot].app = NULL; } if (!pth_mutex_acquire (&lock_table[slot].lock, 0, NULL)) @@ -83,10 +83,8 @@ lock_reader (app_t app) /* Release a lock on the reader. See lock_reader(). */ static void -unlock_reader (app_t app) +unlock_reader (int slot) { - int slot = app->slot; - if (slot < 0 || slot >= DIM (lock_table) || !lock_table[slot].initialized) log_bug ("unlock_reader called for invalid slot %d\n", slot); @@ -96,6 +94,39 @@ unlock_reader (app_t app) slot, strerror (errno)); } + + +static void +dump_mutex_state (pth_mutex_t *m) +{ + if (!(m->mx_state & PTH_MUTEX_INITIALIZED)) + log_printf ("not_initialized"); + else if (!(m->mx_state & PTH_MUTEX_LOCKED)) + log_printf ("not_locked"); + else + log_printf ("locked tid=0x%lx count=%lu", (long)m->mx_owner, m->mx_count); +} + + +/* This function may be called to print information pertaining to the + current state of this module to the log. */ +void +app_dump_state (void) +{ + int slot; + + for (slot=0; slot < DIM (lock_table); slot++) + if (lock_table[slot].initialized) + { + log_info ("app_dump_state: slot=%d lock=", slot); + dump_mutex_state (&lock_table[slot].lock); + if (lock_table[slot].app) + log_printf (" app=%p type=`%s'", + lock_table[slot].app, lock_table[slot].app->apptype); + log_printf ("\n"); + } +} + /* Check wether the application NAME is allowed. This does not mean we have support for it though. */ @@ -120,23 +151,48 @@ gpg_error_t select_application (ctrl_t ctrl, int slot, const char *name, app_t *r_app) { gpg_error_t err; - app_t app; + app_t app = NULL; unsigned char *result = NULL; size_t resultlen; *r_app = NULL; + + err = lock_reader (slot); + if (err) + return err; + + /* First check whether we already have an application to share. */ + app = lock_table[slot].initialized ? lock_table[slot].app : NULL; + if (app && name) + if (!app->apptype || ascii_strcasecmp (app->apptype, name)) + { + unlock_reader (slot); + if (app->apptype) + log_info ("application `%s' in use by reader %d - can't switch\n", + app->apptype, slot); + return gpg_error (GPG_ERR_CONFLICT); + } + + if (app) + { + if (app->slot != slot) + log_bug ("slot mismatch %d/%d\n", app->slot, slot); + app->ref_count++; + *r_app = app; + unlock_reader (slot); + return 0; /* Okay: We share that one. */ + } + app = xtrycalloc (1, sizeof *app); if (!app) { err = gpg_error_from_errno (errno); log_info ("error allocating context: %s\n", gpg_strerror (err)); + unlock_reader (slot); return err; } app->slot = slot; - err = lock_reader (app); - if (err) - return err; /* Fixme: We should now first check whether a card is at all present. */ @@ -162,7 +218,7 @@ select_application (ctrl_t ctrl, int slot, const char *name, app_t *r_app) have some test cards with such an invalid encoding and therefore I use this ugly workaround to return something I can further experiment with. */ - log_debug ("enabling BMI testcard workaround\n"); + log_info ("enabling BMI testcard workaround\n"); n--; } @@ -212,22 +268,41 @@ select_application (ctrl_t ctrl, int slot, const char *name, app_t *r_app) log_info ("no supported card application found: %s\n", gpg_strerror (err)); xfree (app); + unlock_reader (slot); return err; } app->initialized = 1; - unlock_reader (app); + app->ref_count = 1; + lock_table[slot].app = app; *r_app = app; + unlock_reader (slot); return 0; } +/* Free the resources associated with the application APP. APP is + allowed to be NULL in which case this is a no-op. Note that we are + using reference counting to track the users of the application. */ void release_application (app_t app) { + int slot; + if (!app) return; + if (app->ref_count < 1) + log_bug ("trying to release an already released context\n"); + if (--app->ref_count) + return; + + /* Clear the reference to the application from the lock table. */ + for (slot = 0; slot < DIM (lock_table); slot++) + if (lock_table[slot].initialized && lock_table[slot].app == app) + lock_table[slot].app = NULL; + + /* Deallocate. */ if (app->fnc.deinit) { app->fnc.deinit (app); @@ -320,11 +395,11 @@ app_write_learn_status (app_t app, CTRL ctrl) if (app->apptype) send_status_info (ctrl, "APPTYPE", app->apptype, strlen (app->apptype), NULL, 0); - err = lock_reader (app); + err = lock_reader (app->slot); if (err) return err; err = app->fnc.learn_status (app, ctrl); - unlock_reader (app); + unlock_reader (app->slot); return err; } @@ -345,11 +420,11 @@ app_readcert (app_t app, const char *certid, return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.readcert) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - err = lock_reader (app); + err = lock_reader (app->slot); if (err) return err; err = app->fnc.readcert (app, certid, cert, certlen); - unlock_reader (app); + unlock_reader (app->slot); return err; } @@ -377,11 +452,11 @@ app_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.readkey) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - err = lock_reader (app); + err = lock_reader (app->slot); if (err) return err; err= app->fnc.readkey (app, keyid, pk, pklen); - unlock_reader (app); + unlock_reader (app->slot); return err; } @@ -419,11 +494,11 @@ app_getattr (app_t app, CTRL ctrl, const char *name) if (!app->fnc.getattr) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - err = lock_reader (app); + err = lock_reader (app->slot); if (err) return err; err = app->fnc.getattr (app, ctrl, name); - unlock_reader (app); + unlock_reader (app->slot); return err; } @@ -442,11 +517,11 @@ app_setattr (app_t app, const char *name, return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.setattr) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - err = lock_reader (app); + err = lock_reader (app->slot); if (err) return err; err = app->fnc.setattr (app, name, pincb, pincb_arg, value, valuelen); - unlock_reader (app); + unlock_reader (app->slot); return err; } @@ -468,14 +543,14 @@ app_sign (app_t app, const char *keyidstr, int hashalgo, return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.sign) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - err = lock_reader (app); + err = lock_reader (app->slot); if (err) return err; err = app->fnc.sign (app, keyidstr, hashalgo, pincb, pincb_arg, indata, indatalen, outdata, outdatalen); - unlock_reader (app); + unlock_reader (app->slot); if (opt.verbose) log_info ("operation sign result: %s\n", gpg_strerror (err)); return err; @@ -500,14 +575,14 @@ app_auth (app_t app, const char *keyidstr, return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.auth) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - err = lock_reader (app); + err = lock_reader (app->slot); if (err) return err; err = app->fnc.auth (app, keyidstr, pincb, pincb_arg, indata, indatalen, outdata, outdatalen); - unlock_reader (app); + unlock_reader (app->slot); if (opt.verbose) log_info ("operation auth result: %s\n", gpg_strerror (err)); return err; @@ -532,14 +607,14 @@ app_decipher (app_t app, const char *keyidstr, return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.decipher) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - err = lock_reader (app); + err = lock_reader (app->slot); if (err) return err; err = app->fnc.decipher (app, keyidstr, pincb, pincb_arg, indata, indatalen, outdata, outdatalen); - unlock_reader (app); + unlock_reader (app->slot); if (opt.verbose) log_info ("operation decipher result: %s\n", gpg_strerror (err)); return err; @@ -562,12 +637,12 @@ app_writekey (app_t app, ctrl_t ctrl, return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.writekey) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - err = lock_reader (app); + err = lock_reader (app->slot); if (err) return err; err = app->fnc.writekey (app, ctrl, keyidstr, flags, pincb, pincb_arg, keydata, keydatalen); - unlock_reader (app); + unlock_reader (app->slot); if (opt.verbose) log_info ("operation writekey result: %s\n", gpg_strerror (err)); return err; @@ -589,11 +664,11 @@ app_genkey (app_t app, CTRL ctrl, const char *keynostr, unsigned int flags, return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.genkey) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - err = lock_reader (app); + err = lock_reader (app->slot); if (err) return err; err = app->fnc.genkey (app, ctrl, keynostr, flags, pincb, pincb_arg); - unlock_reader (app); + unlock_reader (app->slot); if (opt.verbose) log_info ("operation genkey result: %s\n", gpg_strerror (err)); return err; @@ -612,11 +687,11 @@ app_get_challenge (app_t app, size_t nbytes, unsigned char *buffer) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); - err = lock_reader (app); + err = lock_reader (app->slot); if (err) return err; err = iso7816_get_challenge (app->slot, nbytes, buffer); - unlock_reader (app); + unlock_reader (app->slot); return err; } @@ -636,12 +711,12 @@ app_change_pin (app_t app, CTRL ctrl, const char *chvnostr, int reset_mode, return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.change_pin) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - err = lock_reader (app); + err = lock_reader (app->slot); if (err) return err; err = app->fnc.change_pin (app, ctrl, chvnostr, reset_mode, pincb, pincb_arg); - unlock_reader (app); + unlock_reader (app->slot); if (opt.verbose) log_info ("operation change_pin result: %s\n", gpg_strerror (err)); return err; @@ -664,11 +739,11 @@ app_check_pin (app_t app, const char *keyidstr, return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.check_pin) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - err = lock_reader (app); + err = lock_reader (app->slot); if (err) return err; err = app->fnc.check_pin (app, keyidstr, pincb, pincb_arg); - unlock_reader (app); + unlock_reader (app->slot); if (opt.verbose) log_info ("operation check_pin result: %s\n", gpg_strerror (err)); return err; diff --git a/scd/command.c b/scd/command.c index 738b1f003..287f8c921 100644 --- a/scd/command.c +++ b/scd/command.c @@ -253,12 +253,12 @@ open_card (ctrl_t ctrl, const char *apptype) if (ctrl->server_local->card_removed) return map_to_assuan_status (gpg_error (GPG_ERR_CARD_REMOVED)); - if (ctrl->app_ctx) - return 0; /* Already initialized for one specific application. */ - if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); + if (ctrl->app_ctx) + return 0; /* Already initialized for one specific application. */ + if (ctrl->reader_slot != -1) slot = ctrl->reader_slot; else diff --git a/scd/scdaemon.c b/scd/scdaemon.c index 341719b1e..5b5e09176 100644 --- a/scd/scdaemon.c +++ b/scd/scdaemon.c @@ -809,6 +809,7 @@ handle_signal (int signo) case SIGUSR1: log_info ("SIGUSR1 received - printing internal information:\n"); pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); + app_dump_state (); break; case SIGUSR2: @@ -1013,7 +1014,6 @@ handle_connections (int listen_fd) tattr = pth_attr_new(); pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0); pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 512*1024); - pth_attr_set (tattr, PTH_ATTR_NAME, "scd-connections"); #ifndef HAVE_W32_SYSTEM /* fixme */ sigemptyset (&sigs ); @@ -1022,6 +1022,7 @@ handle_connections (int listen_fd) sigaddset (&sigs, SIGUSR2); sigaddset (&sigs, SIGINT); sigaddset (&sigs, SIGTERM); + pth_sigmask (SIG_UNBLOCK, &sigs, NULL); ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); #else ev = NULL; @@ -1034,6 +1035,8 @@ handle_connections (int listen_fd) for (;;) { + sigset_t oldsigs; + if (shutdown_pending) { if (pth_ctrl (PTH_CTRL_GETTHREADS) == 1) @@ -1093,6 +1096,11 @@ handle_connections (int listen_fd) handle_tick (); } + /* We now might create new threads and because we don't want any + signals - we are handling here - to be delivered to a new + thread. Thus we need to block those signals. */ + pth_sigmask (SIG_BLOCK, &sigs, &oldsigs); + if (listen_fd != -1 && FD_ISSET (listen_fd, &read_fdset)) { plen = sizeof paddr; @@ -1101,15 +1109,26 @@ handle_connections (int listen_fd) { log_error ("accept failed: %s\n", strerror (errno)); } - else if (!pth_spawn (tattr, start_connection_thread, (void*)fd)) - { - log_error ("error spawning connection handler: %s\n", - strerror (errno) ); - close (fd); - } + else + { + char threadname[50]; + snprintf (threadname, sizeof threadname-1, "conn fd=%d", fd); + threadname[sizeof threadname -1] = 0; + pth_attr_set (tattr, PTH_ATTR_NAME, threadname); + + if (!pth_spawn (tattr, start_connection_thread, (void*)fd)) + { + log_error ("error spawning connection handler: %s\n", + strerror (errno) ); + close (fd); + } + } fd = -1; } + /* Restore the signal mask. */ + pth_sigmask (SIG_SETMASK, &oldsigs, NULL); + } pth_event_free (ev, PTH_FREE_ALL); diff --git a/scd/scdaemon.h b/scd/scdaemon.h index eaa9abd35..54566b6ad 100644 --- a/scd/scdaemon.h +++ b/scd/scdaemon.h @@ -77,19 +77,28 @@ struct { #define DBG_CARD_IO (opt.debug & DBG_CARD_IO_VALUE) struct server_local_s; -struct card_ctx_s; struct app_ctx_s; -struct server_control_s { +struct server_control_s +{ + /* Local data of the server; used only in command.c. */ struct server_local_s *server_local; - int reader_slot; /* Slot of the open reader or -1 if not open. */ - struct card_ctx_s *card_ctx; + + /* Slot of the open reader or -1 if not open. */ + int reader_slot; + + /* The application context used with this connection or NULL if none + associated. Note that this is shared with the other connections: + All connections accessing the same reader are using the same + application context. */ struct app_ctx_s *app_ctx; - struct { + + /* Helper to store the value we are going to sign */ + struct + { unsigned char *value; int valuelen; - } in_data; /* helper to store the value we are going to sign */ - + } in_data; }; typedef struct server_control_s *CTRL; -- cgit From 33701641829798ddd7fced64cf9a504cc5f48cc1 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 7 Jun 2005 19:09:18 +0000 Subject: New debugging optionhs, updates to the manual. --- NEWS | 5 +- agent/ChangeLog | 21 ++ agent/agent.h | 49 +++-- agent/cache.c | 36 +++- agent/call-scd.c | 30 +-- agent/command-ssh.c | 5 +- agent/command.c | 21 +- agent/findkey.c | 24 +-- agent/gpg-agent.c | 6 + agent/pkdecrypt.c | 3 +- agent/pksign.c | 18 +- agent/query.c | 30 ++- common/sysutils.c | 2 +- doc/ChangeLog | 4 + doc/Makefile.am | 24 ++- doc/debugging.texi | 44 +++++ doc/gnupg-card-architecture.fig | 419 ++++++++++++++++++++++++++++++++++++++++ doc/gnupg.texi | 19 +- doc/gpg-agent.texi | 47 ++++- doc/scdaemon.texi | 8 + scd/ChangeLog | 4 + scd/command.c | 3 +- scd/scdaemon.c | 18 ++ 23 files changed, 759 insertions(+), 81 deletions(-) create mode 100644 doc/gnupg-card-architecture.fig (limited to 'agent/command.c') diff --git a/NEWS b/NEWS index e28f1284a..daa18c4c0 100644 --- a/NEWS +++ b/NEWS @@ -13,9 +13,12 @@ Noteworthy changes in version 1.9.17 does allows only signing using TCOS cards but we are going to enhance it to match all the old capabilities. - * [gpg-agent] New option --rite-env-file and Assuan command + * [gpg-agent] New option --write-env-file and Assuan command UPDATESTARTUPTTY. + * [gpg-agent] New option --default-cache-ttl-ssh to set the TTL for + SSH passphrase caching independent from the other passphrases. + Noteworthy changes in version 1.9.16 (2005-04-21) ------------------------------------------------- diff --git a/agent/ChangeLog b/agent/ChangeLog index 9621e5de0..1a157fa52 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,24 @@ +2005-06-06 Werner Koch + + * gpg-agent.c: New option --default-cache-ttl-ssh. + * agent.h (cache_mode_t): New. + * pksign.c (agent_pksign_do): New arg CACHE_MODE to replace the + ARG IGNORE_CACHE. Changed all callers. + (agent_pksign): Ditto. + * findkey.c (agent_key_from_file): Ditto. Canged all callers. + (unprotect): Ditto. + * command-ssh.c (data_sign): Use CACHE_MODE_SSH. + * cache.c (agent_get_cache): New arg CACHE_MODE. + (agent_put_cache): Ditto. Store it in the cache. + + * query.c (agent_query_dump_state, dump_mutex_state): New. + (unlock_pinentry): Reset the global context before releasing the + mutex. + * gpg-agent.c (handle_signal): Dump query.c info on SIGUSR1. + + * call-scd.c (agent_scd_check_aliveness): Always do a waitpid and + add a timeout to the locking. + 2005-06-03 Werner Koch * command.c (cmd_updatestartuptty): New. diff --git a/agent/agent.h b/agent/agent.h index 51e66abee..350e5c0d2 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -69,9 +69,13 @@ struct { smartcard tasks. */ int disable_scdaemon; /* Never use the SCdaemon. */ int no_grab; /* Don't let the pinentry grab the keyboard */ - unsigned long def_cache_ttl; + + /* The default and maximum TTL of cache entries. */ + unsigned long def_cache_ttl; /* Normal. */ + unsigned long def_cache_ttl_ssh; /* SSH. */ unsigned long max_cache_ttl; + int running_detached; /* We are running detached from the tty. */ int ignore_cache_for_signing; @@ -147,12 +151,26 @@ struct pin_entry_info_s { }; -enum { - PRIVATE_KEY_UNKNOWN = 0, - PRIVATE_KEY_CLEAR = 1, - PRIVATE_KEY_PROTECTED = 2, - PRIVATE_KEY_SHADOWED = 3 -}; +enum + { + PRIVATE_KEY_UNKNOWN = 0, + PRIVATE_KEY_CLEAR = 1, + PRIVATE_KEY_PROTECTED = 2, + PRIVATE_KEY_SHADOWED = 3 + }; + + +/* Values for the cache_mode arguments. */ +typedef enum + { + CACHE_MODE_IGNORE = 0, /* Special mode to by pass the cache. */ + CACHE_MODE_ANY, /* Any mode except ignore matches. */ + CACHE_MODE_NORMAL, /* Normal cache (gpg-agent). */ + CACHE_MODE_USER, /* GET_PASSPHRASE related cache. */ + CACHE_MODE_SSH /* SSH related cache. */ + } +cache_mode_t; + /*-- gpg-agent.c --*/ void agent_exit (int rc) JNLIB_GCC_A_NR; /* Also implemented in other tools */ @@ -171,7 +189,8 @@ gpg_error_t agent_key_from_file (ctrl_t ctrl, const char *desc_text, const unsigned char *grip, unsigned char **shadow_info, - int ignore_cache, gcry_sexp_t *result); + cache_mode_t cache_mode, + gcry_sexp_t *result); gpg_error_t agent_public_key_from_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result); @@ -179,6 +198,7 @@ int agent_key_available (const unsigned char *grip); /*-- query.c --*/ void initialize_module_query (void); +void agent_query_dump_state (void); int agent_askpin (ctrl_t ctrl, const char *desc_text, const char *prompt_text, const char *inital_errtext, @@ -191,16 +211,19 @@ int agent_get_confirmation (ctrl_t ctrl, const char *desc, const char *ok, /*-- cache.c --*/ void agent_flush_cache (void); -int agent_put_cache (const char *key, const char *data, int ttl); -const char *agent_get_cache (const char *key, void **cache_id); +int agent_put_cache (const char *key, cache_mode_t cache_mode, + const char *data, int ttl); +const char *agent_get_cache (const char *key, cache_mode_t cache_mode, + void **cache_id); void agent_unlock_cache_entry (void **cache_id); /*-- pksign.c --*/ -int agent_pksign_do (CTRL ctrl, const char *desc_text, - gcry_sexp_t *signature_sexp, int ignore_cache); +int agent_pksign_do (ctrl_t ctrl, const char *desc_text, + gcry_sexp_t *signature_sexp, + cache_mode_t cache_mode); int agent_pksign (ctrl_t ctrl, const char *desc_text, - membuf_t *outbuf, int ignore_cache); + membuf_t *outbuf, cache_mode_t cache_mode); /*-- pkdecrypt.c --*/ int agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, diff --git a/agent/cache.c b/agent/cache.c index 18aa7653b..a032b4fa7 100644 --- a/agent/cache.c +++ b/agent/cache.c @@ -42,6 +42,7 @@ struct cache_item_s { int ttl; /* max. lifetime given in seconds, -1 one means infinite */ int lockcount; struct secret_data_s *pw; + cache_mode_t cache_mode; char key[1]; }; @@ -78,6 +79,7 @@ new_data (const void *data, size_t length) } + /* check whether there are items to expire */ static void housekeeping (void) @@ -85,7 +87,7 @@ housekeeping (void) ITEM r, rprev; time_t current = gnupg_get_time (); - /* first expire the actual data */ + /* First expire the actual data */ for (r=thecache; r; r = r->next) { if (!r->lockcount && r->pw @@ -100,7 +102,7 @@ housekeeping (void) } } - /* second, make sure that we also remove them based on the created stamp so + /* Second, make sure that we also remove them based on the created stamp so that the user has to enter it from time to time. We do this every hour */ for (r=thecache; r; r = r->next) { @@ -115,7 +117,7 @@ housekeeping (void) } } - /* third, make sure that we don't have too many items in the list. + /* Third, make sure that we don't have too many items in the list. Expire old and unused entries after 30 minutes */ for (rprev=NULL, r=thecache; r; ) { @@ -186,19 +188,27 @@ agent_flush_cache (void) with a maximum lifetime of TTL seconds. If there is already data under this key, it will be replaced. Using a DATA of NULL deletes the entry. A TTL of 0 is replaced by the default TTL and a TTL of - -1 set infinite timeout. */ + -1 set infinite timeout. CACHE_MODE is stored with the cache entry + and used t select different timeouts. */ int -agent_put_cache (const char *key, const char *data, int ttl) +agent_put_cache (const char *key, cache_mode_t cache_mode, + const char *data, int ttl) { ITEM r; if (DBG_CACHE) - log_debug ("agent_put_cache `%s' requested ttl=%d\n", key, ttl); + log_debug ("agent_put_cache `%s' requested ttl=%d mode=%d\n", + key, ttl, cache_mode); housekeeping (); if (!ttl) - ttl = opt.def_cache_ttl; - if (!ttl) + { + if (cache_mode == CACHE_MODE_SSH) + ttl = opt.def_cache_ttl_ssh; + else + ttl = opt.def_cache_ttl; + } + if (!ttl || cache_mode == CACHE_MODE_IGNORE) return 0; for (r=thecache; r; r = r->next) @@ -217,6 +227,7 @@ agent_put_cache (const char *key, const char *data, int ttl) { r->created = r->accessed = gnupg_get_time (); r->ttl = ttl; + r->cache_mode = cache_mode; r->pw = new_data (data, strlen (data)+1); if (!r->pw) log_error ("out of core while allocating new cache item\n"); @@ -232,6 +243,7 @@ agent_put_cache (const char *key, const char *data, int ttl) strcpy (r->key, key); r->created = r->accessed = gnupg_get_time (); r->ttl = ttl; + r->cache_mode = cache_mode; r->pw = new_data (data, strlen (data)+1); if (!r->pw) { @@ -249,12 +261,16 @@ agent_put_cache (const char *key, const char *data, int ttl) } -/* Try to find an item in the cache */ +/* Try to find an item in the cache. Note that we currently don't + make use of CACHE_MODE. */ const char * -agent_get_cache (const char *key, void **cache_id) +agent_get_cache (const char *key, cache_mode_t cache_mode, void **cache_id) { ITEM r; + if (cache_mode == CACHE_MODE_IGNORE) + return NULL; + if (DBG_CACHE) log_debug ("agent_get_cache `%s'...\n", key); housekeeping (); diff --git a/agent/call-scd.c b/agent/call-scd.c index 00c9df2a7..4dff8e3c1 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -372,25 +372,33 @@ start_scd (ctrl_t ctrl) void agent_scd_check_aliveness (void) { + pth_event_t evt; pid_t pid; int rc; - /* We can do so only if there is no more active primary connection. - With an active primary connection, this is all no problem because - with the end of gpg-agent's session a disconnect is send and the - this function will be used at a later time. */ - if (!primary_scd_ctx || !primary_scd_ctx_reusable) - return; + if (!primary_scd_ctx) + return; /* No scdaemon running. */ - if (!pth_mutex_acquire (&start_scd_lock, 0, NULL)) + /* This is not a critical function so we use a short timeout while + acquiring the lock. */ + evt = pth_event (PTH_EVENT_TIME, pth_timeout (1, 0)); + if (!pth_mutex_acquire (&start_scd_lock, 0, evt)) { - log_error ("failed to acquire the start_scd lock while" - " doing an aliveness check: %s\n", - strerror (errno)); + if (pth_event_occurred (evt)) + { + if (opt.verbose > 1) + log_info ("failed to acquire the start_scd lock while" + " doing an aliveness check: %s\n", "timeout"); + } + else + log_error ("failed to acquire the start_scd lock while" + " doing an aliveness check: %s\n", strerror (errno)); + pth_event_free (evt, PTH_FREE_THIS); return; } + pth_event_free (evt, PTH_FREE_THIS); - if (primary_scd_ctx && primary_scd_ctx_reusable) + if (primary_scd_ctx) { pid = assuan_get_pid (primary_scd_ctx); if (pid != (pid_t)(-1) && pid diff --git a/agent/command-ssh.c b/agent/command-ssh.c index 030cc70a0..870afe059 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -2014,7 +2014,8 @@ data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder, ctrl->use_auth_call = 1; err = agent_pksign_do (ctrl, _("Please enter the passphrase " - "for the ssh key%0A %c"), &signature_sexp, 0); + "for the ssh key%0A %c"), &signature_sexp, + CACHE_MODE_SSH); ctrl->use_auth_call = 0; if (err) goto out; @@ -2386,7 +2387,7 @@ ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl) for (i = 0; i < 20; i++) sprintf (key_grip + 2 * i, "%02X", key_grip_raw[i]); - err = agent_put_cache (key_grip, pi->pin, ttl); + err = agent_put_cache (key_grip, CACHE_MODE_SSH, pi->pin, ttl); if (err) goto out; diff --git a/agent/command.c b/agent/command.c index 56167118d..ebf3a8220 100644 --- a/agent/command.c +++ b/agent/command.c @@ -404,19 +404,19 @@ static int cmd_pksign (ASSUAN_CONTEXT ctx, char *line) { int rc; - int ignore_cache = 0; + cache_mode_t cache_mode = CACHE_MODE_NORMAL; ctrl_t ctrl = assuan_get_pointer (ctx); membuf_t outbuf; - + if (opt.ignore_cache_for_signing) - ignore_cache = 1; + cache_mode = CACHE_MODE_IGNORE; else if (!ctrl->server_local->use_cache_for_signing) - ignore_cache = 1; + cache_mode = CACHE_MODE_IGNORE; init_membuf (&outbuf, 512); rc = agent_pksign (ctrl, ctrl->server_local->keydesc, - &outbuf, ignore_cache); + &outbuf, cache_mode); if (rc) clear_outbuf (&outbuf); else @@ -623,7 +623,8 @@ cmd_get_passphrase (ASSUAN_CONTEXT ctx, char *line) desc = NULL; /* Note: we store the hexified versions in the cache. */ - pw = cacheid ? agent_get_cache (cacheid, &cache_marker) : NULL; + pw = cacheid ? agent_get_cache (cacheid, CACHE_MODE_NORMAL, &cache_marker) + : NULL; if (pw) { assuan_begin_confidential (ctx); @@ -647,7 +648,7 @@ cmd_get_passphrase (ASSUAN_CONTEXT ctx, char *line) if (!rc) { if (cacheid) - agent_put_cache (cacheid, response, 0); + agent_put_cache (cacheid, CACHE_MODE_USER, response, 0); assuan_begin_confidential (ctx); rc = assuan_set_okay_line (ctx, response); xfree (response); @@ -682,7 +683,7 @@ cmd_clear_passphrase (ASSUAN_CONTEXT ctx, char *line) if (!cacheid || !*cacheid || strlen (cacheid) > 50) return set_error (Parameter_Error, "invalid length of cacheID"); - agent_put_cache (cacheid, NULL, 0); + agent_put_cache (cacheid, CACHE_MODE_USER, NULL, 0); return 0; } @@ -772,7 +773,7 @@ cmd_passwd (ASSUAN_CONTEXT ctx, char *line) Assuan error code. */ rc = agent_key_from_file (ctrl, ctrl->server_local->keydesc, - grip, &shadow_info, 1, &s_skey); + grip, &shadow_info, CACHE_MODE_IGNORE, &s_skey); if (rc) ; else if (!s_skey) @@ -842,7 +843,7 @@ cmd_preset_passphrase (ASSUAN_CONTEXT ctx, char *line) else return map_to_assuan_status (gpg_error (GPG_ERR_NOT_IMPLEMENTED)); - rc = agent_put_cache (grip_clear, passphrase, ttl); + rc = agent_put_cache (grip_clear, CACHE_MODE_ANY, passphrase, ttl); if (rc) log_error ("command preset_passwd failed: %s\n", gpg_strerror (rc)); diff --git a/agent/findkey.c b/agent/findkey.c index 999a5d620..56433c9c4 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -230,8 +230,9 @@ modify_description (const char *in, const char *comment, char **result) caching mechanism. DESC_TEXT may be set to override the default description used for the pinentry. */ static int -unprotect (CTRL ctrl, const char *desc_text, - unsigned char **keybuf, const unsigned char *grip, int ignore_cache) +unprotect (ctrl_t ctrl, const char *desc_text, + unsigned char **keybuf, const unsigned char *grip, + cache_mode_t cache_mode) { struct pin_entry_info_s *pi; struct try_unprotect_arg_s arg; @@ -246,10 +247,12 @@ unprotect (CTRL ctrl, const char *desc_text, /* First try to get it from the cache - if there is none or we can't unprotect it, we fall back to ask the user */ - if (!ignore_cache) + if (cache_mode != CACHE_MODE_IGNORE) { void *cache_marker; - const char *pw = agent_get_cache (hexgrip, &cache_marker); + const char *pw; + + pw = agent_get_cache (hexgrip, cache_mode, &cache_marker); if (pw) { rc = agent_unprotect (*keybuf, pw, &result, &resultlen); @@ -280,7 +283,7 @@ unprotect (CTRL ctrl, const char *desc_text, if (!rc) { assert (arg.unprotected_key); - agent_put_cache (hexgrip, pi->pin, 0); + agent_put_cache (hexgrip, cache_mode, pi->pin, 0); xfree (*keybuf); *keybuf = arg.unprotected_key; } @@ -360,14 +363,13 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result) /* Return the secret key as an S-Exp in RESULT after locating it using the grip. Returns NULL in RESULT if the operation should be diverted to a token; SHADOW_INFO will point then to an allocated - S-Expression with the shadow_info part from the file. With - IGNORE_CACHE passed as true the passphrase is not taken from the - cache. DESC_TEXT may be set to present a custom description for the - pinentry. */ + S-Expression with the shadow_info part from the file. CACHE_MODE + defines now the cache shall be used. DESC_TEXT may be set to + present a custom description for the pinentry. */ gpg_error_t agent_key_from_file (ctrl_t ctrl, const char *desc_text, const unsigned char *grip, unsigned char **shadow_info, - int ignore_cache, gcry_sexp_t *result) + cache_mode_t cache_mode, gcry_sexp_t *result) { int rc; unsigned char *buf; @@ -447,7 +449,7 @@ agent_key_from_file (ctrl_t ctrl, const char *desc_text, if (!rc) { - rc = unprotect (ctrl, desc_text_final, &buf, grip, ignore_cache); + rc = unprotect (ctrl, desc_text_final, &buf, grip, cache_mode); if (rc) log_error ("failed to unprotect the secret key: %s\n", gpg_strerror (rc)); diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 90b071d5e..6cc08f845 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -83,6 +83,7 @@ enum cmd_and_opt_values oLCmessages, oScdaemonProgram, oDefCacheTTL, + oDefCacheTTLSSH, oMaxCacheTTL, oUseStandardSocket, oNoUseStandardSocket, @@ -140,6 +141,7 @@ static ARGPARSE_OPTS opts[] = { { oDefCacheTTL, "default-cache-ttl", 4, N_("|N|expire cached PINs after N seconds")}, + { oDefCacheTTLSSH, "default-cache-ttl-ssh", 4, "@" }, { oMaxCacheTTL, "max-cache-ttl", 4, "@" }, { oIgnoreCacheForSigning, "ignore-cache-for-signing", 0, N_("do not use the PIN cache when signing")}, @@ -367,6 +369,7 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) opt.pinentry_program = NULL; opt.scdaemon_program = NULL; opt.def_cache_ttl = DEFAULT_CACHE_TTL; + opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL; opt.max_cache_ttl = MAX_CACHE_TTL; opt.ignore_cache_for_signing = 0; opt.allow_mark_trusted = 0; @@ -402,6 +405,7 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) case oDisableScdaemon: opt.disable_scdaemon = 1; break; case oDefCacheTTL: opt.def_cache_ttl = pargs->r.ret_ulong; break; + case oDefCacheTTLSSH: opt.def_cache_ttl_ssh = pargs->r.ret_ulong; break; case oMaxCacheTTL: opt.max_cache_ttl = pargs->r.ret_ulong; break; case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break; @@ -413,6 +417,7 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) default: return 0; /* not handled */ } + return 1; /* handled */ } @@ -1339,6 +1344,7 @@ handle_signal (int signo) case SIGUSR1: log_info ("SIGUSR1 received - printing internal information:\n"); pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); + agent_query_dump_state (); agent_scd_dump_state (); break; diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c index 7a93e58f8..42ce69697 100644 --- a/agent/pkdecrypt.c +++ b/agent/pkdecrypt.c @@ -66,7 +66,8 @@ agent_pkdecrypt (CTRL ctrl, const char *desc_text, log_printhex ("cipher: ", ciphertext, ciphertextlen); } rc = agent_key_from_file (ctrl, desc_text, - ctrl->keygrip, &shadow_info, 0, &s_skey); + ctrl->keygrip, &shadow_info, + CACHE_MODE_NORMAL, &s_skey); if (rc) { log_error ("failed to read the secret key\n"); diff --git a/agent/pksign.c b/agent/pksign.c index 3337e188c..2a355e43e 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -79,8 +79,8 @@ do_encode_md (const byte * md, size_t mdlen, int algo, gcry_sexp_t * r_hash, /* SIGN whatever information we have accumulated in CTRL and return the signature S-Expression. */ int -agent_pksign_do (CTRL ctrl, const char *desc_text, - gcry_sexp_t *signature_sexp, int ignore_cache) +agent_pksign_do (ctrl_t ctrl, const char *desc_text, + gcry_sexp_t *signature_sexp, cache_mode_t cache_mode) { gcry_sexp_t s_skey = NULL, s_sig = NULL; unsigned char *shadow_info = NULL; @@ -90,16 +90,16 @@ agent_pksign_do (CTRL ctrl, const char *desc_text, return gpg_error (GPG_ERR_NO_SECKEY); rc = agent_key_from_file (ctrl, desc_text, ctrl->keygrip, - &shadow_info, ignore_cache, &s_skey); + &shadow_info, cache_mode, &s_skey); if (rc) { log_error ("failed to read the secret key\n"); goto leave; } - if (! s_skey) + if (!s_skey) { - /* divert operation to the smartcard */ + /* Divert operation to the smartcard */ unsigned char *buf = NULL; size_t len = 0; @@ -128,7 +128,7 @@ agent_pksign_do (CTRL ctrl, const char *desc_text, } else { - /* no smartcard, but a private key */ + /* No smartcard, but a private key */ gcry_sexp_t s_hash = NULL; @@ -176,15 +176,15 @@ agent_pksign_do (CTRL ctrl, const char *desc_text, /* SIGN whatever information we have accumulated in CTRL and write it back to OUTFP. */ int -agent_pksign (CTRL ctrl, const char *desc_text, - membuf_t *outbuf, int ignore_cache) +agent_pksign (ctrl_t ctrl, const char *desc_text, + membuf_t *outbuf, cache_mode_t cache_mode) { gcry_sexp_t s_sig = NULL; char *buf = NULL; size_t len = 0; int rc = 0; - rc = agent_pksign_do (ctrl, desc_text, &s_sig, ignore_cache); + rc = agent_pksign_do (ctrl, desc_text, &s_sig, cache_mode); if (rc) goto leave; diff --git a/agent/query.c b/agent/query.c index d3b42a416..c1e4dbacc 100644 --- a/agent/query.c +++ b/agent/query.c @@ -49,7 +49,7 @@ #define LOCK_TIMEOUT (1*60) -static ASSUAN_CONTEXT entry_ctx = NULL; +static assuan_context_t entry_ctx = NULL; #ifdef USE_GNU_PTH static pth_mutex_t entry_lock; #endif @@ -82,6 +82,30 @@ initialize_module_query (void) +static void +dump_mutex_state (pth_mutex_t *m) +{ + if (!(m->mx_state & PTH_MUTEX_INITIALIZED)) + log_printf ("not_initialized"); + else if (!(m->mx_state & PTH_MUTEX_LOCKED)) + log_printf ("not_locked"); + else + log_printf ("locked tid=0x%lx count=%lu", (long)m->mx_owner, m->mx_count); +} + + +/* This function may be called to print infromation pertaining to the + current state of this module to the log. */ +void +agent_query_dump_state (void) +{ + log_info ("agent_query_dump_state: entry_lock="); + dump_mutex_state (&entry_lock); + log_printf ("\n"); + log_info ("agent_query_dump_state: entry_ctx=%p pid=%ld\n", + entry_ctx, (long)assuan_get_pid (entry_ctx)); +} + /* Unlock the pinentry so that another thread can start one and disconnect that pinentry - we do this after the unlock so that a @@ -90,8 +114,9 @@ initialize_module_query (void) static int unlock_pinentry (int rc) { - ASSUAN_CONTEXT ctx = entry_ctx; + assuan_context_t ctx = entry_ctx; + entry_ctx = NULL; #ifdef USE_GNU_PTH if (!pth_mutex_release (&entry_lock)) { @@ -100,7 +125,6 @@ unlock_pinentry (int rc) rc = gpg_error (GPG_ERR_INTERNAL); } #endif - entry_ctx = NULL; assuan_disconnect (ctx); return rc; } diff --git a/common/sysutils.c b/common/sysutils.c index 97fa23d95..a8f6f6f5d 100644 --- a/common/sysutils.c +++ b/common/sysutils.c @@ -105,7 +105,7 @@ enable_core_dumps (void) setrlimit (RLIMIT_CORE, &limit); return 1; /* We always return true because trhis function is merely a debugging aid. */ -#endif +# endif return 1; #endif } diff --git a/doc/ChangeLog b/doc/ChangeLog index f353bdf03..c4d263513 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,5 +1,9 @@ 2005-06-03 Werner Koch + * debugging.texi (Architecture Details): New section, mostly empty. + * gnupg-card-architecture.fig: New. + * Makefile.am: Rules to build png and eps versions. + * gpg-agent.texi (Agent UPDATESTARTUPTTY): New. 2005-05-17 Werner Koch diff --git a/doc/Makefile.am b/doc/Makefile.am index 988bbf849..fdcd62dc0 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -18,14 +18,34 @@ ## Process this file with automake to produce Makefile.in -EXTRA_DIST = gnupg-badge-openpgp.eps gnupg-badge-openpgp.jpg +EXTRA_DIST = gnupg-badge-openpgp.eps gnupg-badge-openpgp.jpg \ + gnupg-card-architecture.eps gnupg-card-architecture.png + +BUILT_SOURCES = gnupg-card-architecture.eps gnupg-card-architecture.png info_TEXINFOS = gnupg.texi + + gnupg_TEXINFOS = \ gpg.texi gpgsm.texi gpg-agent.texi scdaemon.texi assuan.texi \ tools.texi debugging.texi glossary.texi contrib.texi gpl.texi \ - sysnotes.texi + sysnotes.texi gnupg-card-architecture.fig DISTCLEANFILES = gnupg.tmp gnupg.ops + + +.fig.png: + fig2dev -L png `test -f '$<' || echo '$(srcdir)/'`$< $@ + +.fig.jpg: + fig2dev -L jpg `test -f '$<' || echo '$(srcdir)/'`$< $@ + +.fig.eps: + fig2dev -L eps `test -f '$<' || echo '$(srcdir)/'`$< $@ + +.fig.pdf: + fig2dev -L pdf `test -f '$<' || echo '$(srcdir)/'`$< $@ + + diff --git a/doc/debugging.texi b/doc/debugging.texi index 49ab70bde..429dbd407 100644 --- a/doc/debugging.texi +++ b/doc/debugging.texi @@ -18,6 +18,7 @@ solve the problem at hand. @menu * Debugging Tools:: Description of some useful tools * Common Problems:: Commonly seen problems. +* Architecture Details:: How the whole thing works internally. @end menu @@ -105,6 +106,49 @@ shell). Even for GUI based Pinentries; you should have set on how to do it. +@item SSH hangs while a popping up pinentry was expected + +SSH has no way to tell the gpg-agent what terminal or X display it is +running on. So when remotely logging into a box where a gpg-agent with +SSH support is running, the pinentry will get popped up on whatever +display t he gpg-agent has been started. To solve this problem you may +issue the command + +@smallexample +echo UPDATESTARTUPTTY | gpg-connect-agent +@end smallexample + +and the next pinentry will pop up on your display or screen. However, +you need to kill the running pinentry first because only one pinentry +may be running at once. If you plan to use ssh on a new display you +should issue the above command before invoking ssh or any other service +making use of ssh. + @end itemize + +@c ******************************************** +@c *** Architecture Details ***************** +@c ******************************************** +@node Architecture Details +@section How the whole thing works internally. + + +@menu +* gpg 1.4 vs. 1.9:: Relationship between the two branches. +@end menu + +@node gpg 1.4 vs. 1.9 +@subsection Relationship between the two branches. + +Here is a little picture showing how the components work together: + +@image{gnupg-card-architecture, 14cm} + +@noindent +Lets try to explain it: + +TO BE DONE. + + diff --git a/doc/gnupg-card-architecture.fig b/doc/gnupg-card-architecture.fig new file mode 100644 index 000000000..e5772cd0f --- /dev/null +++ b/doc/gnupg-card-architecture.fig @@ -0,0 +1,419 @@ +#FIG 3.2 Produced by xfig version 3.2.5-alpha5 +# Copyright 2005 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 2 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +Landscape +Center +Metric +A4 +100.00 +Single +-2 +1200 2 +0 32 #414541 +0 33 #808080 +0 34 #c0c0c0 +0 35 #c6b797 +0 36 #eff8ff +0 37 #dccba6 +0 38 #e0e0e0 +0 39 #8e8f8e +0 40 #aaaaaa +0 41 #555555 +0 42 #404040 +0 43 #868286 +0 44 #c7c3c7 +0 45 #e7e3e7 +0 46 #8e8e8e +0 47 #444444 +0 48 #868686 +0 49 #c7c7c7 +0 50 #666666 +0 51 #e2e2ee +0 52 #94949a +0 53 #dbdbdb +0 54 #a1a1b7 +0 55 #9c0000 +0 56 #ededed +0 57 #86acff +0 58 #7070ff +0 59 #bebebe +0 60 #515151 +0 61 #000049 +0 62 #797979 +0 63 #303430 +0 64 #c7b696 +0 65 #d7d7d7 +0 66 #aeaeae +0 67 #85807d +0 68 #d2d2d2 +0 69 #3a3a3a +0 70 #4573aa +0 71 #000000 +0 72 #e7e7e7 +0 73 #f7f7f7 +0 74 #d6d7d6 +0 75 #7b79a5 +0 76 #effbff +0 77 #9e9e9e +0 78 #717571 +0 79 #73758c +0 80 #414141 +0 81 #635dce +0 82 #565151 +0 83 #dd9d93 +0 84 #f1ece0 +0 85 #c3c3c3 +0 86 #e2c8a8 +0 87 #e1e1e1 +0 88 #da7a1a +0 89 #f1e41a +0 90 #887dc2 +0 91 #d6d6d6 +0 92 #8c8ca5 +0 93 #4a4a4a +0 94 #8c6b6b +0 95 #5a5a5a +0 96 #636363 +0 97 #b79b73 +0 98 #4193ff +0 99 #bf703b +0 100 #db7700 +0 101 #dab800 +0 102 #006400 +0 103 #5a6b3b +0 104 #d3d3d3 +0 105 #8e8ea4 +0 106 #f3b95d +0 107 #89996b +0 108 #646464 +0 109 #b7e6ff +0 110 #86c0ec +0 111 #bdbdbd +0 112 #d39552 +0 113 #98d2fe +0 114 #8c9c6b +0 115 #f76b00 +0 116 #5a6b39 +0 117 #8c9c6b +0 118 #8c9c7b +0 119 #184a18 +0 120 #adadad +0 121 #f7bd5a +0 122 #636b9c +0 123 #de0000 +0 124 #adadad +0 125 #f7bd5a +0 126 #adadad +0 127 #f7bd5a +0 128 #636b9c +0 129 #526b29 +0 130 #949494 +0 131 #006300 +0 132 #00634a +0 133 #7b844a +0 134 #e7bd7b +0 135 #a5b5c6 +0 136 #6b6b94 +0 137 #846b6b +0 138 #529c4a +0 139 #d6e7e7 +0 140 #526363 +0 141 #186b4a +0 142 #9ca5b5 +0 143 #ff9400 +0 144 #ff9400 +0 145 #00634a +0 146 #7b844a +0 147 #63737b +0 148 #e7bd7b +0 149 #184a18 +0 150 #f7bd5a +0 151 #dedede +0 152 #f3eed3 +0 153 #f5ae5d +0 154 #95ce99 +0 155 #b5157d +0 156 #eeeeee +0 157 #848484 +0 158 #7b7b7b +0 159 #005a00 +0 160 #e77373 +0 161 #ffcb31 +0 162 #29794a +0 163 #de2821 +0 164 #2159c6 +0 165 #f8f8f8 +0 166 #e6e6e6 +0 167 #21845a +0 168 #ff9408 +0 169 #007000 +0 170 #d00000 +0 171 #fed600 +0 172 #d82010 +0 173 #003484 +0 174 #d62010 +0 175 #389000 +0 176 #ba0000 +0 177 #003380 +0 178 #00a7bd +0 179 #ffc500 +0 180 #087bd0 +0 181 #fbc100 +0 182 #840029 +0 183 #07399c +0 184 #0063bd +0 185 #39acdf +0 186 #42c0e0 +0 187 #31ceff +0 188 #ffde00 +0 189 #085a00 +0 190 #ff2100 +0 191 #f75e08 +0 192 #ef7b08 +0 193 #ff8200 +0 194 #007d00 +0 195 #0000be +0 196 #757575 +0 197 #f3f3f3 +0 198 #d7d3d7 +0 199 #aeaaae +0 200 #c2c2c2 +0 201 #303030 +0 202 #515551 +0 203 #f7f3f7 +0 204 #717171 +6 9270 1980 13230 6570 +6 9471 3906 13014 5677 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 + 10540 4394 10540 3936 9471 3936 9471 4394 10540 4394 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 + 10387 5616 10387 5158 9471 5158 9471 5616 10387 5616 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 + 12984 5005 12984 4547 9471 4547 9471 5005 12984 5005 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 + 12984 5616 12984 5158 12067 5158 12067 5616 12984 5616 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 + 11701 5627 11701 5168 10784 5168 10784 5627 11701 5627 +4 0 0 50 -1 16 11 0.0000 4 173 835 9623 4242 OpenPGP\001 +4 0 0 50 -1 16 11 0.0000 4 132 2770 9776 4853 APDU and ISO-7816 access code\001 +4 0 0 50 -1 16 11 0.0000 4 132 448 9623 5464 CCID\001 +4 0 0 50 -1 16 11 0.0000 4 132 601 12220 5464 CT-API\001 +4 0 0 50 -1 16 11 0.0000 4 132 560 10957 5464 PC/SC\001 +-6 +6 10693 3906 13014 4394 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 + 11762 4394 11762 3936 10693 3936 10693 4394 11762 4394 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 + 12984 4394 12984 3936 11915 3936 11915 4394 12984 4394 +4 0 0 50 -1 16 11 0.0000 4 132 377 10998 4242 NKS\001 +4 0 0 50 -1 16 11 0.0000 4 132 804 12067 4242 PKCS#15\001 +-6 +2 4 0 2 0 6 60 -1 20 0.000 0 0 5 0 0 5 + 13137 2072 9318 2072 9318 5739 13137 5739 13137 2072 +2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 + 9318 3753 13137 3753 +2 4 0 2 0 6 60 -1 20 0.000 0 0 5 0 0 5 + 11691 6360 10774 6360 10774 5901 11691 5901 11691 6360 +2 1 2 2 0 7 50 -1 -1 4.500 0 0 -1 0 0 1 + 11762 5739 +2 1 1 2 0 7 50 -1 -1 6.000 0 0 -1 0 0 4 + 10693 5739 10693 6502 11762 6502 11762 5739 +4 0 0 50 -1 18 15 0.0000 4 183 1293 10540 2989 SCDaemon\001 +4 0 0 50 -1 16 11 0.0000 4 133 662 10896 6176 wrapper\001 +-6 +6 90 1980 4050 5760 +6 306 3906 3849 5677 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 + 1375 4394 1375 3936 306 3936 306 4394 1375 4394 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 + 1222 5616 1222 5158 306 5158 306 5616 1222 5616 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 + 3819 5005 3819 4547 306 4547 306 5005 3819 5005 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 + 3819 5616 3819 5158 2902 5158 2902 5616 3819 5616 +2 4 0 1 0 7 50 -1 -1 0.000 0 0 5 0 0 5 + 2536 5627 2536 5168 1619 5168 1619 5627 2536 5627 +4 0 0 50 -1 16 11 0.0000 4 173 835 458 4242 OpenPGP\001 +4 0 0 50 -1 16 11 0.0000 4 132 2770 611 4853 APDU and ISO-7816 access code\001 +4 0 0 50 -1 16 11 0.0000 4 132 448 458 5464 CCID\001 +4 0 0 50 -1 16 11 0.0000 4 132 601 3055 5464 CT-API\001 +4 0 0 50 -1 16 11 0.0000 4 132 560 1792 5464 PC/SC\001 +-6 +6 2139 3753 3208 4211 +2 4 0 1 0 7 50 -1 -1 4.000 0 0 5 0 0 5 + 3208 4211 3208 3753 2139 3753 2139 4211 3208 4211 +4 0 0 50 -1 16 11 0.0000 4 132 784 2291 4058 Gluecode\001 +-6 +2 1 2 2 0 7 50 -1 -1 4.500 0 0 -1 0 0 1 + 2597 5739 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 1 0 2 + 1 1 1.00 40.73 81.47 + 2139 4028 1405 4150 +2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 4 + 153 3753 1833 3753 1833 4364 3972 4364 +2 4 0 2 0 6 60 -1 20 0.000 0 0 5 0 0 5 + 3972 2072 153 2072 153 5739 3972 5739 3972 2072 +4 0 0 50 -1 18 15 0.0000 4 224 866 1375 2989 gpg 1.4\001 +-6 +6 4888 4058 5346 5433 +2 4 0 1 0 7 50 -1 -1 4.000 0 0 5 0 0 5 + 5346 5433 5346 4058 4888 4058 4888 5433 5346 5433 +4 0 0 50 -1 16 11 1.5708 4 132 611 5194 5128 Assuan\001 +-6 +6 4680 1980 8640 5760 +2 4 0 1 0 7 50 -1 -1 4.000 0 0 5 0 0 5 + 5346 3753 5346 2378 4888 2378 4888 3753 5346 3753 +2 4 0 2 0 6 60 -1 20 0.000 0 0 5 0 0 5 + 8554 5739 4735 5739 4735 2072 8554 2072 8554 5739 +4 0 0 50 -1 16 11 1.5708 4 173 804 5194 3447 ssh-agent\001 +-6 +6 5805 3447 7332 4975 +6 5957 3447 7179 4211 +2 4 0 1 0 7 50 -1 -1 4.000 0 0 5 0 0 5 + 7179 4211 7179 3447 5957 3447 5957 4211 7179 4211 +4 0 0 50 -1 16 11 0.0000 4 173 937 6110 3753 Private Key\001 +4 0 0 50 -1 16 11 0.0000 4 173 896 6110 4058 Operations\001 +-6 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 1 + 7195 4883 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 1 + 7195 4883 +2 4 0 1 0 7 50 -1 -1 4.000 0 0 5 0 0 5 + 7332 4975 7332 4517 6721 4517 6721 4975 7332 4975 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 1 1 2 + 1 1 1.00 40.73 81.47 + 1 1 1.00 40.73 81.47 + 6568 4211 7027 4517 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 1 1 2 + 1 1 1.00 40.73 81.47 + 1 1 1.00 40.73 81.47 + 6568 4211 6110 4517 +2 4 0 1 0 7 50 -1 -1 4.000 0 0 5 0 0 5 + 6416 4975 6416 4517 5805 4517 5805 4975 6416 4975 +4 0 0 50 -1 16 11 0.0000 4 132 397 6874 4822 Card\001 +4 0 0 50 -1 16 11 0.0000 4 132 356 5957 4822 Disk\001 +-6 +6 7638 3600 8401 4058 +2 4 0 1 0 7 50 -1 -1 4.000 0 0 5 0 0 5 + 8401 4058 8401 3600 7638 3600 7638 4058 8401 4058 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 1 + 7638 3814 +4 0 0 50 -1 16 11 0.0000 4 132 530 7790 3905 Cache\001 +-6 +6 9471 2225 9929 3600 +2 4 0 1 0 7 50 -1 -1 4.000 0 0 5 0 0 5 + 9929 3600 9929 2225 9471 2225 9471 3600 9929 3600 +4 0 0 50 -1 16 11 1.5708 4 132 611 9776 3294 Assuan\001 +-6 +6 6480 360 8640 1440 +2 4 0 2 0 6 60 -1 20 0.000 0 0 5 0 0 5 + 8554 1339 6568 1339 6568 423 8554 423 8554 1339 +4 0 0 50 -1 18 15 0.0000 4 234 967 7027 881 pinentry\001 +4 0 0 50 -1 16 10 0.0000 4 153 1375 6874 1187 (GTK+, Qt, Curses)\001 +-6 +6 10570 270 13137 1003 +2 1 1 1 1 2 50 -1 -1 4.000 0 0 -1 1 0 2 + 1 1 1.00 40.73 81.47 + 10632 331 11181 331 +2 1 0 2 1 2 50 -1 -1 6.000 0 0 -1 1 0 2 + 1 1 2.00 81.47 162.94 + 10632 637 11181 637 +2 1 0 1 0 2 50 -1 -1 4.000 0 0 -1 1 0 2 + 1 1 1.00 40.73 81.47 + 10632 942 11181 942 +4 0 0 50 -1 16 10 0.0000 4 163 1762 11365 392 Alternative access paths\001 +4 0 0 50 -1 16 10 0.0000 4 163 1426 11365 698 IPC (pipe or socket)\001 +4 0 0 50 -1 16 10 0.0000 4 122 1232 11365 1003 Internal data flow\001 +-6 +# Smartcard ID-1 +6 6840 6120 8550 7200 +6 7069 6526 7307 6746 +2 1 0 1 0 7 48 -1 -1 0.000 0 0 -1 0 0 2 + 7234 6691 7307 6691 +2 1 0 1 0 0 48 -1 20 0.000 0 0 -1 0 0 2 + 7069 6636 7143 6636 +2 1 0 1 0 7 48 -1 -1 0.000 0 0 -1 0 0 2 + 7069 6581 7143 6581 +2 1 0 1 0 7 48 -1 -1 0.000 0 0 -1 0 0 2 + 7069 6691 7143 6691 +2 1 0 1 0 7 48 -1 -1 0.000 0 0 -1 0 0 2 + 7143 6526 7143 6746 +2 1 0 1 0 7 48 -1 -1 0.000 0 0 -1 0 0 3 + 7307 6581 7234 6581 7234 6746 +2 1 0 1 0 7 48 -1 -1 0.000 0 0 -1 0 0 2 + 7234 6636 7307 6636 +2 4 0 1 0 31 49 -1 20 0.000 0 0 1 0 0 5 + 7069 6526 7307 6526 7307 6746 7069 6746 7069 6526 +-6 +2 4 0 1 -1 7 50 -1 20 0.000 0 0 1 0 0 5 + 8472 7185 6904 7185 6904 6197 8472 6197 8472 7185 +-6 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 1 0 2 + 1 1 1.00 40.73 81.47 + 5346 3142 5957 3753 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 1 0 2 + 1 1 1.00 40.73 81.47 + 5346 4669 5957 3905 +2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 1 1 2 + 1 1 1.00 40.73 81.47 + 1 1 1.00 40.73 81.47 + 7179 3814 7638 3814 +2 4 0 2 0 6 60 -1 20 0.000 0 0 5 0 0 5 + 11731 7480 10693 7480 10693 6991 11731 6991 11731 7480 +3 2 0 2 1 2 50 -1 -1 6.000 0 1 0 3 + 1 1 2.00 81.47 162.94 + 8022 3600 8096 2225 7513 1360 + 0.000 -1.000 0.000 +3 2 0 2 1 2 50 -1 -1 0.000 0 1 0 3 + 0 0 2.00 81.47 162.94 + 7332 4730 8737 4486 9471 2897 + 0.000 -1.000 0.000 +3 2 0 2 1 2 50 -1 -1 6.000 0 1 0 3 + 1 1 2.00 81.47 162.94 + 3238 3997 4216 4242 4888 4730 + 0.000 -1.000 0.000 +3 2 0 2 1 2 50 -1 -1 6.000 0 1 0 3 + 1 1 2.00 81.47 162.94 + 11243 6502 11304 6747 11181 6991 + 0.000 -1.000 0.000 +3 2 1 1 1 2 50 -1 -1 4.000 0 1 0 3 + 1 1 1.00 40.73 81.47 + 10693 7235 9471 7174 8493 6869 + 0.000 -1.000 0.000 +3 2 1 1 1 2 50 -1 -1 4.000 0 1 0 3 + 1 1 1.00 40.73 81.47 + 9898 5647 9532 6380 8493 6563 + 0.000 -1.000 0.000 +3 2 1 1 1 2 50 -1 -1 4.000 0 1 0 3 + 1 1 1.00 40.73 81.47 + 12465 5647 11731 6624 8493 6747 + 0.000 -1.000 0.000 +3 2 1 1 1 2 50 -1 -1 4.000 0 1 0 3 + 1 1 1.00 40.73 81.47 + 2077 5647 3177 6502 6843 6624 + 0.000 -1.000 0.000 +3 2 1 1 1 2 50 -1 -1 4.000 0 1 0 3 + 1 1 1.00 40.73 81.47 + 733 5647 2444 6808 6843 6747 + 0.000 -1.000 0.000 +3 2 1 1 1 2 50 -1 -1 4.000 0 1 0 3 + 1 1 1.00 40.73 81.47 + 3361 5647 4155 6319 6843 6502 + 0.000 -1.000 0.000 +4 0 0 50 -1 18 15 0.0000 4 214 1191 5957 2989 gpg-agent\001 +4 0 0 50 -1 16 11 0.0000 4 173 387 10998 7297 pcsd\001 diff --git a/doc/gnupg.texi b/doc/gnupg.texi index 4c30980b3..d92f01cd9 100644 --- a/doc/gnupg.texi +++ b/doc/gnupg.texi @@ -86,14 +86,15 @@ section entitled ``Copying''. @insertcopying @end titlepage - +@ifnothtml @summarycontents @contents @page +@end ifnothtml @ifnottex @node Top -@top The GNU Privacy Guard +@top @insertcopying This manual documents how to use the GNU Privacy Guard system as well as @@ -120,6 +121,20 @@ the administration and the architecture. * Index:: Index of concepts and symbol names. @end menu +@ifhtml + +@center @image{gnupg-badge-openpgp,6cm,,The GnuPG Logo} + +@end ifhtml + + +@ifhtml +@page +@summarycontents +@contents +@end ifhtml + + @include gpg.texi @include gpgsm.texi @include gpg-agent.texi diff --git a/doc/gpg-agent.texi b/doc/gpg-agent.texi index 5e8c19468..bad6639e2 100644 --- a/doc/gpg-agent.texi +++ b/doc/gpg-agent.texi @@ -42,7 +42,8 @@ else fi @end smallexample -The new option @option{--write-env-file} may be used instead. +@noindent +Note that the new option @option{--write-env-file} may be used instead. @noindent @@ -289,6 +290,11 @@ control this behaviour but this command line option takes precedence. Set the time a cache entry is valid to @var{n} seconds. The default are 600 seconds. +@item --default-cache-ttl-ssh @var{n} +@opindex default-cache-ttl +Set the time a cache entry used for SSH keys is valid to @var{n} +seconds. The default are 600 seconds. + @item --max-cache-ttl @var{n} @opindex max-cache-ttl Set the maximum time a cache entry is valid to @var{n} seconds. After @@ -506,12 +512,13 @@ are still pending, a shutdown is forced. @cpindex SIGINT Shuts down the process immediately. - @item SIGUSR1 -@itemx SIGUSR2 @cpindex SIGUSR1 +Dump internal information to the log file. + +@item SIGUSR2 @cpindex SIGUSR2 -These signals are used for internal purposes. +This signal is used for internal purposes. @end table @@ -523,12 +530,44 @@ These signals are used for internal purposes. @c man begin EXAMPLES +The usual way to invoke @command{gpg-agent} is + @example $ eval `gpg-agent --daemon` @end example @c man end +An alternative way is by replacing @command{ssh-agent} with +@command{gpg-agent}. If for example @command{ssh-agent} is started as +part of the Xsession intialization you may simply replace +@command{ssh-agent} by a script like: + +@cartouche +@example +#!/bin/sh + +exec /usr/local/bin/gpg-agent --enable-ssh-support --daemon \ + --write-env-file $@{HOME@}/.gpg-agent-info "$@@" +@end example +@end cartouche + +@noindent +and add something like (for Bourne shells) + +@cartouche +@example + if [ -f "$@{HOME@}/.gpg-agent-info" ]; then + . "$@{HOME@}/.gpg-agent-info" + export GPG_AGENT_INFO + export SSH_AUTH_SOCK + export SSH_AGENT_PID + fi +@end example +@end cartouche + +@noindent +to your shell initialization file (e.g. @file{~/.bashrc}). @c @c Assuan Protocol diff --git a/doc/scdaemon.texi b/doc/scdaemon.texi index 134ca40df..d4a21b5ce 100644 --- a/doc/scdaemon.texi +++ b/doc/scdaemon.texi @@ -181,6 +181,14 @@ protocol. Note that this option may reveal sensitive data. This option disables all ticker functions like checking for card insertions. +@item --debug-allow-core-dump +@opindex debug-allow-core-dump +For security reasons we won't create a core dump when the process +aborts. For debugging purposes it is sometimes better to allow core +dump. This options enables it and also changes the working directory to +@file{/tmp} when running in @option{--server} mode. + + @item --no-detach @opindex no-detach Don't detach the process from the console. This is manly usefule for diff --git a/scd/ChangeLog b/scd/ChangeLog index da433e2f8..0f7e4d2fa 100644 --- a/scd/ChangeLog +++ b/scd/ChangeLog @@ -1,3 +1,7 @@ +2005-06-06 Werner Koch + + * scdaemon.c (main): New option --debug-allow-core-dump. + 2005-06-03 Werner Koch * scdaemon.c (handle_connections): Make sure that the signals we diff --git a/scd/command.c b/scd/command.c index 287f8c921..a308078d3 100644 --- a/scd/command.c +++ b/scd/command.c @@ -70,7 +70,7 @@ struct server_local_s { struct server_local_s *next_session; /* This object is usually assigned to a CTRL object (which is - globally visible). While enumeratin all sessions we sometimes + globally visible). While enumerating all sessions we sometimes need to access data of the CTRL object; thus we keep a backpointer here. */ ctrl_t ctrl_backlink; @@ -860,6 +860,7 @@ cmd_getattr (assuan_context_t ctx, char *line) /* FIXME: Applications should not return sensistive data if the card is locked. */ rc = app_getattr (ctrl->app_ctx, ctrl, keyword); + xfree (keyword); TEST_CARD_REMOVAL (ctrl, rc); return map_to_assuan_status (rc); diff --git a/scd/scdaemon.c b/scd/scdaemon.c index 5b5e09176..c75e87a62 100644 --- a/scd/scdaemon.c +++ b/scd/scdaemon.c @@ -67,6 +67,7 @@ enum cmd_and_opt_values oDebugAll, oDebugLevel, oDebugWait, + oDebugAllowCoreDump, oDebugCCIDDriver, oNoGreeting, oNoOptions, @@ -110,6 +111,7 @@ static ARGPARSE_OPTS opts[] = { { oDebugAll, "debug-all" ,0, "@"}, { oDebugLevel, "debug-level" ,2, "@"}, { oDebugWait,"debug-wait",1, "@"}, + { oDebugAllowCoreDump, "debug-allow-core-dump", 0, "@" }, { oDebugCCIDDriver, "debug-ccid-driver", 0, "@"}, { oDebugDisableTicker, "debug-disable-ticker", 0, "@"}, { oNoDetach, "no-detach" ,0, N_("do not detach from the console")}, @@ -318,6 +320,7 @@ main (int argc, char **argv ) int debug_wait = 0; int gpgconf_list = 0; const char *config_filename = NULL; + int allow_coredump = 0; set_strusage (my_strusage); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); @@ -448,6 +451,10 @@ main (int argc, char **argv ) case oDebugAll: opt.debug = ~0; break; case oDebugLevel: debug_level = pargs.r.ret_str; break; case oDebugWait: debug_wait = pargs.r.ret_int; break; + case oDebugAllowCoreDump: + enable_core_dumps (); + allow_coredump = 1; + break; case oDebugCCIDDriver: ccid_set_debug_level (ccid_set_debug_level (-1)+1); break; @@ -604,6 +611,17 @@ main (int argc, char **argv ) sigaction (SIGPIPE, &sa, NULL); } + /* If --debug-allow-core-dump has been given we also need to + switch the working directory to a place where we can actually + write. */ + if (allow_coredump) + { + if (chdir("/tmp")) + log_debug ("chdir to `/tmp' failed: %s\n", strerror (errno)); + else + log_debug ("changed working directory to `/tmp'\n"); + } + /* In multi server mode we need to listen on an additional socket. Create that socket now before starting the handler for the pipe connection. This allows that handler to send -- cgit From deeba405a9a5868ea478db5003be6335ab9aac6f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 16 Jun 2005 08:12:03 +0000 Subject: gcc-4 defaults forced me to edit many many files to get rid of the char * vs. unsigned char * warnings. The GNU coding standards used to say that these mismatches are okay and better than a bunch of casts. Obviously this has changed now. --- agent/ChangeLog | 46 ++++++++++++++++++++++ agent/agent.h | 15 ++++---- agent/cache.c | 20 +++++++--- agent/call-scd.c | 19 ++++------ agent/command-ssh.c | 75 ++++++++++++++++-------------------- agent/command.c | 4 +- agent/divert-scd.c | 9 +++-- agent/findkey.c | 4 +- agent/genkey.c | 2 +- agent/gpg-agent.c | 11 ++++-- agent/minip12.c | 2 + agent/pkdecrypt.c | 2 +- agent/pksign.c | 2 +- agent/protect-tool.c | 21 +++++----- agent/protect.c | 29 ++++++++------ agent/query.c | 9 +++-- common/ChangeLog | 26 ++++++++++++- common/estream.c | 21 +++++----- common/estream.h | 8 ++-- common/iobuf.c | 19 ++++++---- common/iobuf.h | 4 +- common/miscellaneous.c | 2 +- common/sexputil.c | 9 +++-- common/simple-pwquery.c | 2 +- common/ttyio.c | 2 +- common/util.h | 2 +- doc/gpg-agent.texi | 8 +++- g10/ChangeLog | 5 +++ g10/g10.c | 4 +- g10/misc.c | 5 ++- jnlib/ChangeLog | 13 +++++++ jnlib/argparse.c | 2 +- jnlib/logging.c | 5 ++- jnlib/stringhelp.c | 97 ++++++++++++++++++++++++++++------------------- jnlib/stringhelp.h | 9 ++--- jnlib/utf8conv.c | 21 +++++----- kbx/ChangeLog | 11 ++++++ kbx/kbxutil.c | 2 +- kbx/keybox-blob.c | 45 ++++++++++++---------- kbx/keybox-defs.h | 5 ++- kbx/keybox-file.c | 4 +- scd/apdu.c | 2 +- scd/app-help.c | 2 +- scd/app-openpgp.c | 27 ++++++------- scd/app-p15.c | 28 +++++++------- scd/app.c | 2 +- scd/ccid-driver.c | 14 +++---- scd/command.c | 10 ++--- scd/iso7816.c | 18 +++++---- scd/pcsc-wrapper.c | 4 +- sm/ChangeLog | 32 ++++++++++++++++ sm/base64.c | 14 ++++--- sm/call-agent.c | 25 ++++++------ sm/certcheck.c | 14 ++++--- sm/certdump.c | 28 +++++++------- sm/certreqgen.c | 9 +++-- sm/delete.c | 4 +- sm/encrypt.c | 12 +++--- sm/fingerprint.c | 17 +++++---- sm/gpgsm.h | 10 ++--- sm/keydb.c | 9 +++-- sm/keylist.c | 4 +- sm/server.c | 4 +- sm/sign.c | 2 +- tools/ChangeLog | 5 +++ tools/gpg-connect-agent.c | 2 +- tools/gpgconf-comp.c | 2 +- tools/gpgkey2ssh.c | 3 ++ tools/watchgnupg.c | 2 +- 69 files changed, 558 insertions(+), 348 deletions(-) (limited to 'agent/command.c') diff --git a/agent/ChangeLog b/agent/ChangeLog index 1a157fa52..055dbe53e 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,49 @@ +2005-06-16 Werner Koch + + * protect-tool.c (make_advanced): Makde RESULT a plain char. + * call-scd.c (unescape_status_string): Need to cast unsigned char* + for strcpy. + (agent_card_pksign): Made arg R_BUF an unsigned char**. + * divert-scd.c (divert_pksign): Made SIGVAL unsigned char*. + (encode_md_for_card): Initialize R_VAL and R_LEN. + * genkey.c (store_key): Made BUF unsigned. + * protect.c (do_encryption): Ditto. + (do_encryption): Made arg PROTBEGIN unsigned. Initialize RESULT + and RESULTLEN even on error. + (merge_lists): Need to cast unsigned char * for strcpy. Initialize + RESULTand RESULTLEN even on error. + (agent_unprotect): Likewise for strtoul. + (make_shadow_info): Made P and INFO plain char. + (agent_shadow_key): Made P plain char. + +2005-06-15 Werner Koch + + * query.c (agent_get_passphrase): Made HEXSTRING a char*. + * command-ssh.c (ssh_key_grip): Made arg BUFFER unsigned. + (ssh_key_grip): Simplified. + (data_sign): Initialize variables with the definition. + (ssh_convert_key_to_blob): Make sure that BLOB and BLOB_SIZE + are set to NULL on error. Cool, gcc-4 detects uninitialized stuff + beyond function boundaries; well it can't know that we do error + proper error handling so that this was not a real error. + (file_to_buffer): Likewise for BUFFER and BUFFER_N. + (data_sign): Likewise for SIG and SIG_N. + (stream_read_byte): Set B to a value even on error. + * command.c (cmd_genkey): Changed VALUE to char. + (cmd_readkey): Cast arg for gcry_sexp_sprint. + * agent.h (struct server_control_s): Made KEYGRIP unsigned. + +2005-06-13 Werner Koch + + * command-ssh.c (start_command_handler_ssh): Reset the SCD. + +2005-06-09 Werner Koch + + * gpg-agent.c (create_socket_name): New option --max-cache-ttl-ssh. + * cache.c (housekeeping): Use it. + (agent_put_cache): Use a switch to get the default ttl so that it + is easier to add more cases. + 2005-06-06 Werner Koch * gpg-agent.c: New option --default-cache-ttl-ssh. diff --git a/agent/agent.h b/agent/agent.h index 350e5c0d2..7a646a85f 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -71,9 +71,10 @@ struct { int no_grab; /* Don't let the pinentry grab the keyboard */ /* The default and maximum TTL of cache entries. */ - unsigned long def_cache_ttl; /* Normal. */ - unsigned long def_cache_ttl_ssh; /* SSH. */ - unsigned long max_cache_ttl; + unsigned long def_cache_ttl; /* Default. */ + unsigned long def_cache_ttl_ssh; /* for SSH. */ + unsigned long max_cache_ttl; /* Default. */ + unsigned long max_cache_ttl_ssh; /* for SSH. */ int running_detached; /* We are running detached from the tty. */ @@ -107,8 +108,8 @@ struct server_local_s; struct scd_local_s; /* Collection of data per session (aka connection). */ -struct server_control_s { - +struct server_control_s +{ /* Private data of the server (command.c). */ struct server_local_s *server_local; @@ -128,7 +129,7 @@ struct server_control_s { int valuelen; int raw_value: 1; } digest; - char keygrip[20]; + unsigned char keygrip[20]; int have_keygrip; int use_auth_call; /* Hack to send the PKAUTH command instead of the @@ -289,7 +290,7 @@ int agent_card_pksign (ctrl_t ctrl, int (*getpin_cb)(void *, const char *, char*, size_t), void *getpin_cb_arg, const unsigned char *indata, size_t indatalen, - char **r_buf, size_t *r_buflen); + unsigned char **r_buf, size_t *r_buflen); int agent_card_pkdecrypt (ctrl_t ctrl, const char *keyid, int (*getpin_cb)(void *, const char *, char*,size_t), diff --git a/agent/cache.c b/agent/cache.c index a032b4fa7..32b6ac0c7 100644 --- a/agent/cache.c +++ b/agent/cache.c @@ -103,10 +103,17 @@ housekeeping (void) } /* Second, make sure that we also remove them based on the created stamp so - that the user has to enter it from time to time. We do this every hour */ + that the user has to enter it from time to time. */ for (r=thecache; r; r = r->next) { - if (!r->lockcount && r->pw && r->created + opt.max_cache_ttl < current) + unsigned long maxttl; + + switch (r->cache_mode) + { + case CACHE_MODE_SSH: maxttl = opt.max_cache_ttl_ssh; break; + default: maxttl = opt.max_cache_ttl; break; + } + if (!r->lockcount && r->pw && r->created + maxttl < current) { if (DBG_CACHE) log_debug (" expired `%s' (%lus after creation)\n", @@ -203,10 +210,11 @@ agent_put_cache (const char *key, cache_mode_t cache_mode, if (!ttl) { - if (cache_mode == CACHE_MODE_SSH) - ttl = opt.def_cache_ttl_ssh; - else - ttl = opt.def_cache_ttl; + switch(cache_mode) + { + case CACHE_MODE_SSH: ttl = opt.def_cache_ttl_ssh; break; + default: ttl = opt.def_cache_ttl; break; + } } if (!ttl || cache_mode == CACHE_MODE_IGNORE) return 0; diff --git a/agent/call-scd.c b/agent/call-scd.c index 4dff8e3c1..7a623fda4 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -465,7 +465,7 @@ unescape_status_string (const unsigned char *s) { char *buffer, *d; - buffer = d = xtrymalloc (strlen (s)+1); + buffer = d = xtrymalloc (strlen ((const char*)s)+1); if (!buffer) return NULL; while (*s) @@ -666,7 +666,7 @@ agent_card_pksign (ctrl_t ctrl, int (*getpin_cb)(void *, const char *, char*, size_t), void *getpin_cb_arg, const unsigned char *indata, size_t indatalen, - char **r_buf, size_t *r_buflen) + unsigned char **r_buf, size_t *r_buflen) { int rc, i; char *p, line[ASSUAN_LINELENGTH]; @@ -714,14 +714,11 @@ agent_card_pksign (ctrl_t ctrl, /* Create an S-expression from it which is formatted like this: "(7:sig-val(3:rsa(1:sSIGBUFLEN:SIGBUF)))" */ *r_buflen = 21 + 11 + sigbuflen + 4; - *r_buf = xtrymalloc (*r_buflen); - if (!*r_buf) - { - gpg_error_t tmperr = out_of_core (); - xfree (*r_buf); - return unlock_scd (ctrl, tmperr); - } - p = stpcpy (*r_buf, "(7:sig-val(3:rsa(1:s" ); + p = xtrymalloc (*r_buflen); + *r_buf = (unsigned char*)p; + if (!p) + return unlock_scd (ctrl, out_of_core ()); + p = stpcpy (p, "(7:sig-val(3:rsa(1:s" ); sprintf (p, "%u:", (unsigned int)sigbuflen); p += strlen (p); memcpy (p, sigbuf, sigbuflen); @@ -895,7 +892,7 @@ card_getattr_cb (void *opaque, const char *line) if (keywordlen == parm->keywordlen && !memcmp (keyword, parm->keyword, keywordlen)) { - parm->data = unescape_status_string (line); + parm->data = unescape_status_string ((const unsigned char*)line); if (!parm->data) parm->error = errno; } diff --git a/agent/command-ssh.c b/agent/command-ssh.c index 870afe059..a43fee24f 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -297,6 +297,7 @@ stream_read_byte (estream_t stream, unsigned char *b) err = gpg_error_from_errno (errno); else err = gpg_error (GPG_ERR_EOF); + *b = 0; } else { @@ -604,6 +605,9 @@ file_to_buffer (const char *filename, unsigned char **buffer, size_t *buffer_n) gpg_error_t err; int ret; + *buffer = NULL; + *buffer_n = 0; + buffer_new = NULL; err = 0; @@ -1381,6 +1385,9 @@ ssh_convert_key_to_blob (unsigned char **blob, size_t *blob_size, gpg_error_t err; unsigned int i; + *blob = NULL; + *blob_size = 0; + blob_new = NULL; stream = NULL; err = 0; @@ -1535,20 +1542,12 @@ ssh_read_key_public_from_blob (unsigned char *blob, size_t blob_size, S-Expression KEY and writes it to BUFFER, which must be large enough to hold it. Returns usual error code. */ static gpg_error_t -ssh_key_grip (gcry_sexp_t key, char *buffer) +ssh_key_grip (gcry_sexp_t key, unsigned char *buffer) { - gpg_error_t err; - char *p; + if (!gcry_pk_get_keygrip (key, buffer)) + return gpg_error (GPG_ERR_INTERNAL); - /* FIXME: unsigned vs. signed. */ - - p = gcry_pk_get_keygrip (key, buffer); - if (! p) - err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ - else - err = 0; - - return err; + return 0; } /* Converts the secret key KEY_SECRET into a public key, storing it in @@ -1654,7 +1653,7 @@ card_key_available (ctrl_t ctrl, gcry_sexp_t *r_pk, char **cardsn) } pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL); - err = gcry_sexp_sscan (&s_pk, NULL, pkbuf, pkbuflen); + err = gcry_sexp_sscan (&s_pk, NULL, (char*)pkbuf, pkbuflen); if (err) { log_error ("failed to build S-Exp from received card key: %s\n", @@ -1877,7 +1876,7 @@ ssh_handler_request_identities (ctrl_t ctrl, if (err) goto out; - err = gcry_sexp_sscan (&key_secret, NULL, buffer, buffer_n); + err = gcry_sexp_sscan (&key_secret, NULL, (char*)buffer, buffer_n); if (err) goto out; @@ -1984,14 +1983,14 @@ data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder, unsigned char **sig, size_t *sig_n) { gpg_error_t err; - gcry_sexp_t signature_sexp; - estream_t stream; - gcry_sexp_t valuelist; - gcry_sexp_t sublist; - gcry_mpi_t sig_value; - unsigned char *sig_blob; - size_t sig_blob_n; - char *identifier; + gcry_sexp_t signature_sexp = NULL; + estream_t stream = NULL; + gcry_sexp_t valuelist = NULL; + gcry_sexp_t sublist = NULL; + gcry_mpi_t sig_value = NULL; + unsigned char *sig_blob = NULL;; + size_t sig_blob_n = 0; + char *identifier = NULL; const char *identifier_raw; size_t identifier_n; ssh_key_type_spec_t spec; @@ -1999,17 +1998,10 @@ data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder, unsigned int i; const char *elems; size_t elems_n; - gcry_mpi_t *mpis; + gcry_mpi_t *mpis = NULL; - signature_sexp = NULL; - identifier = NULL; - valuelist = NULL; - sublist = NULL; - sig_blob = NULL; - sig_blob_n = 0; - stream = NULL; - sig_value = NULL; - mpis = NULL; + *sig = NULL; + *sig_n = 0; ctrl->use_auth_call = 1; err = agent_pksign_do (ctrl, @@ -2119,7 +2111,7 @@ data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder, if (err) goto out; - *sig = (char *) sig_blob; + *sig = sig_blob; *sig_n = sig_blob_n; out: @@ -2684,7 +2676,7 @@ ssh_request_process (ctrl_t ctrl, estream_t stream_sock) secure memory, since we never give out secret keys. FIXME: This is a pretty good DoS. We only have a limited amount - of secure memory, we can't trhow hin everything we get from a + of secure memory, we can't throw in everything we get from a client -wk */ /* Retrieve request. */ @@ -2824,7 +2816,6 @@ start_command_handler_ssh (int sock_client) struct server_control_s ctrl; estream_t stream_sock; gpg_error_t err; - int bad; int ret; /* Setup control structure. */ @@ -2868,15 +2859,15 @@ start_command_handler_ssh (int sock_client) goto out; } - while (1) - { - bad = ssh_request_process (&ctrl, stream_sock); - if (bad) - break; - }; + /* Main processing loop. */ + while ( !ssh_request_process (&ctrl, stream_sock) ) + ; - out: + /* Reset the SCD in case it has been used. */ + agent_reset_scd (&ctrl); + + out: if (stream_sock) es_fclose (stream_sock); diff --git a/agent/command.c b/agent/command.c index ebf3a8220..c39bcc6ab 100644 --- a/agent/command.c +++ b/agent/command.c @@ -168,7 +168,7 @@ parse_keygrip (ASSUAN_CONTEXT ctx, const char *string, unsigned char *buf) if (n != 20) return set_error (Parameter_Error, "invalid length of keygrip"); - for (p=string, n=0; n < 20; p += 2, n++) + for (p=(const unsigned char*)string, n=0; n < 20; p += 2, n++) buf[n] = xtoi_2 (p); return 0; @@ -494,7 +494,7 @@ cmd_genkey (ASSUAN_CONTEXT ctx, char *line) init_membuf (&outbuf, 512); - rc = agent_genkey (ctrl, value, valuelen, &outbuf); + rc = agent_genkey (ctrl, (char*)value, valuelen, &outbuf); xfree (value); if (rc) clear_outbuf (&outbuf); diff --git a/agent/divert-scd.c b/agent/divert-scd.c index 41a5dfcda..9d2fa446c 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -139,10 +139,13 @@ static int encode_md_for_card (const unsigned char *digest, size_t digestlen, int algo, unsigned char **r_val, size_t *r_len) { - byte *frame; - byte asn[100]; + unsigned char *frame; + unsigned char asn[100]; size_t asnlen; + *r_val = NULL; + *r_len = 0; + asnlen = DIM(asn); if (gcry_md_algo_info (algo, GCRYCTL_GET_ASNOID, asn, &asnlen)) { @@ -295,7 +298,7 @@ divert_pksign (CTRL ctrl, int rc; char *kid; size_t siglen; - char *sigval; + unsigned char *sigval; unsigned char *data; size_t ndata; diff --git a/agent/findkey.c b/agent/findkey.c index 56433c9c4..1cb7efaf3 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -345,7 +345,7 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result) } /* Convert the file into a gcrypt S-expression object. */ - rc = gcry_sexp_sscan (&s_skey, &erroff, buf, buflen); + rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen); xfree (fname); fclose (fp); xfree (buf); @@ -500,7 +500,7 @@ agent_key_from_file (ctrl_t ctrl, const char *desc_text, } buflen = gcry_sexp_canon_len (buf, 0, NULL, NULL); - rc = gcry_sexp_sscan (&s_skey, &erroff, buf, buflen); + rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen); wipememory (buf, buflen); xfree (buf); if (rc) diff --git a/agent/genkey.c b/agent/genkey.c index e07518d5a..d0319f7b4 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -33,7 +33,7 @@ static int store_key (gcry_sexp_t private, const char *passphrase, int force) { int rc; - char *buf; + unsigned char *buf; size_t len; unsigned char grip[20]; diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 6cc08f845..8732c98d7 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -85,6 +85,7 @@ enum cmd_and_opt_values oDefCacheTTL, oDefCacheTTLSSH, oMaxCacheTTL, + oMaxCacheTTLSSH, oUseStandardSocket, oNoUseStandardSocket, @@ -143,6 +144,7 @@ static ARGPARSE_OPTS opts[] = { N_("|N|expire cached PINs after N seconds")}, { oDefCacheTTLSSH, "default-cache-ttl-ssh", 4, "@" }, { oMaxCacheTTL, "max-cache-ttl", 4, "@" }, + { oMaxCacheTTLSSH, "max-cache-ttl-ssh", 4, "@" }, { oIgnoreCacheForSigning, "ignore-cache-for-signing", 0, N_("do not use the PIN cache when signing")}, { oAllowMarkTrusted, "allow-mark-trusted", 0, @@ -156,8 +158,9 @@ static ARGPARSE_OPTS opts[] = { }; -#define DEFAULT_CACHE_TTL (10*60) /* 10 minutes */ -#define MAX_CACHE_TTL (120*60) /* 2 hours */ +#define DEFAULT_CACHE_TTL (10*60) /* 10 minutes */ +#define DEFAULT_CACHE_TTL_SSH (30*60) /* 30 minutes */ +#define MAX_CACHE_TTL (120*60) /* 2 hours */ /* flag to indicate that a shutdown was requested */ @@ -369,8 +372,9 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) opt.pinentry_program = NULL; opt.scdaemon_program = NULL; opt.def_cache_ttl = DEFAULT_CACHE_TTL; - opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL; + opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL_SSH; opt.max_cache_ttl = MAX_CACHE_TTL; + opt.max_cache_ttl_ssh = MAX_CACHE_TTL; opt.ignore_cache_for_signing = 0; opt.allow_mark_trusted = 0; opt.disable_scdaemon = 0; @@ -407,6 +411,7 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) case oDefCacheTTL: opt.def_cache_ttl = pargs->r.ret_ulong; break; case oDefCacheTTLSSH: opt.def_cache_ttl_ssh = pargs->r.ret_ulong; break; case oMaxCacheTTL: opt.max_cache_ttl = pargs->r.ret_ulong; break; + case oMaxCacheTTLSSH: opt.max_cache_ttl_ssh = pargs->r.ret_ulong; break; case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break; diff --git a/agent/minip12.c b/agent/minip12.c index 5ca85033d..31be15373 100644 --- a/agent/minip12.c +++ b/agent/minip12.c @@ -1552,6 +1552,8 @@ p12_build (gcry_mpi_t *kparms, unsigned char *cert, size_t certlen, struct buffer_s seqlist[2]; int seqlistidx = 0; + n = buflen = 0; /* (avoid compiler warning). */ + if (cert && certlen) { /* Encode the certificate. */ diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c index 42ce69697..1d64c1b15 100644 --- a/agent/pkdecrypt.c +++ b/agent/pkdecrypt.c @@ -52,7 +52,7 @@ agent_pkdecrypt (CTRL ctrl, const char *desc_text, goto leave; } - rc = gcry_sexp_sscan (&s_cipher, NULL, ciphertext, ciphertextlen); + rc = gcry_sexp_sscan (&s_cipher, NULL, (char*)ciphertext, ciphertextlen); if (rc) { log_error ("failed to convert ciphertext: %s\n", gpg_strerror (rc)); diff --git a/agent/pksign.c b/agent/pksign.c index 2a355e43e..e9df19351 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -117,7 +117,7 @@ agent_pksign_do (ctrl_t ctrl, const char *desc_text, len = gcry_sexp_canon_len (buf, 0, NULL, NULL); assert (len); - rc = gcry_sexp_sscan (&s_sig, NULL, buf, len); + rc = gcry_sexp_sscan (&s_sig, NULL, (char*)buf, len); xfree (buf); if (rc) { diff --git a/agent/protect-tool.c b/agent/protect-tool.c index e8f1d2c10..5f59d5e06 100644 --- a/agent/protect-tool.c +++ b/agent/protect-tool.c @@ -239,9 +239,9 @@ make_advanced (const unsigned char *buf, size_t buflen) int rc; size_t erroff, len; gcry_sexp_t sexp; - unsigned char *result; + char *result; - rc = gcry_sexp_sscan (&sexp, &erroff, buf, buflen); + rc = gcry_sexp_sscan (&sexp, &erroff, (const char*)buf, buflen); if (rc) { log_error ("invalid canonical S-Expression (off=%u): %s\n", @@ -378,7 +378,7 @@ read_and_protect (const char *fname) xfree (result); if (!p) return; - result = p; + result = (unsigned char*)p; resultlen = strlen (p); } @@ -417,7 +417,7 @@ read_and_unprotect (const char *fname) xfree (result); if (!p) return; - result = p; + result = (unsigned char*)p; resultlen = strlen (p); } @@ -434,12 +434,13 @@ read_and_shadow (const char *fname) unsigned char *key; unsigned char *result; size_t resultlen; + unsigned char dummy_info[] = "(8:313233342:43)"; key = read_key (fname); if (!key) return; - rc = agent_shadow_key (key, "(8:313233342:43)", &result); + rc = agent_shadow_key (key, dummy_info, &result); xfree (key); if (rc) { @@ -455,7 +456,7 @@ read_and_shadow (const char *fname) xfree (result); if (!p) return; - result = p; + result = (unsigned char*)p; resultlen = strlen (p); } @@ -682,7 +683,7 @@ import_p12_file (const char *fname) if (!buf) return; - kparms = p12_parse (buf, buflen, (pw=get_passphrase (2)), + kparms = p12_parse ((unsigned char*)buf, buflen, (pw=get_passphrase (2)), import_p12_cert_cb, NULL); release_passphrase (pw); xfree (buf); @@ -773,7 +774,7 @@ import_p12_file (const char *fname) xfree (result); if (!p) return; - result = p; + result = (unsigned char*)p; resultlen = strlen (p); } @@ -932,7 +933,7 @@ export_p12_file (const char *fname) if (opt_have_cert) { - cert = read_file ("-", &certlen); + cert = (unsigned char*)read_file ("-", &certlen); if (!cert) { wipememory (key, keylen_for_wipe); @@ -1040,7 +1041,7 @@ percent_plus_unescape (unsigned char *string) static char * percent_plus_unescape_string (char *string) { - unsigned char *p = string; + unsigned char *p = (unsigned char*)string; size_t n; n = percent_plus_unescape (p); diff --git a/agent/protect.c b/agent/protect.c index 658c8c529..45bdae496 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -134,19 +134,22 @@ calculate_mic (const unsigned char *plainkey, unsigned char *sha1hash) */ static int -do_encryption (const char *protbegin, size_t protlen, +do_encryption (const unsigned char *protbegin, size_t protlen, const char *passphrase, const unsigned char *sha1hash, unsigned char **result, size_t *resultlen) { gcry_cipher_hd_t hd; const char *modestr = "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc"; int blklen, enclen, outlen; - char *iv = NULL; + unsigned char *iv = NULL; int rc; char *outbuf = NULL; char *p; int saltpos, ivpos, encpos; + *resultlen = 0; + *result = NULL; + rc = gcry_cipher_open (&hd, PROT_CIPHER, GCRY_CIPHER_MODE_CBC, GCRY_CIPHER_SECURE); if (rc) @@ -250,7 +253,7 @@ do_encryption (const char *protbegin, size_t protlen, return tmperr; } *resultlen = strlen (p); - *result = p; + *result = (unsigned char*)p; memcpy (p+saltpos, iv+2*blklen, 8); memcpy (p+ivpos, iv, blklen); memcpy (p+encpos, outbuf, enclen); @@ -261,7 +264,7 @@ do_encryption (const char *protbegin, size_t protlen, -/* Protect the key encoded in canonical format in plainkey. We assume +/* Protect the key encoded in canonical format in PLAINKEY. We assume a valid S-Exp here. */ int agent_protect (const unsigned char *plainkey, const char *passphrase, @@ -469,6 +472,9 @@ merge_lists (const unsigned char *protectedkey, const unsigned char *startpos, *endpos; int i, rc; + *result = NULL; + *resultlen = 0; + if (replacepos < 26) return gpg_error (GPG_ERR_BUG); @@ -487,7 +493,7 @@ merge_lists (const unsigned char *protectedkey, return out_of_core (); /* Copy the initial segment */ - strcpy (newlist, "(11:private-key"); + strcpy ((char*)newlist, "(11:private-key"); p = newlist + 15; memcpy (p, protectedkey+15+10, replacepos-15-10); p += replacepos-15-10; @@ -669,7 +675,7 @@ agent_unprotect (const unsigned char *protectedkey, const char *passphrase, is nothing we should worry about */ if (s[n] != ')' ) return gpg_error (GPG_ERR_INV_SEXP); - s2kcount = strtoul (s, NULL, 10); + s2kcount = strtoul ((const char*)s, NULL, 10); if (!s2kcount) return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); s += n; @@ -838,7 +844,7 @@ unsigned char * make_shadow_info (const char *serialno, const char *idstring) { const char *s; - unsigned char *info, *p; + char *info, *p; char numbuf[21]; int n; @@ -853,13 +859,13 @@ make_shadow_info (const char *serialno, const char *idstring) sprintf (numbuf, "%d:", n); p = stpcpy (p, numbuf); for (s=serialno; *s && s[1]; s += 2) - *p++ = xtoi_2 (s); + *(unsigned char *)p++ = xtoi_2 (s); sprintf (numbuf, "%d:", strlen (idstring)); p = stpcpy (p, numbuf); p = stpcpy (p, idstring); *p++ = ')'; *p = 0; - return info; + return (unsigned char *)info; } @@ -878,7 +884,7 @@ agent_shadow_key (const unsigned char *pubkey, const unsigned char *point; size_t n; int depth = 0; - unsigned char *p; + char *p; size_t pubkey_len = gcry_sexp_canon_len (pubkey, 0, NULL,NULL); size_t shadow_info_len = gcry_sexp_canon_len (shadow_info, 0, NULL,NULL); @@ -930,7 +936,8 @@ agent_shadow_key (const unsigned char *pubkey, /* Calculate required length by taking in account: the "shadowed-" prefix, the "shadowed", "t1-v1" as well as some parenthesis */ n = 12 + pubkey_len + 1 + 3+8 + 2+5 + shadow_info_len + 1; - *result = p = xtrymalloc (n); + *result = xtrymalloc (n); + p = (char*)*result; if (!p) return out_of_core (); p = stpcpy (p, "(20:shadowed-private-key"); diff --git a/agent/query.c b/agent/query.c index c1e4dbacc..b231f6fc3 100644 --- a/agent/query.c +++ b/agent/query.c @@ -58,7 +58,7 @@ static pth_mutex_t entry_lock; struct entry_parm_s { int lines; size_t size; - char *buffer; + unsigned char *buffer; }; @@ -372,7 +372,7 @@ agent_askpin (ctrl_t ctrl, { memset (&parm, 0, sizeof parm); parm.size = pininfo->max_length; - parm.buffer = pininfo->pin; + parm.buffer = (unsigned char*)pininfo->pin; if (errtext) { @@ -444,7 +444,8 @@ agent_get_passphrase (CTRL ctrl, int rc; char line[ASSUAN_LINELENGTH]; struct entry_parm_s parm; - unsigned char *p, *hexstring; + unsigned char *p; + char *hexstring; int i; *retpass = NULL; @@ -497,7 +498,7 @@ agent_get_passphrase (CTRL ctrl, return unlock_pinentry (map_assuan_err (rc)); } - hexstring = gcry_malloc_secure (strlen (parm.buffer)*2+1); + hexstring = gcry_malloc_secure (strlen ((char*)parm.buffer)*2+1); if (!hexstring) { gpg_error_t tmperr = out_of_core (); diff --git a/common/ChangeLog b/common/ChangeLog index 08fb06775..e7905ea58 100644 --- a/common/ChangeLog +++ b/common/ChangeLog @@ -1,9 +1,33 @@ +2005-06-15 Werner Koch + + * miscellaneous.c (make_printable_string): Made P a void*. + + * sexputil.c (keygrip_from_canon_sexp, cmp_simple_canon_sexp): + Fixed signed/unsigned pointer mismatch. + (make_simple_sexp_from_hexstr): Ditto. This is all too ugly; I + wonder why gcc-4's default is to warn about them and forcing us to + use cast the warning away. + * iobuf.c (block_filter): Ditto. + (iobuf_flush): Ditto. + (iobuf_read_line): Ditto. + (iobuf_read): Make BUFFER a void *. + (iobuf_write): Make BUFFER a const void *. + * ttyio.c (tty_print_utf8_string2): Ditto. + * estream.c (estream_cookie_mem): Make MEMORY unsigned char*. + (es_write): Make BUFFER a void *. + (es_writen): Ditto. + (es_func_fd_read, es_func_fd_write, es_func_mem_read) + (es_func_mem_write): Ditto. + (es_read, es_readn): Ditto. + (es_func_mem_write): Made MEMORY_NEW an unsigned char *. + * estream.h (es_cookie_read_function_t) + (es_cookie_write_function_t): Changed buffer arg to void*. + 2005-06-03 Werner Koch * estream.c: Use HAVE_CONFIG_H and not USE_CONFIG_H! (es_func_fd_read, es_func_fd_write): Protect against EINTR. - 2005-06-01 Werner Koch * Makefile.am (AM_CPPFLAGS): Added. diff --git a/common/estream.c b/common/estream.c index bf5b02001..70b3d9c6e 100644 --- a/common/estream.c +++ b/common/estream.c @@ -294,7 +294,7 @@ es_init_do (void) typedef struct estream_cookie_mem { unsigned int flags; /* Open flags. */ - char *memory; /* Data. */ + unsigned char *memory; /* Data. */ size_t memory_size; /* Size of MEMORY. */ size_t offset; /* Current offset in MEMORY. */ size_t data_len; /* Length of data in MEMORY. */ @@ -350,7 +350,7 @@ es_func_mem_create (void *ES__RESTRICT *ES__RESTRICT cookie, /* Read function for memory objects. */ static ssize_t -es_func_mem_read (void *cookie, char *buffer, size_t size) +es_func_mem_read (void *cookie, void *buffer, size_t size) { estream_cookie_mem_t mem_cookie = cookie; ssize_t ret; @@ -371,11 +371,11 @@ es_func_mem_read (void *cookie, char *buffer, size_t size) /* Write function for memory objects. */ static ssize_t -es_func_mem_write (void *cookie, const char *buffer, size_t size) +es_func_mem_write (void *cookie, const void *buffer, size_t size) { estream_cookie_mem_t mem_cookie = cookie; func_realloc_t func_realloc = mem_cookie->func_realloc; - char *memory_new; + unsigned char *memory_new; size_t newsize; ssize_t ret; int err; @@ -591,7 +591,7 @@ es_func_fd_create (void **cookie, int fd, unsigned int flags) /* Read function for fd objects. */ static ssize_t -es_func_fd_read (void *cookie, char *buffer, size_t size) +es_func_fd_read (void *cookie, void *buffer, size_t size) { estream_cookie_fd_t file_cookie = cookie; @@ -606,7 +606,7 @@ es_func_fd_read (void *cookie, char *buffer, size_t size) /* Write function for fd objects. */ static ssize_t -es_func_fd_write (void *cookie, const char *buffer, size_t size) +es_func_fd_write (void *cookie, const void *buffer, size_t size) { estream_cookie_fd_t file_cookie = cookie; @@ -1122,9 +1122,10 @@ es_read_lbf (estream_t ES__RESTRICT stream, *the amount of bytes read in BYTES_READ. */ static int es_readn (estream_t ES__RESTRICT stream, - unsigned char *ES__RESTRICT buffer, + void *ES__RESTRICT buffer_arg, size_t bytes_to_read, size_t *ES__RESTRICT bytes_read) { + unsigned char *buffer = (unsigned char *)buffer_arg; size_t data_read_unread, data_read; int err; @@ -1388,7 +1389,7 @@ es_write_lbf (estream_t ES__RESTRICT stream, amount of bytes written in BYTES_WRITTEN. */ static int es_writen (estream_t ES__RESTRICT stream, - const unsigned char *ES__RESTRICT buffer, + const void *ES__RESTRICT buffer, size_t bytes_to_write, size_t *ES__RESTRICT bytes_written) { size_t data_written; @@ -2289,7 +2290,7 @@ es_ungetc (int c, estream_t stream) int es_read (estream_t ES__RESTRICT stream, - char *ES__RESTRICT buffer, size_t bytes_to_read, + void *ES__RESTRICT buffer, size_t bytes_to_read, size_t *ES__RESTRICT bytes_read) { int err; @@ -2309,7 +2310,7 @@ es_read (estream_t ES__RESTRICT stream, int es_write (estream_t ES__RESTRICT stream, - const char *ES__RESTRICT buffer, size_t bytes_to_write, + const void *ES__RESTRICT buffer, size_t bytes_to_write, size_t *ES__RESTRICT bytes_written) { int err; diff --git a/common/estream.h b/common/estream.h index c201b666a..ebe575926 100644 --- a/common/estream.h +++ b/common/estream.h @@ -72,9 +72,9 @@ typedef struct es__stream *estream_t; typedef ssize_t (*es_cookie_read_function_t) (void *cookie, - char *buffer, size_t size); + void *buffer, size_t size); typedef ssize_t (*es_cookie_write_function_t) (void *cookie, - const char *buffer, + const void *buffer, size_t size); typedef int (*es_cookie_seek_function_t) (void *cookie, off_t *pos, int whence); @@ -166,10 +166,10 @@ int _es_putc_overflow (int c, estream_t stream); int es_ungetc (int c, estream_t stream); int es_read (estream_t ES__RESTRICT stream, - char *ES__RESTRICT buffer, size_t bytes_to_read, + void *ES__RESTRICT buffer, size_t bytes_to_read, size_t *ES__RESTRICT bytes_read); int es_write (estream_t ES__RESTRICT stream, - const char *ES__RESTRICT buffer, size_t bytes_to_write, + const void *ES__RESTRICT buffer, size_t bytes_to_write, size_t *ES__RESTRICT bytes_written); size_t es_fread (void *ES__RESTRICT ptr, size_t size, size_t nitems, diff --git a/common/iobuf.c b/common/iobuf.c index 52a388514..32b9e18c6 100644 --- a/common/iobuf.c +++ b/common/iobuf.c @@ -675,10 +675,11 @@ sock_filter (void *opaque, int control, iobuf_t chain, byte * buf, * without a filter */ static int -block_filter (void *opaque, int control, iobuf_t chain, byte * buf, +block_filter (void *opaque, int control, iobuf_t chain, byte * buffer, size_t * ret_len) { block_filter_ctx_t *a = opaque; + char *buf = (char *)buffer; size_t size = *ret_len; int c, needed, rc = 0; char *p; @@ -1762,7 +1763,7 @@ iobuf_flush (iobuf_t a) if (a->use == 3) { /* increase the temp buffer */ - char *newbuf; + unsigned char *newbuf; size_t newsize = a->d.size + 8192; if (DBG_IOBUF) @@ -1829,8 +1830,9 @@ iobuf_readbyte (iobuf_t a) int -iobuf_read (iobuf_t a, byte * buf, unsigned buflen) +iobuf_read (iobuf_t a, void *buffer, unsigned int buflen) { + unsigned char *buf = (unsigned char *)buffer; int c, n; if (a->unget.buf || a->nlimit) @@ -1915,7 +1917,7 @@ iobuf_peek (iobuf_t a, byte * buf, unsigned buflen) int -iobuf_writebyte (iobuf_t a, unsigned c) +iobuf_writebyte (iobuf_t a, unsigned int c) { int rc; @@ -1933,8 +1935,9 @@ iobuf_writebyte (iobuf_t a, unsigned c) int -iobuf_write (iobuf_t a, byte * buf, unsigned buflen) +iobuf_write (iobuf_t a, const void *buffer, unsigned int buflen) { + const unsigned char *buf = (const unsigned char *)buffer; int rc; if (a->directfp) @@ -2311,7 +2314,7 @@ iobuf_read_line (iobuf_t a, byte ** addr_of_buffer, unsigned *length_of_buffer, unsigned *max_length) { int c; - char *buffer = *addr_of_buffer; + char *buffer = (char *)*addr_of_buffer; unsigned length = *length_of_buffer; unsigned nbytes = 0; unsigned maxlen = *max_length; @@ -2321,7 +2324,7 @@ iobuf_read_line (iobuf_t a, byte ** addr_of_buffer, { /* must allocate a new buffer */ length = 256; buffer = xmalloc (length); - *addr_of_buffer = buffer; + *addr_of_buffer = (unsigned char *)buffer; *length_of_buffer = length; } @@ -2344,7 +2347,7 @@ iobuf_read_line (iobuf_t a, byte ** addr_of_buffer, length += 3; /* correct for the reserved byte */ length += length < 1024 ? 256 : 1024; buffer = xrealloc (buffer, length); - *addr_of_buffer = buffer; + *addr_of_buffer = (unsigned char *)buffer; *length_of_buffer = length; length -= 3; /* and reserve again */ p = buffer + nbytes; diff --git a/common/iobuf.h b/common/iobuf.h index 0af94e22d..b991717c2 100644 --- a/common/iobuf.h +++ b/common/iobuf.h @@ -120,12 +120,12 @@ off_t iobuf_tell (iobuf_t a); int iobuf_seek (iobuf_t a, off_t newpos); int iobuf_readbyte (iobuf_t a); -int iobuf_read (iobuf_t a, byte * buf, unsigned buflen); +int iobuf_read (iobuf_t a, void *buf, unsigned buflen); unsigned iobuf_read_line (iobuf_t a, byte ** addr_of_buffer, unsigned *length_of_buffer, unsigned *max_length); int iobuf_peek (iobuf_t a, byte * buf, unsigned buflen); int iobuf_writebyte (iobuf_t a, unsigned c); -int iobuf_write (iobuf_t a, byte * buf, unsigned buflen); +int iobuf_write (iobuf_t a, const void *buf, unsigned buflen); int iobuf_writestr (iobuf_t a, const char *buf); void iobuf_flush_temp (iobuf_t temp); diff --git a/common/miscellaneous.c b/common/miscellaneous.c index 86b0fcb3a..d81213ef9 100644 --- a/common/miscellaneous.c +++ b/common/miscellaneous.c @@ -66,7 +66,7 @@ print_utf8_string( FILE *fp, const byte *p, size_t n ) } char * -make_printable_string( const byte *p, size_t n, int delim ) +make_printable_string (const void *p, size_t n, int delim ) { return sanitize_buffer (p, n, delim); } diff --git a/common/sexputil.c b/common/sexputil.c index 802916b44..8a27ad978 100644 --- a/common/sexputil.c +++ b/common/sexputil.c @@ -52,7 +52,7 @@ keygrip_from_canon_sexp (const unsigned char *key, size_t keylen, if (!grip) return gpg_error (GPG_ERR_INV_VALUE); - err = gcry_sexp_sscan (&sexp, NULL, key, keylen); + err = gcry_sexp_sscan (&sexp, NULL, (const char *)key, keylen); if (err) return err; if (!gcry_pk_get_keygrip (sexp, grip)) @@ -66,8 +66,11 @@ keygrip_from_canon_sexp (const unsigned char *key, size_t keylen, are identical or !0 if they are not. Not that this function can't be used for sorting. */ int -cmp_simple_canon_sexp (const unsigned char *a, const unsigned char *b) +cmp_simple_canon_sexp (const unsigned char *a_orig, + const unsigned char *b_orig) { + const char *a = (const char *)a_orig; + const char *b = (const char *)b_orig; unsigned long n1, n2; char *endp; @@ -124,7 +127,7 @@ make_simple_sexp_from_hexstr (const char *line, size_t *nscanned) buf = xtrymalloc (strlen (numbuf) + len + 1 + 1); if (!buf) return NULL; - p = stpcpy (buf, numbuf); + p = (unsigned char *)stpcpy ((char *)buf, numbuf); s = line; if ((n&1)) { diff --git a/common/simple-pwquery.c b/common/simple-pwquery.c index 8a027e799..de3689810 100644 --- a/common/simple-pwquery.c +++ b/common/simple-pwquery.c @@ -404,7 +404,7 @@ static char * copy_and_escape (char *buffer, const char *text) { int i; - const unsigned char *s = text; + const unsigned char *s = (unsigned char *)text; char *p = buffer; diff --git a/common/ttyio.c b/common/ttyio.c index eab805e20..5749c59fe 100644 --- a/common/ttyio.c +++ b/common/ttyio.c @@ -322,7 +322,7 @@ tty_print_utf8_string2( const byte *p, size_t n, size_t max_n ) break; } if( i < n ) { - buf = utf8_to_native( p, n, 0 ); + buf = utf8_to_native( (const char *)p, n, 0 ); if( max_n && (strlen( buf ) > max_n )) { buf[max_n] = 0; } diff --git a/common/util.h b/common/util.h index d233dbf5e..1ced59b67 100644 --- a/common/util.h +++ b/common/util.h @@ -153,7 +153,7 @@ const char *print_fname_stdin (const char *s); void print_string (FILE *fp, const byte *p, size_t n, int delim); void print_utf8_string2 ( FILE *fp, const byte *p, size_t n, int delim); void print_utf8_string (FILE *fp, const byte *p, size_t n); -char *make_printable_string (const byte *p, size_t n, int delim); +char *make_printable_string (const void *p, size_t n, int delim); int is_file_compressed (const char *s, int *ret_rc); diff --git a/doc/gpg-agent.texi b/doc/gpg-agent.texi index bad6639e2..144745b4c 100644 --- a/doc/gpg-agent.texi +++ b/doc/gpg-agent.texi @@ -293,7 +293,7 @@ Set the time a cache entry is valid to @var{n} seconds. The default are @item --default-cache-ttl-ssh @var{n} @opindex default-cache-ttl Set the time a cache entry used for SSH keys is valid to @var{n} -seconds. The default are 600 seconds. +seconds. The default are 1800 seconds. @item --max-cache-ttl @var{n} @opindex max-cache-ttl @@ -301,6 +301,12 @@ Set the maximum time a cache entry is valid to @var{n} seconds. After this time a cache entry will get expired even if it has been accessed recently. The default are 2 hours (7200 seconds). +@item --max-cache-ttl-ssh @var{n} +@opindex max-cache-ttl-ssh +Set the maximum time a cache entry used for SSH keys is valid to @var{n} +seconds. After this time a cache entry will get expired even if it has +been accessed recently. The default are 2 hours (7200 seconds). + @item --pinentry-program @var{filename} @opindex pinentry-program Use program @var{filename} as the PIN entry. The default is installation diff --git a/g10/ChangeLog b/g10/ChangeLog index b33735e1f..0ae73b535 100644 --- a/g10/ChangeLog +++ b/g10/ChangeLog @@ -1,3 +1,8 @@ +2005-06-15 Werner Koch + + * g10.c (print_hashline, add_group): Fixes for signed/unsigned + pointer mismatch warnings. + 2005-06-01 Werner Koch * mkdtemp.c: Removed. diff --git a/g10/g10.c b/g10/g10.c index 0be5636a2..234d13f41 100644 --- a/g10/g10.c +++ b/g10/g10.c @@ -933,7 +933,7 @@ static void add_group(char *string) return; } - trim_trailing_ws(name,strlen(name)); + trim_trailing_ws((unsigned char *)name,strlen(name)); /* Break apart the values */ while ((value= strsep(&string," \t"))) @@ -3124,7 +3124,7 @@ print_hashline( MD_HANDLE md, int algo, const char *fname ) const byte *p; if ( fname ) { - for (p = fname; *p; p++ ) { + for (p = (const unsigned char *)fname; *p; p++ ) { if ( *p <= 32 || *p > 127 || *p == ':' || *p == '%' ) printf("%%%02X", *p ); else diff --git a/g10/misc.c b/g10/misc.c index 7012a8a25..516e80bcc 100644 --- a/g10/misc.c +++ b/g10/misc.c @@ -986,9 +986,10 @@ mpi_print( FILE *fp, gcry_mpi_t a, int mode ) } else { int rc; - unsigned char *buffer; + char *buffer; - rc = gcry_mpi_aprint( GCRYMPI_FMT_HEX, &buffer, NULL, a ); + rc = gcry_mpi_aprint( GCRYMPI_FMT_HEX, + &(unsigned char*)buffer, NULL, a ); assert( !rc ); fputs( buffer, fp ); n += strlen(buffer); diff --git a/jnlib/ChangeLog b/jnlib/ChangeLog index f308a7ea3..f0463c5b3 100644 --- a/jnlib/ChangeLog +++ b/jnlib/ChangeLog @@ -1,3 +1,16 @@ +2005-06-15 Werner Koch + + * stringhelp.c (sanitize_buffer): Make P a void*. + (ascii_memistr, memistr): Ditto. + (ascii_memcasecmp): Ditto. + * logging.c (writen): Use void * for arg BUFFER. + * stringhelp.c (memistr): Fixed unsigned/signed pointer conflict. + (ascii_memistr): Ditto. + (ascii_memcasemem): Ditto. + * utf8conv.c (utf8_to_native): Ditto. + (utf8_to_native): Ditto. + * argparse.c (show_version): Removed non-required cast. + 2005-01-19 Werner Koch * logging.c (fun_writer): Don't fallback to stderr. Print to diff --git a/jnlib/argparse.c b/jnlib/argparse.c index 485c60786..980d1186c 100644 --- a/jnlib/argparse.c +++ b/jnlib/argparse.c @@ -852,7 +852,7 @@ show_version() /* additional program info */ for(i=30; i < 40; i++ ) if( (s=strusage(i)) ) - fputs( (const byte*)s, stdout); + fputs (s, stdout); fflush(stdout); } diff --git a/jnlib/logging.c b/jnlib/logging.c index 97a2b9c9e..c944006a5 100644 --- a/jnlib/logging.c +++ b/jnlib/logging.c @@ -87,10 +87,11 @@ struct fun_cookie_s { char name[1]; }; -/* Write NBYTES of BUF to file descriptor FD. */ +/* Write NBYTES of BUFFER to file descriptor FD. */ static int -writen (int fd, const unsigned char *buf, size_t nbytes) +writen (int fd, const void *buffer, size_t nbytes) { + const char *buf = buffer; size_t nleft = nbytes; int nwritten; diff --git a/jnlib/stringhelp.c b/jnlib/stringhelp.c index 5a3b41528..760398b0c 100644 --- a/jnlib/stringhelp.c +++ b/jnlib/stringhelp.c @@ -1,6 +1,6 @@ /* stringhelp.c - standard string helper functions * Copyright (C) 1998, 1999, 2000, 2001, 2003, - * 2004 Free Software Foundation, Inc. + * 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -35,45 +35,57 @@ /* * Look for the substring SUB in buffer and return a pointer to that - * substring in BUF or NULL if not found. + * substring in BUFFER or NULL if not found. * Comparison is case-insensitive. */ const char * -memistr( const char *buf, size_t buflen, const char *sub ) +memistr (const void *buffer, size_t buflen, const char *sub) { - const byte *t, *s ; - size_t n; + const unsigned char *buf = buffer; + const unsigned char *t = (const unsigned char *)buffer; + const unsigned char *s = (const unsigned char *)sub; + size_t n = buflen; - for( t=buf, n=buflen, s=sub ; n ; t++, n-- ) - if( toupper(*t) == toupper(*s) ) { - for( buf=t++, buflen = n--, s++; - n && toupper(*t) == toupper(*s); t++, s++, n-- ) - ; - if( !*s ) - return buf; - t = buf; n = buflen; s = sub ; + for ( ; n ; t++, n-- ) + { + if ( toupper (*t) == toupper (*s) ) + { + for ( buf=t++, buflen = n--, s++; + n && toupper (*t) == toupper (*s); t++, s++, n-- ) + ; + if (!*s) + return (const char*)buf; + t = buf; + s = (const unsigned char *)sub ; + n = buflen; } - - return NULL ; + } + return NULL; } const char * -ascii_memistr( const char *buf, size_t buflen, const char *sub ) +ascii_memistr ( const void *buffer, size_t buflen, const char *sub ) { - const byte *t, *s ; - size_t n; + const unsigned char *buf = buffer; + const unsigned char *t = (const unsigned char *)buf; + const unsigned char *s = (const unsigned char *)sub; + size_t n = buflen; - for( t=buf, n=buflen, s=sub ; n ; t++, n-- ) - if( ascii_toupper(*t) == ascii_toupper(*s) ) { - for( buf=t++, buflen = n--, s++; - n && ascii_toupper(*t) == ascii_toupper(*s); t++, s++, n-- ) - ; - if( !*s ) - return buf; - t = buf; n = buflen; s = sub ; + for ( ; n ; t++, n-- ) + { + if (ascii_toupper (*t) == ascii_toupper (*s) ) + { + for ( buf=t++, buflen = n--, s++; + n && ascii_toupper (*t) == ascii_toupper (*s); t++, s++, n-- ) + ; + if (!*s) + return (const char*)buf; + t = (const unsigned char *)buf; + s = (const unsigned char *)sub ; + n = buflen; } - - return NULL ; + } + return NULL; } /* This function is similar to strncpy(). However it won't copy more @@ -402,13 +414,14 @@ print_sanitized_utf8_string (FILE *fp, const char *string, int delim) delim) : 0; } -/* Create a string from the buffer P of length N which is suitable for +/* Create a string from the buffer P_ARG of length N which is suitable for printing. Caller must release the created string using xfree. */ char * -sanitize_buffer (const unsigned char *p, size_t n, int delim) +sanitize_buffer (const void *p_arg, size_t n, int delim) { + const unsigned char *p = p_arg; size_t save_n, buflen; - const byte *save_p; + const unsigned char *save_p; char *buffer, *d; /* first count length */ @@ -552,15 +565,19 @@ ascii_strncasecmp (const char *a, const char *b, size_t n) int -ascii_memcasecmp( const char *a, const char *b, size_t n ) +ascii_memcasecmp (const void *a_arg, const void *b_arg, size_t n ) { - if (a == b) - return 0; - for ( ; n; n--, a++, b++ ) { - if( *a != *b && ascii_toupper (*a) != ascii_toupper (*b) ) - return *a == *b? 0 : (ascii_toupper (*a) - ascii_toupper (*b)); - } + const char *a = a_arg; + const char *b = b_arg; + + if (a == b) return 0; + for ( ; n; n--, a++, b++ ) + { + if( *a != *b && ascii_toupper (*a) != ascii_toupper (*b) ) + return *a == *b? 0 : (ascii_toupper (*a) - ascii_toupper (*b)); + } + return 0; } int @@ -586,8 +603,8 @@ ascii_memcasemem (const void *haystack, size_t nhaystack, return (void*)haystack; /* finding an empty needle is really easy */ if (nneedle <= nhaystack) { - const unsigned char *a = haystack; - const unsigned char *b = a + nhaystack - nneedle; + const char *a = haystack; + const char *b = a + nhaystack - nneedle; for (; a <= b; a++) { diff --git a/jnlib/stringhelp.h b/jnlib/stringhelp.h index 412da3a0e..bdd7d561c 100644 --- a/jnlib/stringhelp.h +++ b/jnlib/stringhelp.h @@ -23,7 +23,7 @@ #include "types.h" -const char *memistr( const char *buf, size_t buflen, const char *sub ); +const char *memistr (const void *buf, size_t buflen, const char *sub); char *mem2str( char *, const void *, size_t); char *trim_spaces( char *string ); char *trim_trailing_spaces( char *string ); @@ -46,7 +46,7 @@ size_t print_sanitized_utf8_buffer (FILE *fp, const void *buffer, size_t length, int delim); size_t print_sanitized_string (FILE *fp, const char *string, int delim); size_t print_sanitized_utf8_string (FILE *fp, const char *string, int delim); -char *sanitize_buffer (const unsigned char *p, size_t n, int delim); +char *sanitize_buffer (const void *p, size_t n, int delim); #ifdef HAVE_W32_SYSTEM @@ -54,15 +54,14 @@ const char *w32_strerror (int ec); #endif -const char *ascii_memistr( const char *buf, size_t buflen, const char *sub ); int ascii_isupper (int c); int ascii_islower (int c); int ascii_toupper (int c); int ascii_tolower (int c); int ascii_strcasecmp( const char *a, const char *b ); int ascii_strncasecmp (const char *a, const char *b, size_t n); -int ascii_memcasecmp( const char *a, const char *b, size_t n ); -const char *ascii_memistr ( const char *buf, size_t buflen, const char *sub); +int ascii_memcasecmp( const void *a, const void *b, size_t n ); +const char *ascii_memistr ( const void *buf, size_t buflen, const char *sub); void *ascii_memcasemem (const void *haystack, size_t nhaystack, const void *needle, size_t nneedle); diff --git a/jnlib/utf8conv.c b/jnlib/utf8conv.c index 691176766..4df8b7b32 100644 --- a/jnlib/utf8conv.c +++ b/jnlib/utf8conv.c @@ -136,16 +136,17 @@ get_native_charset () * new allocated UTF8 string. */ char * -native_to_utf8 (const char *string) +native_to_utf8 (const char *orig_string) { - const byte *s; + const unsigned char *string = (const unsigned char *)orig_string; + const unsigned char *s; char *buffer; - byte *p; + unsigned char *p; size_t length = 0; if (no_translation) { - buffer = jnlib_xstrdup (string); + buffer = jnlib_xstrdup (orig_string); } else if (active_charset) { @@ -156,7 +157,7 @@ native_to_utf8 (const char *string) length += 2; /* we may need 3 bytes */ } buffer = jnlib_xmalloc (length + 1); - for (p = buffer, s = string; *s; s++) + for (p = (unsigned char *)buffer, s = string; *s; s++) { if ((*s & 0x80)) { @@ -187,7 +188,7 @@ native_to_utf8 (const char *string) length++; } buffer = jnlib_xmalloc (length + 1); - for (p = buffer, s = string; *s; s++) + for (p = (unsigned char *)buffer, s = string; *s; s++) { if (*s & 0x80) { @@ -212,11 +213,12 @@ utf8_to_native (const char *string, size_t length, int delim) { int nleft; int i; - byte encbuf[8]; + unsigned char encbuf[8]; int encidx; const byte *s; size_t n; - byte *buffer = NULL, *p = NULL; + char *buffer = NULL; + char *p = NULL; unsigned long val = 0; size_t slen; int resync = 0; @@ -225,7 +227,8 @@ utf8_to_native (const char *string, size_t length, int delim) /* 2. pass (p!=NULL): create string */ for (;;) { - for (slen = length, nleft = encidx = 0, n = 0, s = string; slen; + for (slen = length, nleft = encidx = 0, n = 0, + s = (const unsigned char *)string; slen; s++, slen--) { if (resync) diff --git a/kbx/ChangeLog b/kbx/ChangeLog index 7c112085c..4fd06d5ca 100644 --- a/kbx/ChangeLog +++ b/kbx/ChangeLog @@ -1,3 +1,14 @@ +2005-06-15 Werner Koch + + * keybox-file.c (_keybox_read_blob2): Make IMAGE unsigned. + (_keybox_write_blob): + + * keybox-blob.c (create_blob_finish, _keybox_create_x509_blob): + Fixed warnings about signed/unsigned pointer mismatches. + (x509_email_kludge): Ditto. + (_keybox_new_blob): Changed arg IMAGE to unsigned char *. + (_keybox_get_blob_image): Changed return type to unsigned char*. + 2005-06-01 Werner Koch * keybox-file.c (ftello) [!HAVE_FSEEKO]: New replacement diff --git a/kbx/kbxutil.c b/kbx/kbxutil.c index 7fe6178d6..0569b5a67 100644 --- a/kbx/kbxutil.c +++ b/kbx/kbxutil.c @@ -386,7 +386,7 @@ import_openpgp (const char *filename) buffer = read_file (filename, &buflen); if (!buffer) return; - p = buffer; + p = (unsigned char *)buffer; for (;;) { err = _keybox_parse_openpgp (p, buflen, &nparsed, &info); diff --git a/kbx/keybox-blob.c b/kbx/keybox-blob.c index 48bce28e2..67c74b777 100644 --- a/kbx/keybox-blob.c +++ b/kbx/keybox-blob.c @@ -646,8 +646,8 @@ static int create_blob_finish (KEYBOXBLOB blob) { struct membuf *a = blob->buf; - byte *p; - char *pp; + unsigned char *p; + unsigned char *pp; int i; size_t n; @@ -656,6 +656,7 @@ create_blob_finish (KEYBOXBLOB blob) put32 (a, 0); /* Hmmm: why put32() ?? */ /* get the memory area */ + n = 0; /* (Just to avoid compiler warning.) */ p = get_membuf (a, &n); if (!p) return gpg_error (GPG_ERR_ENOMEM); @@ -783,7 +784,7 @@ _keybox_create_pgp_blob (KEYBOXBLOB *r_blob, KBNODE keyblock, int as_ephemeral) static char * x509_email_kludge (const char *name) { - const unsigned char *p; + const char *p; unsigned char *buf; int n; @@ -805,7 +806,7 @@ x509_email_kludge (const char *name) buf[n] = xtoi_2 (p); buf[n++] = '>'; buf[n] = 0; - return buf; + return (char *)buf; } @@ -818,8 +819,9 @@ _keybox_create_x509_blob (KEYBOXBLOB *r_blob, ksba_cert_t cert, { int i, rc = 0; KEYBOXBLOB blob; - unsigned char *p; - unsigned char **names = NULL; + unsigned char *sn; + char *p; + char **names = NULL; size_t max_names; *r_blob = NULL; @@ -827,28 +829,28 @@ _keybox_create_x509_blob (KEYBOXBLOB *r_blob, ksba_cert_t cert, if( !blob ) return gpg_error (gpg_err_code_from_errno (errno)); - p = ksba_cert_get_serial (cert); - if (p) + sn = ksba_cert_get_serial (cert); + if (sn) { size_t n, len; - n = gcry_sexp_canon_len (p, 0, NULL, NULL); + n = gcry_sexp_canon_len (sn, 0, NULL, NULL); if (n < 2) { - xfree (p); + xfree (sn); return gpg_error (GPG_ERR_GENERAL); } - blob->serialbuf = p; - p++; n--; /* skip '(' */ - for (len=0; n && *p && *p != ':' && digitp (p); n--, p++) - len = len*10 + atoi_1 (p); - if (*p != ':') + blob->serialbuf = sn; + sn++; n--; /* skip '(' */ + for (len=0; n && *sn && *sn != ':' && digitp (sn); n--, sn++) + len = len*10 + atoi_1 (sn); + if (*sn != ':') { xfree (blob->serialbuf); blob->serialbuf = NULL; return gpg_error (GPG_ERR_GENERAL); } - p++; - blob->serial = p; + sn++; + blob->serial = sn; blob->seriallen = len; } @@ -863,6 +865,7 @@ _keybox_create_x509_blob (KEYBOXBLOB *r_blob, ksba_cert_t cert, rc = gpg_error (gpg_err_code_from_errno (errno)); goto leave; } + p = ksba_cert_get_issuer (cert, 0); if (!p) { @@ -872,10 +875,9 @@ _keybox_create_x509_blob (KEYBOXBLOB *r_blob, ksba_cert_t cert, names[blob->nuids++] = p; for (i=0; (p = ksba_cert_get_subject (cert, i)); i++) { - if (blob->nuids >= max_names) { - unsigned char **tmp; + char **tmp; max_names += 100; tmp = xtryrealloc (names, max_names * sizeof *names); @@ -964,7 +966,8 @@ _keybox_create_x509_blob (KEYBOXBLOB *r_blob, ksba_cert_t cert, int -_keybox_new_blob (KEYBOXBLOB *r_blob, char *image, size_t imagelen, off_t off) +_keybox_new_blob (KEYBOXBLOB *r_blob, + unsigned char *image, size_t imagelen, off_t off) { KEYBOXBLOB blob; @@ -1000,7 +1003,7 @@ _keybox_release_blob (KEYBOXBLOB blob) -const char * +const unsigned char * _keybox_get_blob_image ( KEYBOXBLOB blob, size_t *n ) { *n = blob->bloblen; diff --git a/kbx/keybox-defs.h b/kbx/keybox-defs.h index b58294459..7bbed8519 100644 --- a/kbx/keybox-defs.h +++ b/kbx/keybox-defs.h @@ -140,10 +140,11 @@ int _keybox_create_x509_blob (KEYBOXBLOB *r_blob, ksba_cert_t cert, unsigned char *sha1_digest, int as_ephemeral); #endif /*KEYBOX_WITH_X509*/ -int _keybox_new_blob (KEYBOXBLOB *r_blob, char *image, size_t imagelen, +int _keybox_new_blob (KEYBOXBLOB *r_blob, + unsigned char *image, size_t imagelen, off_t off); void _keybox_release_blob (KEYBOXBLOB blob); -const char *_keybox_get_blob_image (KEYBOXBLOB blob, size_t *n); +const unsigned char *_keybox_get_blob_image (KEYBOXBLOB blob, size_t *n); off_t _keybox_get_blob_fileoffset (KEYBOXBLOB blob); void _keybox_update_header_blob (KEYBOXBLOB blob); diff --git a/kbx/keybox-file.c b/kbx/keybox-file.c index fe02c1f9f..3883ce607 100644 --- a/kbx/keybox-file.c +++ b/kbx/keybox-file.c @@ -48,7 +48,7 @@ ftello (FILE *stream) int _keybox_read_blob2 (KEYBOXBLOB *r_blob, FILE *fp, int *skipped_deleted) { - char *image; + unsigned char *image; size_t imagelen = 0; int c1, c2, c3, c4, type; int rc; @@ -118,7 +118,7 @@ _keybox_read_blob (KEYBOXBLOB *r_blob, FILE *fp) int _keybox_write_blob (KEYBOXBLOB blob, FILE *fp) { - const char *image; + const unsigned char *image; size_t length; image = _keybox_get_blob_image (blob, &length); diff --git a/scd/apdu.c b/scd/apdu.c index 212b9df24..975fffa24 100644 --- a/scd/apdu.c +++ b/scd/apdu.c @@ -2393,7 +2393,7 @@ apdu_activate (int slot) unsigned char * apdu_get_atr (int slot, size_t *atrlen) { - char *buf; + unsigned char *buf; if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) return NULL; diff --git a/scd/app-help.c b/scd/app-help.c index 1c3c52b15..27cbea5c7 100644 --- a/scd/app-help.c +++ b/scd/app-help.c @@ -48,7 +48,7 @@ app_help_get_keygrip_string (ksba_cert_t cert, char *hexkeygrip) n = gcry_sexp_canon_len (p, 0, NULL, NULL); if (!n) return gpg_error (GPG_ERR_INV_SEXP); - err = gcry_sexp_sscan (&s_pkey, NULL, p, n); + err = gcry_sexp_sscan (&s_pkey, NULL, (char*)p, n); xfree (p); if (err) return err; /* Can't parse that S-expression. */ diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index 1ff096138..11e6eebaf 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -948,8 +948,8 @@ get_public_key (app_t app, int keyno) size_t buflen, keydatalen, mlen, elen; unsigned char *mbuf = NULL; unsigned char *ebuf = NULL; - unsigned char *keybuf = NULL; - unsigned char *keybuf_p; + char *keybuf = NULL; + char *keybuf_p; if (keyno < 1 || keyno > 3) return gpg_error (GPG_ERR_INV_ID); @@ -963,14 +963,16 @@ get_public_key (app_t app, int keyno) app->app_local->pk[keyno].key = NULL; app->app_local->pk[keyno].keylen = 0; + m = e = NULL; /* (avoid cc warning) */ + if (app->card_version > 0x0100) { /* We may simply read the public key out of these cards. */ - err = iso7816_read_public_key (app->slot, - keyno == 0? "\xB6" : - keyno == 1? "\xB8" : "\xA4", - 2, - &buffer, &buflen); + err = iso7816_read_public_key + (app->slot, (const unsigned char*)(keyno == 0? "\xB6" : + keyno == 1? "\xB8" : "\xA4"), + 2, + &buffer, &buflen); if (err) { log_error (_("reading public key failed: %s\n"), gpg_strerror (err)); @@ -1107,7 +1109,7 @@ get_public_key (app_t app, int keyno) strcpy (keybuf_p, ")))"); keybuf_p += strlen (keybuf_p); - app->app_local->pk[keyno].key = keybuf; + app->app_local->pk[keyno].key = (unsigned char*)keybuf; app->app_local->pk[keyno].keylen = (keybuf_p - keybuf); leave: @@ -1889,11 +1891,10 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, #warning key generation temporary replaced by reading an existing key. rc = iso7816_read_public_key #endif - (app->slot, - keyno == 0? "\xB6" : - keyno == 1? "\xB8" : "\xA4", - 2, - &buffer, &buflen); + (app->slot, (const unsigned char*)(keyno == 0? "\xB6" : + keyno == 1? "\xB8" : "\xA4"), + 2, + &buffer, &buflen); if (rc) { rc = gpg_error (GPG_ERR_CARD); diff --git a/scd/app-p15.c b/scd/app-p15.c index 831f0d1f4..f03e5d5f0 100644 --- a/scd/app-p15.c +++ b/scd/app-p15.c @@ -43,33 +43,35 @@ typedef enum } card_type_t; /* A list card types with ATRs noticed with these cards. */ +#define X(a) ((unsigned char const *)(a)) static struct { size_t atrlen; - unsigned char *atr; + unsigned char const *atr; card_type_t type; } card_atr_list[] = { - { 19, "\x3B\xBA\x13\x00\x81\x31\x86\x5D\x00\x64\x05\x0A\x02\x01\x31\x80" - "\x90\x00\x8B", + { 19, X("\x3B\xBA\x13\x00\x81\x31\x86\x5D\x00\x64\x05\x0A\x02\x01\x31\x80" + "\x90\x00\x8B"), CARD_TYPE_TCOS }, /* SLE44 */ - { 19, "\x3B\xBA\x14\x00\x81\x31\x86\x5D\x00\x64\x05\x14\x02\x02\x31\x80" - "\x90\x00\x91", + { 19, X("\x3B\xBA\x14\x00\x81\x31\x86\x5D\x00\x64\x05\x14\x02\x02\x31\x80" + "\x90\x00\x91"), CARD_TYPE_TCOS }, /* SLE66S */ - { 19, "\x3B\xBA\x96\x00\x81\x31\x86\x5D\x00\x64\x05\x60\x02\x03\x31\x80" - "\x90\x00\x66", + { 19, X("\x3B\xBA\x96\x00\x81\x31\x86\x5D\x00\x64\x05\x60\x02\x03\x31\x80" + "\x90\x00\x66"), CARD_TYPE_TCOS }, /* SLE66P */ - { 27, "\x3B\xFF\x94\x00\xFF\x80\xB1\xFE\x45\x1F\x03\x00\x68\xD2\x76\x00" - "\x00\x28\xFF\x05\x1E\x31\x80\x00\x90\x00\x23", + { 27, X("\x3B\xFF\x94\x00\xFF\x80\xB1\xFE\x45\x1F\x03\x00\x68\xD2\x76\x00" + "\x00\x28\xFF\x05\x1E\x31\x80\x00\x90\x00\x23"), CARD_TYPE_MICARDO }, /* German BMI card */ - { 19, "\x3B\x6F\x00\xFF\x00\x68\xD2\x76\x00\x00\x28\xFF\x05\x1E\x31\x80" - "\x00\x90\x00", + { 19, X("\x3B\x6F\x00\xFF\x00\x68\xD2\x76\x00\x00\x28\xFF\x05\x1E\x31\x80" + "\x00\x90\x00"), CARD_TYPE_MICARDO }, /* German BMI card (ATR due to reader problem) */ - { 26, "\x3B\xFE\x94\x00\xFF\x80\xB1\xFA\x45\x1F\x03\x45\x73\x74\x45\x49" - "\x44\x20\x76\x65\x72\x20\x31\x2E\x30\x43", + { 26, X("\x3B\xFE\x94\x00\xFF\x80\xB1\xFA\x45\x1F\x03\x45\x73\x74\x45\x49" + "\x44\x20\x76\x65\x72\x20\x31\x2E\x30\x43"), CARD_TYPE_MICARDO }, /* EstEID (Estonian Big Brother card) */ { 0 } }; +#undef X /* The Pin Types as defined in pkcs#15 v1.1 */ diff --git a/scd/app.c b/scd/app.c index 2c8c915d7..f27b400b1 100644 --- a/scd/app.c +++ b/scd/app.c @@ -357,7 +357,7 @@ app_munge_serialno (app_t app) gpg_error_t app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp) { - unsigned char *buf, *p; + char *buf, *p; int i; if (!app || !serial) diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c index 9ac655e63..096a6811b 100644 --- a/scd/ccid-driver.c +++ b/scd/ccid-driver.c @@ -555,7 +555,7 @@ get_escaped_usb_string (usb_dev_handle *idev, int idx, all in a 2 bute Unicode encoding using little endian. */ rc = usb_control_msg (idev, USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8), 0, - buf, sizeof buf, 1000 /* ms timeout */); + (char*)buf, sizeof buf, 1000 /* ms timeout */); if (rc < 4) langid = 0x0409; /* English. */ else @@ -563,7 +563,7 @@ get_escaped_usb_string (usb_dev_handle *idev, int idx, rc = usb_control_msg (idev, USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) + idx, langid, - buf, sizeof buf, 1000 /* ms timeout */); + (char*)buf, sizeof buf, 1000 /* ms timeout */); if (rc < 2 || buf[1] != USB_DT_STRING) return NULL; /* Error or not a string. */ len = buf[0]; @@ -1155,7 +1155,7 @@ bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen) rc = usb_bulk_write (handle->idev, handle->ep_bulk_out, - msg, msglen, + (char*)msg, msglen, 1000 /* ms timeout */); if (rc == msglen) return 0; @@ -1188,7 +1188,7 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, retry: rc = usb_bulk_read (handle->idev, handle->ep_bulk_in, - buffer, length, + (char*)buffer, length, timeout); if (rc < 0) { @@ -1300,7 +1300,7 @@ ccid_poll (ccid_driver_t handle) rc = usb_bulk_read (handle->idev, handle->ep_intr, - msg, sizeof msg, + (char*)msg, sizeof msg, 0 /* ms timeout */ ); if (rc < 0 && errno == ETIMEDOUT) return 0; @@ -1444,7 +1444,7 @@ ccid_get_atr (ccid_driver_t handle, { tried_iso = 1; /* Try switching to ISO mode. */ - if (!send_escape_cmd (handle, "\xF1\x01", 2)) + if (!send_escape_cmd (handle, (const unsigned char*)"\xF1\x01", 2)) goto again; } else if (CCID_COMMAND_FAILED (msg)) @@ -2026,7 +2026,7 @@ ccid_transceive_secure (ccid_driver_t handle, if (handle->id_vendor == VENDOR_SCM) { DEBUGOUT ("sending escape sequence to switch to a case 1 APDU\n"); - rc = send_escape_cmd (handle, "\x80\x02\x00", 3); + rc = send_escape_cmd (handle, (const unsigned char*)"\x80\x02\x00", 3); if (rc) return rc; } diff --git a/scd/command.c b/scd/command.c index a308078d3..52a86871e 100644 --- a/scd/command.c +++ b/scd/command.c @@ -679,7 +679,7 @@ pin_cb (void *opaque, const char *info, char **retstr) xfree (value); return gpg_error (GPG_ERR_INV_RESPONSE); } - *retstr = value; + *retstr = (char*)value; return 0; } @@ -844,7 +844,7 @@ cmd_getattr (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; - char *keyword; + const char *keyword; if ((rc = open_card (ctrl, NULL))) return rc; @@ -860,7 +860,6 @@ cmd_getattr (assuan_context_t ctx, char *line) /* FIXME: Applications should not return sensistive data if the card is locked. */ rc = app_getattr (ctrl->app_ctx, ctrl, keyword); - xfree (keyword); TEST_CARD_REMOVAL (ctrl, rc); return map_to_assuan_status (rc); @@ -908,9 +907,10 @@ cmd_setattr (assuan_context_t ctx, char *orig_line) *line++ = 0; while (spacep (line)) line++; - nbytes = percent_plus_unescape (line); + nbytes = percent_plus_unescape ((unsigned char*)line); - rc = app_setattr (ctrl->app_ctx, keyword, pin_cb, ctx, line, nbytes); + rc = app_setattr (ctrl->app_ctx, keyword, pin_cb, ctx, + (const unsigned char*)line, nbytes); xfree (linebuf); TEST_CARD_REMOVAL (ctrl, rc); diff --git a/scd/iso7816.c b/scd/iso7816.c index e9dc6541c..742ed9433 100644 --- a/scd/iso7816.c +++ b/scd/iso7816.c @@ -153,7 +153,7 @@ iso7816_select_file (int slot, int tag, int is_dir, p0 = (tag == 0x3F00)? 0: is_dir? 1:2; p1 = 0x0c; /* No FC return. */ sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, - p0, p1, 2, tagbuf ); + p0, p1, 2, (char*)tagbuf ); return map_sw (sw); } @@ -285,7 +285,7 @@ iso7816_put_data (int slot, int tag, sw = apdu_send_simple (slot, 0x00, CMD_PUT_DATA, ((tag >> 8) & 0xff), (tag & 0xff), - datalen, data); + datalen, (const char*)data); return map_sw (sw); } @@ -303,7 +303,7 @@ iso7816_manage_security_env (int slot, int p1, int p2, return gpg_error (GPG_ERR_INV_VALUE); sw = apdu_send_simple (slot, 0x00, CMD_MSE, p1, p2, - data? datalen : -1, data); + data? datalen : -1, (const char*)data); return map_sw (sw); } @@ -323,7 +323,7 @@ iso7816_compute_ds (int slot, const unsigned char *data, size_t datalen, *result = NULL; *resultlen = 0; - sw = apdu_send (slot, 0x00, CMD_PSO, 0x9E, 0x9A, datalen, data, + sw = apdu_send (slot, 0x00, CMD_PSO, 0x9E, 0x9A, datalen, (const char*)data, result, resultlen); if (sw != SW_SUCCESS) { @@ -364,13 +364,15 @@ iso7816_decipher (int slot, const unsigned char *data, size_t datalen, *buf = padind; /* Padding indicator. */ memcpy (buf+1, data, datalen); - sw = apdu_send (slot, 0x00, CMD_PSO, 0x80, 0x86, datalen+1, buf, + sw = apdu_send (slot, 0x00, CMD_PSO, 0x80, 0x86, + datalen+1, (char*)buf, result, resultlen); xfree (buf); } else { - sw = apdu_send (slot, 0x00, CMD_PSO, 0x80, 0x86, datalen, data, + sw = apdu_send (slot, 0x00, CMD_PSO, 0x80, 0x86, + datalen, (const char *)data, result, resultlen); } if (sw != SW_SUCCESS) @@ -399,7 +401,7 @@ iso7816_internal_authenticate (int slot, *resultlen = 0; sw = apdu_send (slot, 0x00, CMD_INTERNAL_AUTHENTICATE, 0, 0, - datalen, data, result, resultlen); + datalen, (const char*)data, result, resultlen); if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ @@ -426,7 +428,7 @@ do_generate_keypair (int slot, int readonly, *resultlen = 0; sw = apdu_send (slot, 0x00, CMD_GENERATE_KEYPAIR, readonly? 0x81:0x80, 0, - datalen, data, result, resultlen); + datalen, (const char*)data, result, resultlen); if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ diff --git a/scd/pcsc-wrapper.c b/scd/pcsc-wrapper.c index 93e78fdfe..21af16fba 100644 --- a/scd/pcsc-wrapper.c +++ b/scd/pcsc-wrapper.c @@ -390,9 +390,9 @@ handle_open (unsigned char *argbuf, size_t arglen) unsigned char atr[33]; /* Make sure there is only the port string */ - if (arglen != strlen (argbuf)) + if (arglen != strlen ((char*)argbuf)) bad_request ("OPEN"); - portstr = argbuf; + portstr = (char*)argbuf; if (driver_is_open) { diff --git a/sm/ChangeLog b/sm/ChangeLog index ffb61a294..d9f295e1d 100644 --- a/sm/ChangeLog +++ b/sm/ChangeLog @@ -1,3 +1,35 @@ +2005-06-15 Werner Koch + + * delete.c (delete_one): Changed FPR to unsigned. + * encrypt.c (encrypt_dek): Made ENCVAL unsigned. + (gpgsm_encrypt): Ditto. + * sign.c (gpgsm_sign): Made SIGVAL unsigned. + * base64.c (base64_reader_cb): Need to use some casting to get + around signed/unsigned char* warnings. + * certcheck.c (gpgsm_check_cms_signature): Ditto. + (gpgsm_create_cms_signature): Changed arg R_SIGVAL to unsigned char*. + (do_encode_md): Made NFRAME a size_t. + * certdump.c (gpgsm_print_serial): Fixed signed/unsigned warning. + (gpgsm_dump_serial): Ditto. + (gpgsm_format_serial): Ditto. + (gpgsm_dump_string): Ditto. + (gpgsm_dump_cert): Ditto. + (parse_dn_part): Ditto. + (gpgsm_print_name2): Ditto. + * keylist.c (email_kludge): Ditto. + * certreqgen.c (proc_parameters, create_request): Ditto. + (create_request): Ditto. + * call-agent.c (gpgsm_agent_pksign): Made arg R_BUF unsigned. + (struct cipher_parm_s): Made CIPHERTEXT unsigned. + (struct genkey_parm_s): Ditto. + * server.c (strcpy_escaped_plus): Made arg S signed char*. + * fingerprint.c (gpgsm_get_fingerprint): Made ARRAY unsigned. + (gpgsm_get_keygrip): Ditto. + * keydb.c (keydb_insert_cert): Made DIGEST unsigned. + (keydb_update_cert): Ditto. + (classify_user_id): Apply cast to signed/unsigned assignment. + (hextobyte): Ditto. + 2005-06-01 Werner Koch * misc.c: Include setenv.h. diff --git a/sm/base64.c b/sm/base64.c index 4cc6ffa27..62c2c9ad9 100644 --- a/sm/base64.c +++ b/sm/base64.c @@ -95,7 +95,7 @@ struct base64_context_s { /* The base-64 character list */ -static unsigned char bintoasc[64] = +static char bintoasc[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; @@ -202,8 +202,9 @@ base64_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread) { /* wait for the header line */ parm->linelen = parm->readpos = 0; - if (!parm->have_lf || strncmp (parm->line, "-----BEGIN ", 11) - || !strncmp (parm->line+11, "PGP ", 4)) + if (!parm->have_lf + || strncmp ((char*)parm->line, "-----BEGIN ", 11) + || !strncmp ((char*)parm->line+11, "PGP ", 4)) goto next; parm->is_pem = 1; } @@ -220,8 +221,9 @@ base64_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread) /* the very first byte does pretty much look like a SEQUENCE tag*/ parm->is_pem = 0; } - else if ( parm->have_lf && !strncmp (parm->line, "-----BEGIN ", 11) - && strncmp (parm->line+11, "PGP ", 4) ) + else if ( parm->have_lf + && !strncmp ((char*)parm->line, "-----BEGIN ", 11) + && strncmp ((char *)parm->line+11, "PGP ", 4) ) { /* Fixme: we must only compare if the line really starts at the beginning */ @@ -268,7 +270,7 @@ base64_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread) if (parm->is_pem || parm->is_base64) { if (parm->is_pem && parm->have_lf - && !strncmp (parm->line, "-----END ", 9)) + && !strncmp ((char*)parm->line, "-----END ", 9)) { parm->identified = 0; parm->linelen = parm->readpos = 0; diff --git a/sm/call-agent.c b/sm/call-agent.c index 885abf421..92a29928c 100644 --- a/sm/call-agent.c +++ b/sm/call-agent.c @@ -39,24 +39,27 @@ #include "../common/membuf.h" -static ASSUAN_CONTEXT agent_ctx = NULL; +static assuan_context_t agent_ctx = NULL; static int force_pipe_server = 0; -struct cipher_parm_s { - ASSUAN_CONTEXT ctx; - const char *ciphertext; +struct cipher_parm_s +{ + assuan_context_t ctx; + const unsigned char *ciphertext; size_t ciphertextlen; }; -struct genkey_parm_s { - ASSUAN_CONTEXT ctx; - const char *sexp; +struct genkey_parm_s +{ + assuan_context_t ctx; + const unsigned char *sexp; size_t sexplen; }; -struct learn_parm_s { +struct learn_parm_s +{ int error; - ASSUAN_CONTEXT ctx; + assuan_context_t ctx; membuf_t *data; }; @@ -204,7 +207,7 @@ membuf_data_cb (void *opaque, const void *buffer, size_t length) int gpgsm_agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc, unsigned char *digest, size_t digestlen, int digestalgo, - char **r_buf, size_t *r_buflen ) + unsigned char **r_buf, size_t *r_buflen ) { int rc, i; char *p, line[ASSUAN_LINELENGTH]; @@ -392,7 +395,7 @@ gpgsm_agent_genkey (ctrl_t ctrl, struct genkey_parm_s gk_parm; membuf_t data; size_t len; - char *buf; + unsigned char *buf; *r_pubkey = NULL; rc = start_agent (ctrl); diff --git a/sm/certcheck.c b/sm/certcheck.c index 611d3219c..84dfdb9ab 100644 --- a/sm/certcheck.c +++ b/sm/certcheck.c @@ -39,7 +39,8 @@ static int do_encode_md (gcry_md_hd_t md, int algo, int pkalgo, unsigned int nbits, gcry_mpi_t *r_val) { - int n, nframe; + int n; + size_t nframe; unsigned char *frame; if (pkalgo == GCRY_PK_DSA) @@ -205,7 +206,7 @@ gpgsm_check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert) log_printf ("\n"); } - rc = gcry_sexp_sscan ( &s_sig, NULL, p, n); + rc = gcry_sexp_sscan ( &s_sig, NULL, (char*)p, n); ksba_free (p); if (rc) { @@ -224,7 +225,7 @@ gpgsm_check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert) gcry_sexp_release (s_sig); return gpg_error (GPG_ERR_BUG); } - rc = gcry_sexp_sscan ( &s_pkey, NULL, p, n); + rc = gcry_sexp_sscan ( &s_pkey, NULL, (char*)p, n); ksba_free (p); if (rc) { @@ -278,7 +279,7 @@ gpgsm_check_cms_signature (ksba_cert_t cert, ksba_const_sexp_t sigval, log_error ("libksba did not return a proper S-Exp\n"); return gpg_error (GPG_ERR_BUG); } - rc = gcry_sexp_sscan (&s_sig, NULL, sigval, n); + rc = gcry_sexp_sscan (&s_sig, NULL, (char*)sigval, n); if (rc) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); @@ -297,7 +298,7 @@ gpgsm_check_cms_signature (ksba_cert_t cert, ksba_const_sexp_t sigval, if (DBG_CRYPTO) log_printhex ("public key: ", p, n); - rc = gcry_sexp_sscan ( &s_pkey, NULL, p, n); + rc = gcry_sexp_sscan ( &s_pkey, NULL, (char*)p, n); ksba_free (p); if (rc) { @@ -333,7 +334,8 @@ gpgsm_check_cms_signature (ksba_cert_t cert, ksba_const_sexp_t sigval, int gpgsm_create_cms_signature (ctrl_t ctrl, ksba_cert_t cert, - gcry_md_hd_t md, int mdalgo, char **r_sigval) + gcry_md_hd_t md, int mdalgo, + unsigned char **r_sigval) { int rc; char *grip, *desc; diff --git a/sm/certdump.c b/sm/certdump.c index 26510c70d..98f019c4a 100644 --- a/sm/certdump.c +++ b/sm/certdump.c @@ -50,8 +50,9 @@ struct dn_array_s { /* print the first element of an S-Expression */ void -gpgsm_print_serial (FILE *fp, ksba_const_sexp_t p) +gpgsm_print_serial (FILE *fp, ksba_const_sexp_t sn) { + const char *p = (const char *)sn; unsigned long n; char *endp; @@ -77,8 +78,9 @@ gpgsm_print_serial (FILE *fp, ksba_const_sexp_t p) /* Dump the serial number or any other simple S-expression. */ void -gpgsm_dump_serial (ksba_const_sexp_t p) +gpgsm_dump_serial (ksba_const_sexp_t sn) { + const char *p = (const char *)sn; unsigned long n; char *endp; @@ -103,8 +105,9 @@ gpgsm_dump_serial (ksba_const_sexp_t p) char * -gpgsm_format_serial (ksba_const_sexp_t p) +gpgsm_format_serial (ksba_const_sexp_t sn) { + const char *p = (const char *)sn; unsigned long n; char *endp; char *buffer; @@ -168,7 +171,7 @@ gpgsm_dump_string (const char *string) { const unsigned char *s; - for (s=string; *s; s++) + for (s=(const unsigned char*)string; *s; s++) { if (*s < ' ' || (*s >= 0x7f && *s <= 0xa0)) break; @@ -190,7 +193,7 @@ void gpgsm_dump_cert (const char *text, ksba_cert_t cert) { ksba_sexp_t sexp; - unsigned char *p; + char *p; char *dn; ksba_isotime_t t; @@ -260,7 +263,7 @@ parse_dn_part (struct dn_array_s *array, const unsigned char *string) }; const unsigned char *s, *s1; size_t n; - unsigned char *p; + char *p; int i; /* Parse attributeType */ @@ -306,7 +309,7 @@ parse_dn_part (struct dn_array_s *array, const unsigned char *string) return NULL; for (s1=string; n; s1 += 2, n--, p++) { - *p = xtoi_2 (s1); + *(unsigned char *)p = xtoi_2 (s1); if (!*p) *p = 0x01; /* Better print a wrong value than truncating the string. */ @@ -351,7 +354,7 @@ parse_dn_part (struct dn_array_s *array, const unsigned char *string) s++; if (hexdigitp (s)) { - *p++ = xtoi_2 (s); + *(unsigned char *)p++ = xtoi_2 (s); s++; } else @@ -485,23 +488,22 @@ print_dn_parts (FILE *fp, struct dn_array_s *dn, int translate) void gpgsm_print_name2 (FILE *fp, const char *name, int translate) { - const unsigned char *s; + const unsigned char *s = (const unsigned char *)name; int i; - s = name; if (!s) { fputs (_("[Error - No name]"), fp); } else if (*s == '<') { - const unsigned char *s2 = strchr (s+1, '>'); + const char *s2 = strchr ( (char*)s+1, '>'); if (s2) { if (translate) - print_sanitized_utf8_buffer (fp, s + 1, s2 - s - 1, 0); + print_sanitized_utf8_buffer (fp, s + 1, s2 - (char*)s - 1, 0); else - print_sanitized_buffer (fp, s + 1, s2 - s - 1, 0); + print_sanitized_buffer (fp, s + 1, s2 - (char*)s - 1, 0); } } else if (*s == '(') diff --git a/sm/certreqgen.c b/sm/certreqgen.c index 7b29a5b8d..2b920da7e 100644 --- a/sm/certreqgen.c +++ b/sm/certreqgen.c @@ -492,7 +492,7 @@ proc_parameters (ctrl_t ctrl, } sprintf (numbuf, "%u", nbits); - snprintf (keyparms, DIM (keyparms)-1, + snprintf ((char*)keyparms, DIM (keyparms)-1, "(6:genkey(3:rsa(5:nbits%d:%s)))", (int)strlen (numbuf), numbuf); rc = gpgsm_agent_genkey (ctrl, keyparms, &public); if (rc) @@ -627,8 +627,9 @@ create_request (ctrl_t ctrl, { gcry_sexp_t s_pkey; size_t n; - unsigned char grip[20], hexgrip[41]; - char *sigval; + unsigned char grip[20]; + char hexgrip[41]; + unsigned char *sigval; size_t siglen; n = gcry_sexp_canon_len (public, 0, NULL, NULL); @@ -638,7 +639,7 @@ create_request (ctrl_t ctrl, err = gpg_error (GPG_ERR_BUG); goto leave; } - rc = gcry_sexp_sscan (&s_pkey, NULL, public, n); + rc = gcry_sexp_sscan (&s_pkey, NULL, (const char*)public, n); if (rc) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); diff --git a/sm/delete.c b/sm/delete.c index 11a0a5476..8e06b9489 100644 --- a/sm/delete.c +++ b/sm/delete.c @@ -67,7 +67,7 @@ delete_one (CTRL ctrl, const char *username) rc = keydb_get_cert (kh, &cert); if (!rc) { - char fpr[20]; + unsigned char fpr[20]; gpgsm_get_fingerprint (cert, 0, fpr, NULL); @@ -78,7 +78,7 @@ delete_one (CTRL ctrl, const char *username) else if (!rc) { ksba_cert_t cert2 = NULL; - char fpr2[20]; + unsigned char fpr2[20]; /* We ignore all duplicated certificates which might have been inserted due to program bugs. */ diff --git a/sm/encrypt.c b/sm/encrypt.c index 50da92c32..e4c0d5437 100644 --- a/sm/encrypt.c +++ b/sm/encrypt.c @@ -164,10 +164,10 @@ encode_session_key (DEK dek, gcry_sexp_t * r_data) } -/* encrypt the DEK under the key contained in CERT and return it as a - canonical S-Exp in encval */ +/* Encrypt the DEK under the key contained in CERT and return it as a + canonical S-Exp in encval. */ static int -encrypt_dek (const DEK dek, ksba_cert_t cert, char **encval) +encrypt_dek (const DEK dek, ksba_cert_t cert, unsigned char **encval) { gcry_sexp_t s_ciph, s_data, s_pkey; int rc; @@ -189,7 +189,7 @@ encrypt_dek (const DEK dek, ksba_cert_t cert, char **encval) log_error ("libksba did not return a proper S-Exp\n"); return gpg_error (GPG_ERR_BUG); } - rc = gcry_sexp_sscan (&s_pkey, NULL, buf, len); + rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)buf, len); xfree (buf); buf = NULL; if (rc) { @@ -220,7 +220,7 @@ encrypt_dek (const DEK dek, ksba_cert_t cert, char **encval) gcry_sexp_release (s_ciph); return tmperr; } - len = gcry_sexp_sprint (s_ciph, GCRYSEXP_FMT_CANON, buf, len); + len = gcry_sexp_sprint (s_ciph, GCRYSEXP_FMT_CANON, (char*)buf, len); assert (len); *encval = buf; @@ -437,7 +437,7 @@ gpgsm_encrypt (CTRL ctrl, CERTLIST recplist, int data_fd, FILE *out_fp) each and store them in the CMS object */ for (recpno = 0, cl = recplist; cl; recpno++, cl = cl->next) { - char *encval; + unsigned char *encval; rc = encrypt_dek (dek, cl->cert, &encval); if (rc) diff --git a/sm/fingerprint.c b/sm/fingerprint.c index 7fe619c18..9c3ab85db 100644 --- a/sm/fingerprint.c +++ b/sm/fingerprint.c @@ -42,8 +42,9 @@ If there is a problem , the function does never return NULL but a digest of all 0xff. */ -char * -gpgsm_get_fingerprint (ksba_cert_t cert, int algo, char *array, int *r_len) +unsigned char * +gpgsm_get_fingerprint (ksba_cert_t cert, int algo, + unsigned char *array, int *r_len) { gcry_md_hd_t md; int rc, len; @@ -140,8 +141,8 @@ gpgsm_get_short_fingerprint (ksba_cert_t cert) key parameters expressed as an canoncial encoded S-Exp. array must be 20 bytes long. returns the array or a newly allocated one if the passed one was NULL */ -char * -gpgsm_get_keygrip (ksba_cert_t cert, char *array) +unsigned char * +gpgsm_get_keygrip (ksba_cert_t cert, unsigned char *array) { gcry_sexp_t s_pkey; int rc; @@ -160,7 +161,7 @@ gpgsm_get_keygrip (ksba_cert_t cert, char *array) log_error ("libksba did not return a proper S-Exp\n"); return NULL; } - rc = gcry_sexp_sscan ( &s_pkey, NULL, p, n); + rc = gcry_sexp_sscan ( &s_pkey, NULL, (char*)p, n); xfree (p); if (rc) { @@ -223,7 +224,7 @@ gpgsm_get_key_algo_info (ksba_cert_t cert, unsigned int *nbits) xfree (p); return 0; } - rc = gcry_sexp_sscan (&s_pkey, NULL, p, n); + rc = gcry_sexp_sscan (&s_pkey, NULL, (char *)p, n); xfree (p); if (rc) return 0; @@ -272,7 +273,7 @@ char * gpgsm_get_certid (ksba_cert_t cert) { ksba_sexp_t serial; - unsigned char *p; + char *p; char *endp; unsigned char hash[20]; unsigned long n; @@ -288,7 +289,7 @@ gpgsm_get_certid (ksba_cert_t cert) serial = ksba_cert_get_serial (cert); if (!serial) return NULL; /* oops: no serial number */ - p = serial; + p = (char *)serial; if (*p != '(') { log_error ("Ooops: invalid serial number\n"); diff --git a/sm/gpgsm.h b/sm/gpgsm.h index 1068e9d5e..2f3e83485 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -181,12 +181,12 @@ gpg_error_t gpgsm_status_with_err_code (ctrl_t ctrl, int no, const char *text, gpg_err_code_t ec); /*-- fingerprint --*/ -char *gpgsm_get_fingerprint (ksba_cert_t cert, int algo, - char *array, int *r_len); +unsigned char *gpgsm_get_fingerprint (ksba_cert_t cert, int algo, + unsigned char *array, int *r_len); char *gpgsm_get_fingerprint_string (ksba_cert_t cert, int algo); char *gpgsm_get_fingerprint_hexstring (ksba_cert_t cert, int algo); unsigned long gpgsm_get_short_fingerprint (ksba_cert_t cert); -char *gpgsm_get_keygrip (ksba_cert_t cert, char *array); +unsigned char *gpgsm_get_keygrip (ksba_cert_t cert, unsigned char *array); char *gpgsm_get_keygrip_hexstring (ksba_cert_t cert); int gpgsm_get_key_algo_info (ksba_cert_t cert, unsigned int *nbits); char *gpgsm_get_certid (ksba_cert_t cert); @@ -229,7 +229,7 @@ int gpgsm_check_cms_signature (ksba_cert_t cert, ksba_const_sexp_t sigval, /* fixme: move create functions to another file */ int gpgsm_create_cms_signature (ctrl_t ctrl, ksba_cert_t cert, gcry_md_hd_t md, int mdalgo, - char **r_sigval); + unsigned char **r_sigval); /*-- certchain.c --*/ @@ -293,7 +293,7 @@ int gpgsm_agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc, unsigned char *digest, size_t digestlen, int digestalgo, - char **r_buf, size_t *r_buflen); + unsigned char **r_buf, size_t *r_buflen); int gpgsm_agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc, ksba_const_sexp_t ciphertext, char **r_buf, size_t *r_buflen); diff --git a/sm/keydb.c b/sm/keydb.c index 293e5233d..17f04fe4b 100644 --- a/sm/keydb.c +++ b/sm/keydb.c @@ -681,7 +681,7 @@ keydb_insert_cert (KEYDB_HANDLE hd, ksba_cert_t cert) { int rc = -1; int idx; - char digest[20]; + unsigned char digest[20]; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); @@ -723,7 +723,7 @@ int keydb_update_cert (KEYDB_HANDLE hd, ksba_cert_t cert) { int rc = 0; - char digest[20]; + unsigned char digest[20]; if (!hd) return gpg_error (GPG_ERR_INV_VALUE); @@ -1010,8 +1010,9 @@ keydb_search_subject (KEYDB_HANDLE hd, const char *name) static int -hextobyte (const unsigned char *s) +hextobyte (const char *string) { + const unsigned char *s = (const unsigned char *)string; int c; if( *s >= '0' && *s <= '9' ) @@ -1122,7 +1123,7 @@ classify_user_id (const char *name, if (!strchr("01234567890abcdefABCDEF", *si)) return 0; /* invalid digit in serial number*/ } - desc->sn = s; + desc->sn = (const unsigned char*)s; desc->snlen = -1; if (!*si) mode = KEYDB_SEARCH_MODE_SN; diff --git a/sm/keylist.c b/sm/keylist.c index 8e1233341..a0ac73fb3 100644 --- a/sm/keylist.c +++ b/sm/keylist.c @@ -256,7 +256,7 @@ print_time (gnupg_isotime_t t, FILE *fp) static char * email_kludge (const char *name) { - const unsigned char *p; + const char *p; unsigned char *buf; int n; @@ -278,7 +278,7 @@ email_kludge (const char *name) buf[n] = xtoi_2 (p); buf[n++] = '>'; buf[n] = 0; - return buf; + return (char*)buf; } diff --git a/sm/server.c b/sm/server.c index 7bfd3fc20..b3816d3d9 100644 --- a/sm/server.c +++ b/sm/server.c @@ -53,14 +53,14 @@ struct server_local_s { /* Note that it is sufficient to allocate the target string D as long as the source string S, i.e.: strlen(s)+1; */ static void -strcpy_escaped_plus (char *d, const unsigned char *s) +strcpy_escaped_plus (char *d, const char *s) { while (*s) { if (*s == '%' && s[1] && s[2]) { s++; - *d++ = xtoi_2 ( s); + *d++ = xtoi_2 (s); s += 2; } else if (*s == '+') diff --git a/sm/sign.c b/sm/sign.c index 5deef6088..3230a0e98 100644 --- a/sm/sign.c +++ b/sm/sign.c @@ -575,7 +575,7 @@ gpgsm_sign (CTRL ctrl, CERTLIST signerlist, ksba_cms_set_hash_function (cms, HASH_FNC, md); for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) { - char *sigval = NULL; + unsigned char *sigval = NULL; char *buf, *fpr; if (signer) diff --git a/tools/ChangeLog b/tools/ChangeLog index 39f17e2ce..5965b2871 100644 --- a/tools/ChangeLog +++ b/tools/ChangeLog @@ -1,3 +1,8 @@ +2005-06-16 Werner Koch + + * gpg-connect-agent.c (read_and_print_response): Made LINELEN a + size_t. + 2005-06-04 Marcus Brinkmann * symcryptrun.c (main): Allow any number of arguments, don't use diff --git a/tools/gpg-connect-agent.c b/tools/gpg-connect-agent.c index bb05030ee..c9a324fa8 100644 --- a/tools/gpg-connect-agent.c +++ b/tools/gpg-connect-agent.c @@ -458,7 +458,7 @@ static int read_and_print_response (assuan_context_t ctx) { char *line; - int linelen; + size_t linelen; assuan_error_t rc; int i, j; diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index c49d1dcbb..e8d9ca27e 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -2316,7 +2316,7 @@ gc_component_change_options (int component, FILE *in) char *linep; unsigned long flags = 0; char *new_value = ""; - unsigned long new_value_nr; + unsigned long new_value_nr = 0; /* Strip newline and carriage return, if present. */ while (length > 0 diff --git a/tools/gpgkey2ssh.c b/tools/gpgkey2ssh.c index 75b18b29b..e874ab22e 100644 --- a/tools/gpgkey2ssh.c +++ b/tools/gpgkey2ssh.c @@ -249,6 +249,9 @@ main (int argc, char **argv) pkdbuf = NULL; pkdbuf_n = 0; + algorithm_id = 0; /* (avoid cc warning) */ + identifier = NULL; /* (avoid cc warning) */ + assert (argc == 2); keyid = argv[1]; diff --git a/tools/watchgnupg.c b/tools/watchgnupg.c index 25ca8c413..6cb570fbc 100644 --- a/tools/watchgnupg.c +++ b/tools/watchgnupg.c @@ -223,7 +223,7 @@ main (int argc, char **argv) int force = 0; struct sockaddr_un srvr_addr; - int addrlen; + socklen_t addrlen; int server; int flags; client_t client_list = NULL; -- cgit From 6a13cf2c3dbacb9f3afd3f64e5d0c78b9c0e77e9 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 28 Nov 2005 11:52:25 +0000 Subject: Preparing an interim release --- ChangeLog | 16 +++++ NEWS | 7 ++ TODO | 1 - agent/ChangeLog | 17 +++++ agent/agent.h | 7 +- agent/call-scd.c | 51 +++++++++----- agent/command.c | 9 ++- agent/divert-scd.c | 19 ++++- agent/minip12.c | 21 ++++-- agent/query.c | 196 +++++++++++++++++++++++++++++++++++++++++++-------- agent/t-protect.c | 3 +- am/cmacros.am | 3 +- configure.ac | 11 +-- kbx/keybox-blob.c | 2 +- po/POTFILES.in | 1 + po/de.po | 16 +++-- scd/ChangeLog | 55 +++++++++++++++ scd/apdu.c | 176 +++++++++++++++++++++++++++++++++++---------- scd/apdu.h | 9 ++- scd/app-dinsig.c | 41 +++++++++-- scd/app-nks.c | 11 +-- scd/app-openpgp.c | 9 ++- scd/app-p15.c | 13 ++-- scd/ccid-driver.c | 64 ++++++++++++++--- scd/ccid-driver.h | 7 +- scd/iso7816.c | 115 +++++++++++++++++++++++++----- scd/iso7816.h | 35 ++++++++- scd/scdaemon.c | 6 +- scd/scdaemon.h | 17 ++--- tools/ChangeLog | 8 +++ tools/gpgconf-comp.c | 7 +- tools/rfc822parse.c | 1 + 32 files changed, 784 insertions(+), 170 deletions(-) (limited to 'agent/command.c') diff --git a/ChangeLog b/ChangeLog index f63a035cc..46d66ed47 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2005-11-28 Werner Koch + + * configure.ac: Append the revision to the version string. + +2005-11-13 Werner Koch + + * am/cmacros.am (-DGNUPG_SYSCONFDIR): Define it. + +2005-11-11 Werner Koch + + * configure.ac (NEED_KSBA_VERSION: Require 0.9.13. + +2005-09-12 Werner Koch + + Released 1.9.19. + 2005-08-01 Werner Koch Released 1.9.18. diff --git a/NEWS b/NEWS index 1e1148748..edf29885d 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,10 @@ +Noteworthy changes in version 1.9.20 +------------------------------------------------- + + * [scdaemon] Support for keypads of some readers. Tested only with + SPR532. New option --disable-keypad. + + Noteworthy changes in version 1.9.19 (2005-09-12) ------------------------------------------------- diff --git a/TODO b/TODO index 32b728588..50f58cee9 100644 --- a/TODO +++ b/TODO @@ -26,7 +26,6 @@ might want to have an agent context for each service request * sm/gpgsm.c ** Support --output for all commands ** mark all unimplemented commands and options. -** Print a hint when MD2 is the cause for a problem. ** Implement --default-key ** support the anyPolicy semantic ** Check that we are really following the verification procedures in rfc3280. diff --git a/agent/ChangeLog b/agent/ChangeLog index 975484007..105178730 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,20 @@ +2005-11-24 Werner Koch + + * minip12.c (p12_parse): Fixed for case that the key object comes + prior to the certificate. + +2005-10-19 Werner Koch + + * divert-scd.c (getpin_cb): Hack to use it for a keypad message. + + * call-scd.c (inq_needpin): Reworked to support the new KEYPADINFO. + + * query.c (start_pinentry): Keep track of the owner. + (popup_message_thread, agent_popup_message_start) + (agent_popup_message_stop, agent_reset_query): New. + * command.c (start_command_handler): Make sure a popup window gets + closed. + 2005-10-08 Marcus Brinkmann * Makefile.am (gpg_protect_tool_LDADD): Add ../gl/libgnu.a. diff --git a/agent/agent.h b/agent/agent.h index 7a646a85f..0918395ce 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -133,7 +133,7 @@ struct server_control_s int have_keygrip; int use_auth_call; /* Hack to send the PKAUTH command instead of the - PKSIGN command tro scdaemon. */ + PKSIGN command to the scdaemon. */ }; typedef struct server_control_s *CTRL; typedef struct server_control_s *ctrl_t; @@ -200,6 +200,7 @@ int agent_key_available (const unsigned char *grip); /*-- query.c --*/ void initialize_module_query (void); void agent_query_dump_state (void); +void agent_reset_query (ctrl_t ctrl); int agent_askpin (ctrl_t ctrl, const char *desc_text, const char *prompt_text, const char *inital_errtext, @@ -209,6 +210,10 @@ int agent_get_passphrase (ctrl_t ctrl, char **retpass, const char *errtext); int agent_get_confirmation (ctrl_t ctrl, const char *desc, const char *ok, const char *cancel); +int agent_popup_message_start (ctrl_t ctrl, const char *desc, + const char *ok_btn, const char *cancel_btn); +void agent_popup_message_stop (ctrl_t ctrl); + /*-- cache.c --*/ void agent_flush_cache (void); diff --git a/agent/call-scd.c b/agent/call-scd.c index 7a623fda4..a883f2733 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -633,26 +633,43 @@ inq_needpin (void *opaque, const char *line) size_t pinlen; int rc; - if (!(!strncmp (line, "NEEDPIN", 7) && (line[7] == ' ' || !line[7]))) + if (!strncmp (line, "NEEDPIN", 7) && (line[7] == ' ' || !line[7])) { - log_error ("unsupported inquiry `%s'\n", line); - return ASSUAN_Inquire_Unknown; - } - line += 7; - while (*line == ' ') - line++; + line += 7; + while (*line == ' ') + line++; + + pinlen = 90; + pin = gcry_malloc_secure (pinlen); + if (!pin) + return ASSUAN_Out_Of_Core; - pinlen = 90; - pin = gcry_malloc_secure (pinlen); - if (!pin) - return ASSUAN_Out_Of_Core; + rc = parm->getpin_cb (parm->getpin_cb_arg, line, pin, pinlen); + if (rc) + rc = ASSUAN_Canceled; + if (!rc) + rc = assuan_send_data (parm->ctx, pin, pinlen); + xfree (pin); + } + else if (!strncmp (line, "KEYPADINFO", 10) && (line[10] == ' ' || !line[10])) + { + size_t code; + char *endp; - rc = parm->getpin_cb (parm->getpin_cb_arg, line, pin, pinlen); - if (rc) - rc = ASSUAN_Canceled; - if (!rc) - rc = assuan_send_data (parm->ctx, pin, pinlen); - xfree (pin); + code = strtoul (line+10, &endp, 10); + line = endp; + while (*line == ' ') + line++; + + rc = parm->getpin_cb (parm->getpin_cb_arg, line, NULL, code); + if (rc) + rc = ASSUAN_Canceled; + } + else + { + log_error ("unsupported inquiry `%s'\n", line); + rc = ASSUAN_Inquire_Unknown; + } return rc; } diff --git a/agent/command.c b/agent/command.c index c39bcc6ab..daf9b8698 100644 --- a/agent/command.c +++ b/agent/command.c @@ -316,11 +316,11 @@ cmd_sigkey (ASSUAN_CONTEXT ctx, char *line) this command is not used a default text will be used. Note, that this description implictly selects the label used for the entry box; if the string contains the string PIN (which in general will - not be translated), "PIN" is used, other wiese the translation of + not be translated), "PIN" is used, otherwise the translation of 'passphrase" is used. The description string should not contain blanks unless they are percent or '+' escaped. - The descrition is only valid for the next PKSIGN or PKDECRYPT + The description is only valid for the next PKSIGN or PKDECRYPT operation. */ static int @@ -399,7 +399,7 @@ cmd_sethash (ASSUAN_CONTEXT ctx, char *line) /* PKSIGN Perform the actual sign operation. Neither input nor output are - sensitive to eavesdropping */ + sensitive to eavesdropping. */ static int cmd_pksign (ASSUAN_CONTEXT ctx, char *line) { @@ -1085,6 +1085,9 @@ start_command_handler (int listen_fd, int fd) /* Reset the SCD if needed. */ agent_reset_scd (&ctrl); + /* Reset the pinentry (in case of popup messages). */ + agent_reset_query (&ctrl); + assuan_deinit_server (ctx); if (ctrl.display) free (ctrl.display); diff --git a/agent/divert-scd.c b/agent/divert-scd.c index 9d2fa446c..926df2622 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -204,7 +204,7 @@ getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf) const char *again_text = NULL; const char *prompt = "PIN"; - if (maxbuf < 2) + if (buf && maxbuf < 2) return gpg_error (GPG_ERR_INV_VALUE); /* Parse the flags. */ @@ -223,6 +223,23 @@ getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf) else if (info && *info == '|') log_debug ("pin_cb called without proper PIN info hack\n"); + /* If BUF has been passed as NULL, we are in keypad mode: The + callback opens the popup and immediatley returns. */ + if (!buf) + { + if (maxbuf == 0) /* Close the pinentry. */ + { + agent_popup_message_stop (ctrl); + rc = 0; + } + else if (maxbuf == 1) /* Open the pinentry. */ + { + rc = agent_popup_message_start (ctrl, info, NULL, NULL); + } + else + rc = gpg_error (GPG_ERR_INV_VALUE); + return rc; + } /* FIXME: keep PI and TRIES in OPAQUE. Frankly this is a whole mess because we should call the card's verify function from the diff --git a/agent/minip12.c b/agent/minip12.c index 91eef63f4..55f3946bf 100644 --- a/agent/minip12.c +++ b/agent/minip12.c @@ -511,7 +511,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length, goto bailout; } - /* Loop over all certificates inside the bab. */ + /* Loop over all certificates inside the bag. */ while (n) { int isbag = 0; @@ -860,6 +860,7 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, size_t n = length; const char *where; int bagseqlength, len; + gcry_mpi_t *result = NULL; where = "pfx"; if (parse_tag (&p, &n, &ti)) @@ -936,10 +937,17 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, else if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_data) && !memcmp (p, oid_data, DIM(oid_data))) { - p += DIM(oid_data); - n -= DIM(oid_data); - len -= DIM(oid_data); - return parse_bag_data (p, n, (p-buffer), pw); + if (result) + log_info ("already got an data object, skipping next one\n"); + else + { + p += DIM(oid_data); + n -= DIM(oid_data); + len -= DIM(oid_data); + result = parse_bag_data (p, n, (p-buffer), pw); + if (!result) + goto bailout; + } } else log_info ( "unknown bag type - skipped\n"); @@ -950,9 +958,10 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, n -= len; } - return NULL; + return result; bailout: log_error ("error at \"%s\", offset %u\n", where, (p - buffer)); + /* fixme: need to release RESULT. */ return NULL; } diff --git a/agent/query.c b/agent/query.c index b231f6fc3..a5a3d0153 100644 --- a/agent/query.c +++ b/agent/query.c @@ -27,9 +27,10 @@ #include #include #include -#ifdef USE_GNU_PTH -# include +#ifndef HAVE_W32_SYSTEM +#include #endif +#include #include "agent.h" #include "i18n.h" @@ -48,14 +49,30 @@ time. */ #define LOCK_TIMEOUT (1*60) +/* The assuan context of the current pinentry. */ +static assuan_context_t entry_ctx; -static assuan_context_t entry_ctx = NULL; -#ifdef USE_GNU_PTH +/* The control variable of the connection owning the current pinentry. + This is only valid if ENTRY_CTX is not NULL. Note, that we care + only about the value of the pointer and that it should never be + dereferenced. */ +static ctrl_t entry_owner; + +/* A mutex used to serialize access to the pinentry. */ static pth_mutex_t entry_lock; -#endif -/* data to be passed to our callbacks */ -struct entry_parm_s { +/* The thread ID of the popup working thread. */ +static pth_t popup_tid; + +/* A flag used in communication between the popup working thread and + its stop function. */ +static int popup_finished; + + + +/* Data to be passed to our callbacks, */ +struct entry_parm_s +{ int lines; size_t size; unsigned char *buffer; @@ -67,17 +84,17 @@ struct entry_parm_s { /* This function must be called once to initialize this module. This has to be done before a second thread is spawned. We can't do the static initialization because Pth emulation code might not be able - to do a static init; in particualr, it is not possible for W32. */ + to do a static init; in particular, it is not possible for W32. */ void initialize_module_query (void) { -#ifdef USE_GNU_PTH static int initialized; if (!initialized) - if (pth_mutex_init (&entry_lock)) - initialized = 1; -#endif /*USE_GNU_PTH*/ + { + if (pth_mutex_init (&entry_lock)) + initialized = 1; + } } @@ -102,8 +119,19 @@ agent_query_dump_state (void) log_info ("agent_query_dump_state: entry_lock="); dump_mutex_state (&entry_lock); log_printf ("\n"); - log_info ("agent_query_dump_state: entry_ctx=%p pid=%ld\n", - entry_ctx, (long)assuan_get_pid (entry_ctx)); + log_info ("agent_query_dump_state: entry_ctx=%p pid=%ld popup_tid=%p\n", + entry_ctx, (long)assuan_get_pid (entry_ctx), popup_tid); +} + +/* Called to make sure that a popup window owned by the current + connection gets closed. */ +void +agent_reset_query (ctrl_t ctrl) +{ + if (entry_ctx && popup_tid && entry_owner == ctrl) + { + agent_popup_message_stop (ctrl); + } } @@ -117,14 +145,12 @@ unlock_pinentry (int rc) assuan_context_t ctx = entry_ctx; entry_ctx = NULL; -#ifdef USE_GNU_PTH if (!pth_mutex_release (&entry_lock)) { log_error ("failed to release the entry lock\n"); if (!rc) rc = gpg_error (GPG_ERR_INTERNAL); } -#endif assuan_disconnect (ctx); return rc; } @@ -145,7 +171,7 @@ atfork_cb (void *opaque, int where) pinentry - we will serialize _all_ pinentry calls. */ static int -start_pinentry (CTRL ctrl) +start_pinentry (ctrl_t ctrl) { int rc; const char *pgmname; @@ -153,13 +179,10 @@ start_pinentry (CTRL ctrl) const char *argv[5]; int no_close_list[3]; int i; + pth_event_t evt; -#ifdef USE_GNU_PTH - { - pth_event_t evt; - - evt = pth_event (PTH_EVENT_TIME, pth_timeout (LOCK_TIMEOUT, 0)); - if (!pth_mutex_acquire (&entry_lock, 0, evt)) + evt = pth_event (PTH_EVENT_TIME, pth_timeout (LOCK_TIMEOUT, 0)); + if (!pth_mutex_acquire (&entry_lock, 0, evt)) { if (pth_event_occurred (evt)) rc = gpg_error (GPG_ERR_TIMEOUT); @@ -170,9 +193,9 @@ start_pinentry (CTRL ctrl) gpg_strerror (rc)); return rc; } - pth_event_free (evt, PTH_FREE_THIS); - } -#endif + pth_event_free (evt, PTH_FREE_THIS); + + entry_owner = ctrl; if (entry_ctx) return 0; @@ -436,7 +459,7 @@ agent_askpin (ctrl_t ctrl, passphrase is returned in RETPASS as an hex encoded string to be freed by the caller */ int -agent_get_passphrase (CTRL ctrl, +agent_get_passphrase (ctrl_t ctrl, char **retpass, const char *desc, const char *prompt, const char *errtext) { @@ -517,11 +540,11 @@ agent_get_passphrase (CTRL ctrl, /* Pop up the PIN-entry, display the text and the prompt and ask the - user to confirm this. We return 0 for success, ie. the used + user to confirm this. We return 0 for success, ie. the user confirmed it, GPG_ERR_NOT_CONFIRMED for what the text says or an other error. */ int -agent_get_confirmation (CTRL ctrl, +agent_get_confirmation (ctrl_t ctrl, const char *desc, const char *ok, const char *cancel) { int rc; @@ -562,4 +585,119 @@ agent_get_confirmation (CTRL ctrl, } +/* The thread running the popup message. */ +static void * +popup_message_thread (void *arg) +{ + assuan_transact (entry_ctx, "CONFIRM", NULL, NULL, NULL, NULL, NULL, NULL); + popup_finished = 1; + return NULL; +} + + +/* Pop up a message window similar to the confirm one but keep it open + until agent_popup_message_stop has been called. It is crucial for + the caller to make sure that the stop function gets called as soon + as the message is not anymore required becuase the message is + system modal and all other attempts to use the pinentry will fail + (after a timeout). */ +int +agent_popup_message_start (ctrl_t ctrl, const char *desc, + const char *ok_btn, const char *cancel_btn) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + pth_attr_t tattr; + + rc = start_pinentry (ctrl); + if (rc) + return rc; + + if (desc) + snprintf (line, DIM(line)-1, "SETDESC %s", desc); + else + snprintf (line, DIM(line)-1, "RESET"); + line[DIM(line)-1] = 0; + rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_pinentry (map_assuan_err (rc)); + + if (ok_btn) + { + snprintf (line, DIM(line)-1, "SETOK %s", ok_btn); + line[DIM(line)-1] = 0; + rc = assuan_transact (entry_ctx, line, NULL,NULL,NULL,NULL,NULL,NULL); + if (rc) + return unlock_pinentry (map_assuan_err (rc)); + } + if (cancel_btn) + { + snprintf (line, DIM(line)-1, "SETCANCEL %s", cancel_btn); + line[DIM(line)-1] = 0; + rc = assuan_transact (entry_ctx, line, NULL,NULL,NULL,NULL,NULL,NULL); + if (rc) + return unlock_pinentry (map_assuan_err (rc)); + } + + tattr = pth_attr_new(); + pth_attr_set (tattr, PTH_ATTR_JOINABLE, 1); + pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024); + pth_attr_set (tattr, PTH_ATTR_NAME, "popup-message"); + + popup_finished = 0; + popup_tid = pth_spawn (tattr, popup_message_thread, NULL); + if (!popup_tid) + { + rc = gpg_error_from_errno (errno); + log_error ("error spawning popup message handler: %s\n", + strerror (errno) ); + pth_attr_destroy (tattr); + return unlock_pinentry (rc); + } + pth_attr_destroy (tattr); + + return 0; +} + +/* Close a popup window. */ +void +agent_popup_message_stop (ctrl_t ctrl) +{ + int rc; + pid_t pid; + + if (!popup_tid || !entry_ctx) + { + log_debug ("agent_popup_message_stop called with no active popup\n"); + return; + } + + pid = assuan_get_pid (entry_ctx); + if (pid == (pid_t)(-1)) + ; /* No pid available can't send a kill. */ + else if (popup_finished) + ; /* Already finished and ready for joining. */ + else if (pid && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) ) + { /* The daemon already died. No need to send a kill. However + because we already waited for the process, we need to tell + assuan that it should not wait again (done by + unlock_pinentry). */ + if (rc == pid) + assuan_set_flag (entry_ctx, ASSUAN_NO_WAITPID, 1); + } + else + kill (pid, SIGINT); + + /* Now wait for the thread to terminate. */ + rc = pth_join (popup_tid, NULL); + if (!rc) + log_debug ("agent_popup_message_stop: pth_join failed: %s\n", + strerror (errno)); + popup_tid = NULL; + entry_owner = NULL; + + /* Now we can close the connection. */ + unlock_pinentry (0); +} + diff --git a/agent/t-protect.c b/agent/t-protect.c index 5187cf8f7..fee3c561d 100644 --- a/agent/t-protect.c +++ b/agent/t-protect.c @@ -173,7 +173,8 @@ test_agent_protect (void) for (i = 0; i < DIM (specs); i++) { - ret = agent_protect (specs[i].key, specs[i].passphrase, + ret = agent_protect ((const unsigned char*)specs[i].key, + specs[i].passphrase, &specs[i].result, &specs[i].resultlen); if (gpg_err_code (ret) != specs[i].ret_expected) { diff --git a/am/cmacros.am b/am/cmacros.am index 0f7a09fe0..de68b6f31 100644 --- a/am/cmacros.am +++ b/am/cmacros.am @@ -25,7 +25,8 @@ if ! HAVE_DOSISH_SYSTEM AM_CPPFLAGS += -DGNUPG_BINDIR="\"$(bindir)\"" \ -DGNUPG_LIBEXECDIR="\"$(libexecdir)\"" \ -DGNUPG_LIBDIR="\"$(libdir)/@PACKAGE@\"" \ - -DGNUPG_DATADIR="\"$(datadir)/@PACKAGE@\"" + -DGNUPG_DATADIR="\"$(datadir)/@PACKAGE@\"" \ + -DGNUPG_SYSCONFDIR="\"$(sysconfdir)/@PACKAGE@\"" endif if GNUPG_AGENT_PGM diff --git a/configure.ac b/configure.ac index 30b183046..6ba66968d 100644 --- a/configure.ac +++ b/configure.ac @@ -22,9 +22,12 @@ AC_PREREQ(2.52) min_automake_version="1.9.3" -# Version number: Remember to change it immediately *after* a release. -# Add a "-cvs" prefix for non-released code. -AC_INIT(gnupg, 1.9.19, gnupg-devel@gnupg.org) +# Remember to change the version number immediately *after* a release. +# Uncomment the my_iscvs macro for non-released code. +m4_define(my_version, [1.9.20]) +m4_define(my_iscvs, yes) +AC_INIT([gnupg], my_version[]m4_ifdef([my_iscvs], [-cvs[]m4_translit( + [$Revision$],[Ra-z $:])]), [gnupg-devel@gnupg.org]) # Set development_version to yes if the minor number is odd or you # feel that the default check for a development version is not # sufficient. @@ -36,7 +39,7 @@ NEED_LIBGCRYPT_VERSION=1.1.94 NEED_LIBASSUAN_VERSION=0.6.10 -NEED_KSBA_VERSION=0.9.12 +NEED_KSBA_VERSION=0.9.13 PACKAGE=$PACKAGE_NAME diff --git a/kbx/keybox-blob.c b/kbx/keybox-blob.c index 67c74b777..eacc0014a 100644 --- a/kbx/keybox-blob.c +++ b/kbx/keybox-blob.c @@ -61,7 +61,7 @@ X.509 specific are noted like [X.509: xxx] u32 offset to the n-th key's keyID (a keyID is always 8 byte) or 0 if not known which is the case only for X509. u16 special key flags - bit 0 = + bit 0 = qualified signature (not yet implemented} u16 reserved u16 size of serialnumber(may be zero) n u16 (see above) bytes of serial number diff --git a/po/POTFILES.in b/po/POTFILES.in index 8fff858db..eb5711ddb 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -17,6 +17,7 @@ kbx/kbxutil.c scd/scdaemon.c scd/app-openpgp.c +scd/app-nks.c sm/base64.c sm/call-agent.c diff --git a/po/de.po b/po/de.po index 6b28544cb..2201c0429 100644 --- a/po/de.po +++ b/po/de.po @@ -10,8 +10,8 @@ msgid "" msgstr "" "Project-Id-Version: gnupg2 1.9.18\n" "Report-Msgid-Bugs-To: translations@gnupg.org\n" -"POT-Creation-Date: 2005-11-23 13:00+0100\n" -"PO-Revision-Date: 2005-11-23 13:02+0100\n" +"POT-Creation-Date: 2005-11-28 12:14+0100\n" +"PO-Revision-Date: 2005-11-28 12:16+0100\n" "Last-Translator: Werner Koch \n" "Language-Team: de\n" "MIME-Version: 1.0\n" @@ -766,6 +766,10 @@ msgstr "" msgid "can't access %s - invalid OpenPGP card?\n" msgstr "Zugriff auf %s nicht möglich - ungültige OpenPGP Karte?\n" +#: scd/app-nks.c:344 +msgid "the NullPIN has not yet been changed\n" +msgstr "Die Nullpin wurde noch nicht geändert\n" + #: sm/base64.c:317 #, c-format msgid "invalid radix64 character %02x skipped\n" @@ -1369,7 +1373,7 @@ msgstr "Signieren mit `%s' nicht möglich: %s\n" #: sm/gpgsm.c:1475 msgid "this command has not yet been implemented\n" -msgstr "Diee Kommando wurde noch nicht implementiert\n" +msgstr "Dieses Kommando wurde noch nicht implementiert\n" #: sm/gpgsm.c:1705 sm/gpgsm.c:1742 sm/qualified.c:73 #, c-format @@ -1548,10 +1552,10 @@ msgid "" msgstr "" "Sie sind dabei, eine Signatur mit dem Zertifikat:\n" "\"%s\"\n" -"zu erzeugen. Dies wird einen qualifizierte Signatur erzeugen, \n" -"die gesetzlich einer handgeschriebene gleichgestellt ist.\n" +"zu erzeugen. Dies wird eine qualifizierte Signatur erzeugen, \n" +"die gesetzlich einer handgeschriebenen gleichgestellt ist.\n" "\n" -"%s%sSind Sie wirklich sicher, da Sie dies möchten?" +"%s%sSind Sie wirklich sicher, daß Sie dies möchten?" #: sm/qualified.c:224 msgid "" diff --git a/scd/ChangeLog b/scd/ChangeLog index ccea117dd..008d84080 100644 --- a/scd/ChangeLog +++ b/scd/ChangeLog @@ -1,8 +1,63 @@ +2005-11-23 Werner Koch + + * app-nks.c (verify_pin): Give a special error message for a Nullpin. + +2005-10-29 Werner Koch + + * ccid-driver.c (send_escape_cmd): New args RESULT, RESULTLEN and + RESULTMAX. Changed all callers. + (ccid_transceive_escape): New. + +2005-10-27 Werner Koch + + * apdu.c [__CYGWIN__]: Make cygwin environment similar to _WIN32. + Suggested by John P. Clizbe. + * scdaemon.c [__CYGWIN__]: Set default PC/SC driver to winscard.dll. + +2005-10-19 Werner Koch + + * ccid-driver.h (CCID_DRIVER_ERR_NO_KEYPAD): New. + * apdu.h (SW_HOST_NO_KEYPAD): New. + * iso7816.h (struct iso7816_pininfo_s): New. + * iso7816.c (map_sw): Support new code. + (iso7816_check_keypad): New. + (iso7816_verify_kp, iso7816_change_reference_data_kp) + (iso7816_reset_retry_counter_kp): New. Extended versions of the + original functions. + * apdu.c (host_sw_string): Support new code. + (reader_table_s): New field CHECK_KEYPAD. + (new_reader_slot, open_ct_reader, open_pcsc_reader) + (open_ccid_reader, open_rapdu_reader): Initialize it. + (check_ccid_keypad): New. + (apdu_check_keypad): New. + (apdu_send_le): Factored all code out to ... + (send_le): .. new. Takes an additional arg; changed all callers + of the orginal function to use this one with a NULL for the new + arg. + (apdu_send_simple_kp): New. + (ct_send_apdu, pcsc_send_apdu, my_rapdu_send_apdu) + (send_apdu_ccid): New arg PININFO. + (send_apdu_ccid): Use the new arg. + + * scdaemon.c: New option --disable-keypad. + 2005-10-08 Marcus Brinkmann * Makefile.am (scdaemon_LDADD): Add ../gl/libgnu.a after ../common/libcommon.a. +2005-09-20 Werner Koch + + * app-dinsig.c (verify_pin): Try ISO 9564 BCD encoding. + + * iso7816.c (iso7816_select_application): Add arg FLAGS. Changed + all callers to pass 0. + * app-openpgp.c (app_select_openpgp): But this one requires a + special flag. + + * app-p15.c (app_select_p15): Don't use select application for the + BELPIC. + 2005-09-09 Werner Koch * pcsc-wrapper.c (main): Removed bogus free. diff --git a/scd/apdu.c b/scd/apdu.c index 678ea12d3..f59d832d4 100644 --- a/scd/apdu.c +++ b/scd/apdu.c @@ -66,10 +66,10 @@ #include "ccid-driver.h" -/* To to conflicting use of threading libraries we usually can't link +/* Due to conflicting use of threading libraries we usually can't link against libpcsclite. Instead we use a wrapper program. */ #ifdef USE_GNU_PTH -#ifndef HAVE_W32_SYSTEM +#if !defined(HAVE_W32_SYSTEM) && !defined(__CYGWIN__) #define NEED_PCSC_WRAPPER 1 #endif #endif @@ -78,7 +78,7 @@ #define MAX_READER 4 /* Number of readers we support concurrently. */ -#ifdef _WIN32 +#if defined(_WIN32) || defined(__CYGWIN__) #define DLSTDCALL __stdcall #else #define DLSTDCALL @@ -90,6 +90,14 @@ #define MAX_OPEN_FDS 20 #endif +/* Helper to pass patrameters related to keypad based operations. */ +struct pininfo_s +{ + int mode; + int minlen; + int maxlen; + int padlen; +}; /* A structure to collect information pertaining to one reader slot. */ @@ -103,7 +111,8 @@ struct reader_table_s { int (*reset_reader)(int); int (*get_status_reader)(int, unsigned int *); int (*send_apdu_reader)(int,unsigned char *,size_t, - unsigned char *, size_t *); + unsigned char *, size_t *, struct pininfo_s *); + int (*check_keypad)(int, int, int, int, int, int); void (*dump_status_reader)(int); struct { @@ -320,6 +329,7 @@ new_reader_slot (void) reader_table[reader].reset_reader = NULL; reader_table[reader].get_status_reader = NULL; reader_table[reader].send_apdu_reader = NULL; + reader_table[reader].check_keypad = NULL; reader_table[reader].dump_status_reader = NULL; reader_table[reader].used = 1; @@ -372,6 +382,7 @@ host_sw_string (long err) case SW_HOST_GENERAL_ERROR: return "general error"; case SW_HOST_NO_READER: return "no reader"; case SW_HOST_ABORTED: return "aborted"; + case SW_HOST_NO_KEYPAD: return "no keypad"; default: return "unknown host status error"; } } @@ -533,7 +544,7 @@ ct_get_status (int slot, unsigned int *status) set to BUFLEN. Returns: CT API error code. */ static int ct_send_apdu (int slot, unsigned char *apdu, size_t apdulen, - unsigned char *buffer, size_t *buflen) + unsigned char *buffer, size_t *buflen, struct pininfo_s *pininfo) { int rc; unsigned char dad[1], sad[1]; @@ -596,6 +607,7 @@ open_ct_reader (int port) reader_table[reader].reset_reader = reset_ct_reader; reader_table[reader].get_status_reader = ct_get_status; reader_table[reader].send_apdu_reader = ct_send_apdu; + reader_table[reader].check_keypad = NULL; reader_table[reader].dump_status_reader = ct_dump_reader_status; dump_reader_status (reader); @@ -1082,7 +1094,8 @@ pcsc_get_status (int slot, unsigned int *status) set to BUFLEN. Returns: CT API error code. */ static int pcsc_send_apdu (int slot, unsigned char *apdu, size_t apdulen, - unsigned char *buffer, size_t *buflen) + unsigned char *buffer, size_t *buflen, + struct pininfo_s *pininfo) { #ifdef NEED_PCSC_WRAPPER long err; @@ -1479,6 +1492,7 @@ open_pcsc_reader (const char *portstr) reader_table[slot].reset_reader = reset_pcsc_reader; reader_table[slot].get_status_reader = pcsc_get_status; reader_table[slot].send_apdu_reader = pcsc_send_apdu; + reader_table[slot].check_keypad = NULL; reader_table[slot].dump_status_reader = dump_pcsc_reader_status; /* Read the status so that IS_T0 will be set. */ @@ -1625,6 +1639,7 @@ open_pcsc_reader (const char *portstr) reader_table[slot].reset_reader = reset_pcsc_reader; reader_table[slot].get_status_reader = pcsc_get_status; reader_table[slot].send_apdu_reader = pcsc_send_apdu; + reader_table[slot].check_keypad = NULL; reader_table[slot].dump_status_reader = dump_pcsc_reader_status; /* log_debug ("state from pcsc_status: 0x%lx\n", card_state); */ @@ -1713,7 +1728,8 @@ get_status_ccid (int slot, unsigned int *status) set to BUFLEN. Returns: Internal CCID driver error code. */ static int send_apdu_ccid (int slot, unsigned char *apdu, size_t apdulen, - unsigned char *buffer, size_t *buflen) + unsigned char *buffer, size_t *buflen, + struct pininfo_s *pininfo) { long err; size_t maxbuflen; @@ -1727,9 +1743,18 @@ send_apdu_ccid (int slot, unsigned char *apdu, size_t apdulen, log_printhex (" APDU_data:", apdu, apdulen); maxbuflen = *buflen; - err = ccid_transceive (reader_table[slot].ccid.handle, - apdu, apdulen, - buffer, maxbuflen, buflen); + if (pininfo) + err = ccid_transceive_secure (reader_table[slot].ccid.handle, + apdu, apdulen, + pininfo->mode, + pininfo->minlen, + pininfo->maxlen, + pininfo->padlen, + buffer, maxbuflen, buflen); + else + err = ccid_transceive (reader_table[slot].ccid.handle, + apdu, apdulen, + buffer, maxbuflen, buflen); if (err) log_error ("ccid_transceive failed: (0x%lx)\n", err); @@ -1737,6 +1762,24 @@ send_apdu_ccid (int slot, unsigned char *apdu, size_t apdulen, return err; } + +/* Check whether the CCID reader supports the ISO command code COMMAND + on the keypad. Return 0 on success. For a description of the pin + parameters, see ccid-driver.c */ +static int +check_ccid_keypad (int slot, int command, int pin_mode, + int pinlen_min, int pinlen_max, int pin_padlen) +{ + unsigned char apdu[] = { 0, 0, 0, 0x81 }; + + apdu[1] = command; + return ccid_transceive_secure (reader_table[slot].ccid.handle, + apdu, sizeof apdu, + pin_mode, pinlen_min, pinlen_max, pin_padlen, + NULL, 0, NULL); +} + + /* Open the reader and try to read an ATR. */ static int open_ccid_reader (const char *portstr) @@ -1776,6 +1819,7 @@ open_ccid_reader (const char *portstr) reader_table[slot].reset_reader = reset_ccid_reader; reader_table[slot].get_status_reader = get_status_ccid; reader_table[slot].send_apdu_reader = send_apdu_ccid; + reader_table[slot].check_keypad = check_ccid_keypad; reader_table[slot].dump_status_reader = dump_ccid_reader_status; dump_reader_status (slot); @@ -1932,7 +1976,8 @@ my_rapdu_get_status (int slot, unsigned int *status) set to BUFLEN. Returns: APDU error code. */ static int my_rapdu_send_apdu (int slot, unsigned char *apdu, size_t apdulen, - unsigned char *buffer, size_t *buflen) + unsigned char *buffer, size_t *buflen, + struct pininfo_s *pininfo) { int err; reader_table_t slotp; @@ -2063,6 +2108,7 @@ open_rapdu_reader (int portno, reader_table[slot].reset_reader = reset_rapdu_reader; reader_table[slot].get_status_reader = my_rapdu_get_status; reader_table[slot].send_apdu_reader = my_rapdu_send_apdu; + reader_table[slot].check_keypad = NULL; reader_table[slot].dump_status_reader = NULL; dump_reader_status (slot); @@ -2198,28 +2244,28 @@ apdu_open_reader (const char *portstr) pcsc_establish_context = dlsym (handle, "SCardEstablishContext"); pcsc_release_context = dlsym (handle, "SCardReleaseContext"); pcsc_list_readers = dlsym (handle, "SCardListReaders"); -#ifdef _WIN32 +#if defined(_WIN32) || defined(__CYGWIN__) if (!pcsc_list_readers) pcsc_list_readers = dlsym (handle, "SCardListReadersA"); #endif pcsc_get_status_change = dlsym (handle, "SCardGetStatusChange"); -#ifdef _WIN32 +#if defined(_WIN32) || defined(__CYGWIN__) if (!pcsc_get_status_change) pcsc_get_status_change = dlsym (handle, "SCardGetStatusChangeA"); #endif pcsc_connect = dlsym (handle, "SCardConnect"); -#ifdef _WIN32 +#if defined(_WIN32) || defined(__CYGWIN__) if (!pcsc_connect) pcsc_connect = dlsym (handle, "SCardConnectA"); #endif pcsc_reconnect = dlsym (handle, "SCardReconnect"); -#ifdef _WIN32 +#if defined(_WIN32) || defined(__CYGWIN__) if (!pcsc_reconnect) pcsc_reconnect = dlsym (handle, "SCardReconnectA"); #endif pcsc_disconnect = dlsym (handle, "SCardDisconnect"); pcsc_status = dlsym (handle, "SCardStatus"); -#ifdef _WIN32 +#if defined(_WIN32) || defined(__CYGWIN__) if (!pcsc_status) pcsc_status = dlsym (handle, "SCardStatusA"); #endif @@ -2492,11 +2538,30 @@ apdu_get_status (int slot, int hang, } +/* Check whether the reader supports the ISO command code COMMAND on + the keypad. Return 0 on success. For a description of the pin + parameters, see ccid-driver.c */ +int +apdu_check_keypad (int slot, int command, int pin_mode, + int pinlen_min, int pinlen_max, int pin_padlen) +{ + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if (reader_table[slot].check_keypad) + return reader_table[slot].check_keypad (slot, command, + pin_mode, pinlen_min, pinlen_max, + pin_padlen); + else + return SW_HOST_NOT_SUPPORTED; +} + + /* Dispatcher for the actual send_apdu function. Note, that this function should be called in locked state. */ static int send_apdu (int slot, unsigned char *apdu, size_t apdulen, - unsigned char *buffer, size_t *buflen) + unsigned char *buffer, size_t *buflen, struct pininfo_s *pininfo) { if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) return SW_HOST_NO_DRIVER; @@ -2504,24 +2569,20 @@ send_apdu (int slot, unsigned char *apdu, size_t apdulen, if (reader_table[slot].send_apdu_reader) return reader_table[slot].send_apdu_reader (slot, apdu, apdulen, - buffer, buflen); + buffer, buflen, pininfo); else return SW_HOST_NOT_SUPPORTED; } -/* Send an APDU to the card in SLOT. The APDU is created from all - given parameters: CLASS, INS, P0, P1, LC, DATA, LE. A value of -1 - for LC won't sent this field and the data field; in this case DATA - must also be passed as NULL. The return value is the status word - or -1 for an invalid SLOT or other non card related error. If - RETBUF is not NULL, it will receive an allocated buffer with the - returned data. The length of that data will be put into - *RETBUFLEN. The caller is reponsible for releasing the buffer even - in case of errors. */ -int -apdu_send_le(int slot, int class, int ins, int p0, int p1, - int lc, const char *data, int le, - unsigned char **retbuf, size_t *retbuflen) + +/* Core APDU trabceiver function. Parameters are described at + apdu_send_le with the exception of PININFO which indicates keypad + related operations if not NULL. */ +static int +send_le (int slot, int class, int ins, int p0, int p1, + int lc, const char *data, int le, + unsigned char **retbuf, size_t *retbuflen, + struct pininfo_s *pininfo) { #define RESULTLEN 256 unsigned char result[RESULTLEN+10]; /* 10 extra in case of bugs in @@ -2570,7 +2631,7 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, /* As safeguard don't pass any garbage from the stack to the driver. */ memset (apdu+apdulen, 0, sizeof (apdu) - apdulen); resultlen = RESULTLEN; - rc = send_apdu (slot, apdu, apdulen, result, &resultlen); + rc = send_apdu (slot, apdu, apdulen, result, &resultlen, pininfo); if (rc || resultlen < 2) { log_error ("apdu_send_simple(%d) failed: %s\n", @@ -2638,7 +2699,7 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, apdu[apdulen++] = len; memset (apdu+apdulen, 0, sizeof (apdu) - apdulen); resultlen = RESULTLEN; - rc = send_apdu (slot, apdu, apdulen, result, &resultlen); + rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL); if (rc || resultlen < 2) { log_error ("apdu_send_simple(%d) for get response failed: %s\n", @@ -2703,6 +2764,27 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, #undef RESULTLEN } +/* Send an APDU to the card in SLOT. The APDU is created from all + given parameters: CLASS, INS, P0, P1, LC, DATA, LE. A value of -1 + for LC won't sent this field and the data field; in this case DATA + must also be passed as NULL. The return value is the status word + or -1 for an invalid SLOT or other non card related error. If + RETBUF is not NULL, it will receive an allocated buffer with the + returned data. The length of that data will be put into + *RETBUFLEN. The caller is reponsible for releasing the buffer even + in case of errors. */ +int +apdu_send_le(int slot, int class, int ins, int p0, int p1, + int lc, const char *data, int le, + unsigned char **retbuf, size_t *retbuflen) +{ + return send_le (slot, class, ins, p0, p1, + lc, data, le, + retbuf, retbuflen, + NULL); +} + + /* Send an APDU to the card in SLOT. The APDU is created from all given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for LC won't sent this field and the data field; in this case DATA must @@ -2716,8 +2798,8 @@ int apdu_send (int slot, int class, int ins, int p0, int p1, int lc, const char *data, unsigned char **retbuf, size_t *retbuflen) { - return apdu_send_le (slot, class, ins, p0, p1, lc, data, 256, - retbuf, retbuflen); + return send_le (slot, class, ins, p0, p1, lc, data, 256, + retbuf, retbuflen, NULL); } /* Send an APDU to the card in SLOT. The APDU is created from all @@ -2730,7 +2812,25 @@ int apdu_send_simple (int slot, int class, int ins, int p0, int p1, int lc, const char *data) { - return apdu_send_le (slot, class, ins, p0, p1, lc, data, -1, NULL, NULL); + return send_le (slot, class, ins, p0, p1, lc, data, -1, NULL, NULL, NULL); +} + + +/* Same as apdu_send_simple but uses the keypad of the reader. */ +int +apdu_send_simple_kp (int slot, int class, int ins, int p0, int p1, + int lc, const char *data, + int pin_mode, + int pinlen_min, int pinlen_max, int pin_padlen) +{ + struct pininfo_s pininfo; + + pininfo.mode = pin_mode; + pininfo.minlen = pinlen_min; + pininfo.maxlen = pinlen_max; + pininfo.padlen = pin_padlen; + return send_le (slot, class, ins, p0, p1, lc, data, -1, + NULL, NULL, &pininfo); } @@ -2771,7 +2871,7 @@ apdu_send_direct (int slot, const unsigned char *apdudata, size_t apdudatalen, class = apdulen? *apdu : 0; resultlen = RESULTLEN; - rc = send_apdu (slot, apdu, apdulen, result, &resultlen); + rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL); if (rc || resultlen < 2) { log_error ("apdu_send_direct(%d) failed: %s\n", @@ -2825,7 +2925,7 @@ apdu_send_direct (int slot, const unsigned char *apdudata, size_t apdudatalen, apdu[apdulen++] = len; memset (apdu+apdulen, 0, sizeof (apdu) - apdulen); resultlen = RESULTLEN; - rc = send_apdu (slot, apdu, apdulen, result, &resultlen); + rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL); if (rc || resultlen < 2) { log_error ("apdu_send_direct(%d) for get response failed: %s\n", diff --git a/scd/apdu.h b/scd/apdu.h index 45388fdd1..c3af82506 100644 --- a/scd/apdu.h +++ b/scd/apdu.h @@ -63,7 +63,8 @@ enum { SW_HOST_CARD_IO_ERROR = 0x1000a, SW_HOST_GENERAL_ERROR = 0x1000b, SW_HOST_NO_READER = 0x1000c, - SW_HOST_ABORTED = 0x1000d + SW_HOST_ABORTED = 0x1000d, + SW_HOST_NO_KEYPAD = 0x1000e }; @@ -96,8 +97,14 @@ int apdu_activate (int slot); int apdu_reset (int slot); int apdu_get_status (int slot, int hang, unsigned int *status, unsigned int *changed); +int apdu_check_keypad (int slot, int command, int pin_mode, + int pinlen_min, int pinlen_max, int pin_padlen); int apdu_send_simple (int slot, int class, int ins, int p0, int p1, int lc, const char *data); +int apdu_send_simple_kp (int slot, int class, int ins, int p0, int p1, + int lc, const char *data, + int pin_mode, + int pinlen_min, int pinlen_max, int pin_padlen); int apdu_send (int slot, int class, int ins, int p0, int p1, int lc, const char *data, unsigned char **retbuf, size_t *retbuflen); diff --git a/scd/app-dinsig.c b/scd/app-dinsig.c index 28b70c466..75cd12c59 100644 --- a/scd/app-dinsig.c +++ b/scd/app-dinsig.c @@ -1,5 +1,5 @@ /* app-dinsig.c - The DINSIG (DIN V 66291-1) card application. - * Copyright (C) 2002, 2004 Free Software Foundation, Inc. + * Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -280,10 +280,11 @@ verify_pin (app_t app, { if (!app->did_chv1 || app->force_chv1 ) { + const char *s; char *pinvalue; int rc; - rc = pincb (pincb_arg, "PIN", &pinvalue); + rc = pincb (pincb_arg, "PIN", &pinvalue); if (rc) { log_info ("PIN callback returned error: %s\n", gpg_strerror (rc)); @@ -291,8 +292,16 @@ verify_pin (app_t app, } /* We require the PIN to be at least 6 and at max 8 bytes. - According to the specs, this should all be ASCII but we don't - check this. */ + According to the specs, this should all be ASCII. */ + for (s=pinvalue; digitp (s); s++) + ; + if (*s) + { + log_error ("Non-numeric digits found in PIN\n"); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + if (strlen (pinvalue) < 6) { log_error ("PIN is too short; minimum length is 6\n"); @@ -307,6 +316,28 @@ verify_pin (app_t app, } rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue)); + if (gpg_err_code (rc) == GPG_ERR_INV_VALUE) + { + /* We assume that ISO 9564-1 encoding is used and we failed + because the first nibble we passed was 3 and not 2. DIN + says something about looking up such an encoding in the + SSD but I was not able to find any tag relevant to + this. */ + char paddedpin[8]; + int i, ndigits; + + for (ndigits=0, s=pinvalue; *s; ndigits++, s++) + ; + i = 0; + paddedpin[i++] = 0x20 | (ndigits & 0x0f); + for (s=pinvalue; i < sizeof paddedpin && *s && s[1]; s = s+2 ) + paddedpin[i++] = (((*s - '0') << 4) | ((s[1] - '0') & 0x0f)); + if (i < sizeof paddedpin && *s) + paddedpin[i++] = (((*s - '0') << 4) | 0x0f); + while (i < sizeof paddedpin) + paddedpin[i++] = 0xff; + rc = iso7816_verify (app->slot, 0x81, paddedpin, sizeof paddedpin); + } if (rc) { log_error ("verify PIN failed\n"); @@ -404,7 +435,7 @@ app_select_dinsig (APP app) int slot = app->slot; int rc; - rc = iso7816_select_application (slot, aid, sizeof aid); + rc = iso7816_select_application (slot, aid, sizeof aid, 0); if (!rc) { app->apptype = "DINSIG"; diff --git a/scd/app-nks.c b/scd/app-nks.c index b6a3037ed..73ec8ea01 100644 --- a/scd/app-nks.c +++ b/scd/app-nks.c @@ -27,7 +27,7 @@ #include #include "scdaemon.h" - +#include "i18n.h" #include "iso7816.h" #include "app-common.h" #include "tlv.h" @@ -320,7 +320,7 @@ verify_pin (app_t app, return rc; } - /* The follwoing limits are due to TCOS but also defined in the + /* The following limits are due to TCOS but also defined in the NKS specs. */ if (strlen (pinvalue) < 6) { @@ -340,7 +340,10 @@ verify_pin (app_t app, rc = iso7816_verify (app->slot, 0, pinvalue, strlen (pinvalue)); if (rc) { - log_error ("verify PIN failed\n"); + if ( gpg_error (rc) == GPG_ERR_USE_CONDITIONS ) + log_error (_("the NullPIN has not yet been changed\n")); + else + log_error ("verify PIN failed\n"); xfree (pinvalue); return rc; } @@ -492,7 +495,7 @@ app_select_nks (APP app) int slot = app->slot; int rc; - rc = iso7816_select_application (slot, aid, sizeof aid); + rc = iso7816_select_application (slot, aid, sizeof aid, 0); if (!rc) { app->apptype = "NKS"; diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index 5625c729b..3d04be0be 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -1284,6 +1284,11 @@ verify_chv2 (app_t app, if (!app->did_chv2) { char *pinvalue; + iso7816_pininfo_t pininfo; + + memset (&pininfo, 0, sizeof pininfo); + pininfo.mode = 1; + pininfo.minlen = 6; rc = pincb (pincb_arg, "PIN", &pinvalue); if (rc) @@ -2455,7 +2460,9 @@ app_select_openpgp (app_t app) size_t buflen; void *relptr; - rc = iso7816_select_application (slot, aid, sizeof aid); + /* Note that the card can't cope with P2=0xCO, thus we need to pass a + special flag value. */ + rc = iso7816_select_application (slot, aid, sizeof aid, 0x0001); if (!rc) { unsigned int manufacturer; diff --git a/scd/app-p15.c b/scd/app-p15.c index 739a9ef95..8bb94cfcd 100644 --- a/scd/app-p15.c +++ b/scd/app-p15.c @@ -3268,18 +3268,15 @@ app_select_p15 (app_t app) int direct = 0; int is_belpic = 0; - rc = iso7816_select_application (slot, pkcs15_aid, sizeof pkcs15_aid); - if (rc) - { - rc = iso7816_select_application (slot, pkcs15be_aid,sizeof pkcs15be_aid); - if (!rc) - is_belpic = 1; - } + rc = iso7816_select_application (slot, pkcs15_aid, sizeof pkcs15_aid, 0); if (rc) { /* Not found: Try to locate it from 2F00. We use direct path selection here because it seems that the Belgian eID card does only allow for that. Many other cards supports this - selection method too. */ + selection method too. Note, that we don't use + select_application above for the Belgian card - the call + works but it seems that it did not switch to the correct DF. + Using the 2f02 just works. */ unsigned short path[1] = { 0x2f00 }; rc = iso7816_select_path (app->slot, path, 1, NULL, NULL); diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c index f82d93b00..fee733358 100644 --- a/scd/ccid-driver.c +++ b/scd/ccid-driver.c @@ -1240,7 +1240,9 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, if (CCID_COMMAND_FAILED (buffer)) print_command_failed (buffer); - /* Check whether a card is at all available. */ + /* Check whether a card is at all available. Note: If you add new + error codes here, check whether they need to be ignored in + send_escape_cmd. */ switch ((buffer[7] & 0x03)) { case 0: /* no error */ break; @@ -1253,16 +1255,23 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, /* Note that this function won't return the error codes NO_CARD or - CARD_INACTIVE */ + CARD_INACTIVE. IF RESULT is not NULL, the result from the + operation will get returned in RESULT and its length in RESULTLEN. + If the response is larger than RESULTMAX, an error is returned and + the required buffer length returned in RESULTLEN. */ static int send_escape_cmd (ccid_driver_t handle, - const unsigned char *data, size_t datalen) + const unsigned char *data, size_t datalen, + unsigned char *result, size_t resultmax, size_t *resultlen) { int i, rc; unsigned char msg[100]; size_t msglen; unsigned char seqno; + if (resultlen) + *resultlen = 0; + if (datalen > sizeof msg - 10) return CCID_DRIVER_ERR_INV_VALUE; /* Escape data too large. */ @@ -1285,11 +1294,42 @@ send_escape_cmd (ccid_driver_t handle, return rc; rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Escape, seqno, 5000, 0); - + if (result) + switch (rc) + { + /* We need to ignore certain errorcode here. */ + case 0: + case CCID_DRIVER_ERR_CARD_INACTIVE: + case CCID_DRIVER_ERR_NO_CARD: + { + if (msglen > resultmax) + rc = CCID_DRIVER_ERR_INV_VALUE; /* Response too large. */ + else + { + memcpy (result, msg, msglen); + *resultlen = msglen; + } + rc = 0; + } + break; + default: + break; + } + return rc; } +int +ccid_transceive_escape (ccid_driver_t handle, + const unsigned char *data, size_t datalen, + unsigned char *resp, size_t maxresplen, size_t *nresp) +{ + return send_escape_cmd (handle, data, datalen, resp, maxresplen, nresp); +} + + + /* experimental */ int ccid_poll (ccid_driver_t handle) @@ -1445,7 +1485,8 @@ ccid_get_atr (ccid_driver_t handle, { tried_iso = 1; /* Try switching to ISO mode. */ - if (!send_escape_cmd (handle, (const unsigned char*)"\xF1\x01", 2)) + if (!send_escape_cmd (handle, (const unsigned char*)"\xF1\x01", 2, + NULL, 0, NULL)) goto again; } else if (CCID_COMMAND_FAILED (msg)) @@ -1957,14 +1998,16 @@ ccid_transceive (ccid_driver_t handle, } -/* Send the CCID Secure command to the reader. APDU_BUF should contain the APDU template. PIN_MODE defines now the pin gets formatted: +/* Send the CCID Secure command to the reader. APDU_BUF should + contain the APDU template. PIN_MODE defines how the pin gets + formatted: 1 := The PIN is ASCII encoded and of variable length. The length of the PIN entered will be put into Lc by the reader. The APDU should me made up of 4 bytes without Lc. PINLEN_MIN and PINLEN_MAX define the limits for the pin length. 0 - may be used t enable usbale defaults. PIN_PADLEN should be 0 + may be used t enable reasonable defaults. PIN_PADLEN should be 0. When called with RESP and NRESP set to NULL, the function will merely check whether the reader supports the secure command for the @@ -1996,7 +2039,7 @@ ccid_transceive_secure (ccid_driver_t handle, else if (apdu_buflen >= 4 && apdu_buf[1] == 0x24 && (handle->has_pinpad & 2)) return CCID_DRIVER_ERR_NOT_SUPPORTED; /* Not yet by our code. */ else - return CCID_DRIVER_ERR_NOT_SUPPORTED; + return CCID_DRIVER_ERR_NO_KEYPAD; if (pin_mode != 1) return CCID_DRIVER_ERR_NOT_SUPPORTED; @@ -2027,7 +2070,8 @@ ccid_transceive_secure (ccid_driver_t handle, if (handle->id_vendor == VENDOR_SCM) { DEBUGOUT ("sending escape sequence to switch to a case 1 APDU\n"); - rc = send_escape_cmd (handle, (const unsigned char*)"\x80\x02\x00", 3); + rc = send_escape_cmd (handle, (const unsigned char*)"\x80\x02\x00", 3, + NULL, 0, NULL); if (rc) return rc; } @@ -2044,7 +2088,7 @@ ccid_transceive_secure (ccid_driver_t handle, if (handle->id_vendor == VENDOR_SCM) { /* For the SPR532 the next 2 bytes need to be zero. We do this - for all SCM product. Kudos to to Martin Paljak for this + for all SCM product. Kudos to Martin Paljak for this hint. */ msg[13] = msg[14] = 0; } diff --git a/scd/ccid-driver.h b/scd/ccid-driver.h index 1b9ac2f85..6f6527108 100644 --- a/scd/ccid-driver.h +++ b/scd/ccid-driver.h @@ -58,7 +58,7 @@ #ifndef CCID_DRIVER_H #define CCID_DRIVER_H -/* The CID driver returns the same error codes as the statsu words +/* The CID driver returns the same error codes as the status words used by GnuPG's apdu.h. For ease of maintenance they should always match. */ #define CCID_DRIVER_ERR_OUT_OF_CORE 0x10001 @@ -74,6 +74,7 @@ #define CCID_DRIVER_ERR_GENERAL_ERROR 0x1000b #define CCID_DRIVER_ERR_NO_READER 0x1000c #define CCID_DRIVER_ERR_ABORTED 0x1000d +#define CCID_DRIVER_ERR_NO_KEYPAD 0x1000e struct ccid_driver_s; typedef struct ccid_driver_s *ccid_driver_t; @@ -94,6 +95,10 @@ int ccid_transceive_secure (ccid_driver_t handle, int pin_mode, int pinlen_min, int pinlen_max, int pin_padlen, unsigned char *resp, size_t maxresplen, size_t *nresp); +int ccid_transceive_escape (ccid_driver_t handle, + const unsigned char *data, size_t datalen, + unsigned char *resp, size_t maxresplen, + size_t *nresp); diff --git a/scd/iso7816.c b/scd/iso7816.c index 5b985324f..5c62e1371 100644 --- a/scd/iso7816.c +++ b/scd/iso7816.c @@ -47,9 +47,9 @@ #define CMD_SELECT_FILE 0xA4 -#define CMD_VERIFY 0x20 -#define CMD_CHANGE_REFERENCE_DATA 0x24 -#define CMD_RESET_RETRY_COUNTER 0x2C +#define CMD_VERIFY ISO7816_VERIFY +#define CMD_CHANGE_REFERENCE_DATA ISO7816_CHANGE_REFERENCE_DATA +#define CMD_RESET_RETRY_COUNTER ISO7816_RESET_RETRY_COUNTER #define CMD_GET_DATA 0xCA #define CMD_PUT_DATA 0xDA #define CMD_MSE 0x22 @@ -95,6 +95,7 @@ map_sw (int sw) case SW_HOST_GENERAL_ERROR: ec = GPG_ERR_GENERAL; break; case SW_HOST_NO_READER: ec = GPG_ERR_ENODEV; break; case SW_HOST_ABORTED: ec = GPG_ERR_CANCELED; break; + case SW_HOST_NO_KEYPAD: ec = GPG_ERR_NOT_SUPPORTED; break; default: if ((sw & 0x010000)) @@ -124,12 +125,15 @@ iso7816_map_sw (int sw) requested application ID. The function can't be used to enumerate AIDs and won't return the AID on success. The return value is 0 for okay or a GPG error code. Note that ISO error codes are - internally mapped. */ + internally mapped. Bit 0 of FLAGS should be set if the card does + not understand P2=0xC0. */ gpg_error_t -iso7816_select_application (int slot, const char *aid, size_t aidlen) +iso7816_select_application (int slot, const char *aid, size_t aidlen, + unsigned int flags) { int sw; - sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, 4, 0, aidlen, aid); + sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, 4, + (flags&1)? 0 :0x0c, aidlen, aid); return map_sw (sw); } @@ -221,27 +225,59 @@ iso7816_list_directory (int slot, int list_dirs, } +/* Check whether the reader supports the ISO command code COMMAND on + the keypad. Returns 0 on success. */ +gpg_error_t +iso7816_check_keypad (int slot, int command, iso7816_pininfo_t *pininfo) +{ + int sw; + + sw = apdu_check_keypad (slot, command, + pininfo->mode, pininfo->minlen, pininfo->maxlen, + pininfo->padlen); + return map_sw (sw); +} + /* Perform a VERIFY command on SLOT using the card holder verification - vector CHVNO with a CHV of lenght CHVLEN. Returns 0 on success. */ + vector CHVNO with a CHV of lenght CHVLEN. With PININFO non-NULL + the keypad of the reader will be used. Returns 0 on success. */ gpg_error_t -iso7816_verify (int slot, int chvno, const char *chv, size_t chvlen) +iso7816_verify_kp (int slot, int chvno, const char *chv, size_t chvlen, + iso7816_pininfo_t *pininfo) { int sw; - sw = apdu_send_simple (slot, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv); + if (pininfo && pininfo->mode) + sw = apdu_send_simple_kp (slot, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv, + pininfo->mode, + pininfo->minlen, + pininfo->maxlen, + pininfo->padlen); + else + sw = apdu_send_simple (slot, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv); return map_sw (sw); } +/* Perform a VERIFY command on SLOT using the card holder verification + vector CHVNO with a CHV of lenght CHVLEN. Returns 0 on success. */ +gpg_error_t +iso7816_verify (int slot, int chvno, const char *chv, size_t chvlen) +{ + return iso7816_verify_kp (slot, chvno, chv, chvlen, NULL); +} + /* Perform a CHANGE_REFERENCE_DATA command on SLOT for the card holder verification vector CHVNO. If the OLDCHV is NULL (and OLDCHVLEN 0), a "change reference data" is done, otherwise an "exchange reference data". The new reference data is expected in NEWCHV of - length NEWCHVLEN. */ + length NEWCHVLEN. With PININFO non-NULL the keypad of the reader + will be used. */ gpg_error_t -iso7816_change_reference_data (int slot, int chvno, - const char *oldchv, size_t oldchvlen, - const char *newchv, size_t newchvlen) +iso7816_change_reference_data_kp (int slot, int chvno, + const char *oldchv, size_t oldchvlen, + const char *newchv, size_t newchvlen, + iso7816_pininfo_t *pininfo) { int sw; char *buf; @@ -258,28 +294,69 @@ iso7816_change_reference_data (int slot, int chvno, memcpy (buf, oldchv, oldchvlen); memcpy (buf+oldchvlen, newchv, newchvlen); - sw = apdu_send_simple (slot, 0x00, CMD_CHANGE_REFERENCE_DATA, - oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf); + if (pininfo && pininfo->mode) + sw = apdu_send_simple_kp (slot, 0x00, CMD_CHANGE_REFERENCE_DATA, + oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf, + pininfo->mode, + pininfo->minlen, + pininfo->maxlen, + pininfo->padlen); + else + sw = apdu_send_simple (slot, 0x00, CMD_CHANGE_REFERENCE_DATA, + oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf); xfree (buf); return map_sw (sw); } +/* Perform a CHANGE_REFERENCE_DATA command on SLOT for the card holder + verification vector CHVNO. If the OLDCHV is NULL (and OLDCHVLEN + 0), a "change reference data" is done, otherwise an "exchange + reference data". The new reference data is expected in NEWCHV of + length NEWCHVLEN. */ +gpg_error_t +iso7816_change_reference_data (int slot, int chvno, + const char *oldchv, size_t oldchvlen, + const char *newchv, size_t newchvlen) +{ + return iso7816_change_reference_data_kp (slot, chvno, oldchv, oldchvlen, + newchv, newchvlen, NULL); +} + + gpg_error_t -iso7816_reset_retry_counter (int slot, int chvno, - const char *newchv, size_t newchvlen) +iso7816_reset_retry_counter_kp (int slot, int chvno, + const char *newchv, size_t newchvlen, + iso7816_pininfo_t *pininfo) { int sw; if (!newchv || !newchvlen ) return gpg_error (GPG_ERR_INV_VALUE); - sw = apdu_send_simple (slot, 0x00, CMD_RESET_RETRY_COUNTER, - 2, chvno, newchvlen, newchv); + if (pininfo && pininfo->mode) + sw = apdu_send_simple_kp (slot, 0x00, CMD_RESET_RETRY_COUNTER, + 2, chvno, newchvlen, newchv, + pininfo->mode, + pininfo->minlen, + pininfo->maxlen, + pininfo->padlen); + else + sw = apdu_send_simple (slot, 0x00, CMD_RESET_RETRY_COUNTER, + 2, chvno, newchvlen, newchv); return map_sw (sw); } +gpg_error_t +iso7816_reset_retry_counter (int slot, int chvno, + const char *newchv, size_t newchvlen) +{ + return iso7816_reset_retry_counter_kp (slot, chvno, newchv, newchvlen, NULL); +} + + + /* Perform a GET DATA command requesting TAG and storing the result in a newly allocated buffer at the address passed by RESULT. Return the length of this data at the address of RESULTLEN. */ diff --git a/scd/iso7816.h b/scd/iso7816.h index 04c7ae63e..8f7907405 100644 --- a/scd/iso7816.h +++ b/scd/iso7816.h @@ -28,10 +28,30 @@ #include "cardglue.h" #endif +/* Command codes used by iso7816_check_keypad. */ +#define ISO7816_VERIFY 0x20 +#define ISO7816_CHANGE_REFERENCE_DATA 0x24 +#define ISO7816_RESET_RETRY_COUNTER 0x2C + + +/* Information to be passed to keypad equipped readers. See + ccid-driver.c for details. */ +struct iso7816_pininfo_s +{ + int mode; /* A mode of 0 means: Do not use the keypad. */ + int minlen; + int maxlen; + int padlen; + int padchar; +}; +typedef struct iso7816_pininfo_s iso7816_pininfo_t; + + gpg_error_t iso7816_map_sw (int sw); gpg_error_t iso7816_select_application (int slot, - const char *aid, size_t aidlen); + const char *aid, size_t aidlen, + unsigned int flags); gpg_error_t iso7816_select_file (int slot, int tag, int is_dir, unsigned char **result, size_t *resultlen); gpg_error_t iso7816_select_path (int slot, @@ -39,13 +59,26 @@ gpg_error_t iso7816_select_path (int slot, unsigned char **result, size_t *resultlen); gpg_error_t iso7816_list_directory (int slot, int list_dirs, unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_check_keypad (int slot, int command, + iso7816_pininfo_t *pininfo); gpg_error_t iso7816_verify (int slot, int chvno, const char *chv, size_t chvlen); +gpg_error_t iso7816_verify_kp (int slot, + int chvno, const char *chv, size_t chvlen, + iso7816_pininfo_t *pininfo); gpg_error_t iso7816_change_reference_data (int slot, int chvno, const char *oldchv, size_t oldchvlen, const char *newchv, size_t newchvlen); +gpg_error_t iso7816_change_reference_data_kp (int slot, int chvno, + const char *oldchv, size_t oldchvlen, + const char *newchv, size_t newchvlen, + iso7816_pininfo_t *pininfo); gpg_error_t iso7816_reset_retry_counter (int slot, int chvno, const char *newchv, size_t newchvlen); +gpg_error_t iso7816_reset_retry_counter_kp (int slot, int chvno, + const char *newchv, + size_t newchvlen, + iso7816_pininfo_t *pininfo); gpg_error_t iso7816_get_data (int slot, int tag, unsigned char **result, size_t *resultlen); gpg_error_t iso7816_put_data (int slot, int tag, diff --git a/scd/scdaemon.c b/scd/scdaemon.c index c6995abcc..56c0d7600 100644 --- a/scd/scdaemon.c +++ b/scd/scdaemon.c @@ -84,6 +84,7 @@ enum cmd_and_opt_values opcscDriver, oDisableCCID, oDisableOpenSC, + oDisableKeypad, oAllowAdmin, oDenyAdmin, oDisableApplication, @@ -126,6 +127,7 @@ static ARGPARSE_OPTS opts[] = { "@" #endif /* end --disable-ccid */}, + { oDisableKeypad, "disable-keypad", 0, N_("do not use a reader's keypad")}, { oAllowAdmin, "allow-admin", 0, N_("allow the use of admin card commands")}, { oDenyAdmin, "deny-admin", 0, "@" }, { oDisableApplication, "disable-application", 2, "@"}, @@ -135,7 +137,7 @@ static ARGPARSE_OPTS opts[] = { /* The card dirver we use by default for PC/SC. */ -#ifdef HAVE_W32_SYSTEM +#if defined(HAVE_W32_SYSTEM) || defined(__CYGWIN__) #define DEFAULT_PCSC_DRIVER "winscard.dll" #else #define DEFAULT_PCSC_DRIVER "libpcsclite.so" @@ -489,6 +491,8 @@ main (int argc, char **argv ) case oDisableCCID: opt.disable_ccid = 1; break; case oDisableOpenSC: break; + case oDisableKeypad: opt.disable_keypad = 1; break; + case oAllowAdmin: opt.allow_admin = 1; break; case oDenyAdmin: opt.allow_admin = 0; break; diff --git a/scd/scdaemon.h b/scd/scdaemon.h index 54566b6ad..abe9730a7 100644 --- a/scd/scdaemon.h +++ b/scd/scdaemon.h @@ -39,21 +39,22 @@ #define MAX_DIGEST_LEN 24 -/* A large struct name "opt" to keep global flags */ +/* A large struct name "opt" to keep global flags. */ struct { - unsigned int debug; /* debug flags (DBG_foo_VALUE) */ - int verbose; /* verbosity level */ - int quiet; /* be as quiet as possible */ - int dry_run; /* don't change any persistent data */ - int batch; /* batch mode */ - const char *homedir; /* configuration directory name */ + unsigned int debug; /* Debug flags (DBG_foo_VALUE). */ + int verbose; /* Verbosity level. */ + int quiet; /* Be as quiet as possible. */ + int dry_run; /* Don't change any persistent data. */ + int batch; /* Batch mode. */ + const char *homedir; /* Configuration directory name. */ const char *ctapi_driver; /* Library to access the ctAPI. */ const char *pcsc_driver; /* Library to access the PC/SC system. */ const char *reader_port; /* NULL or reder port to use. */ int disable_ccid; /* Disable the use of the internal CCID driver. */ + int disable_keypad; /* Do not use a keypad. */ int allow_admin; /* Allow the use of admin commands for certain cards. */ - strlist_t disabled_applications; /* card applications we do not + strlist_t disabled_applications; /* Card applications we do not want to use. */ } opt; diff --git a/tools/ChangeLog b/tools/ChangeLog index ba4f0ec27..a57a0bccf 100644 --- a/tools/ChangeLog +++ b/tools/ChangeLog @@ -1,3 +1,11 @@ +2005-10-19 Werner Koch + + * gpgconf-comp.c (gc_options_scdaemon): New option --disable-keypad. + +2005-09-22 Werner Koch + + * rfc822parse.c (parse_field): Tread Content-Disposition special. + 2005-10-08 Marcus Brinkmann * Makefile.am (watchgnupg_LDADD): New variable. diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index e8d9ca27e..497707532 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -528,7 +528,9 @@ static gc_option_t gc_options_scdaemon[] = { "disable-ccid", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", "do not use the internal CCID driver", GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, - + { "disable-keypad", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "gnupg", "do not use a reader's keypad", + GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, { "Debug", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, @@ -2447,7 +2449,8 @@ gc_component_change_options (int component, FILE *in) { #ifdef HAVE_W32_SYSTEM /* FIXME: Won't work becuase W32 doesn't silently - overwrite. */ + overwrite. Fix it by creating a backup copy and + deliting the orginal file first. */ err = rename (src_pathname[i], dest_pathname[i]); #else /*!HAVE_W32_SYSTEM*/ /* This is a bit safer than rename() because we diff --git a/tools/rfc822parse.c b/tools/rfc822parse.c index 61377e7e6..df3b2e7a4 100644 --- a/tools/rfc822parse.c +++ b/tools/rfc822parse.c @@ -766,6 +766,7 @@ parse_field (HDR_LINE hdr) } tspecial_header[] = { { "Content-Type", 12}, { "Content-Transfer-Encoding", 25}, + { "Content-Disposition", 19}, { NULL, 0} }; const char *delimiters; -- cgit From f98537733ac96fd7e786286944fd3c2696229c4f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 20 Jun 2006 17:21:37 +0000 Subject: Updated FSF's address. --- Makefile.am | 3 ++- NEWS | 3 +++ TODO | 5 ++--- agent/Makefile.am | 3 ++- agent/agent.h | 3 ++- agent/cache.c | 3 ++- agent/call-scd.c | 3 ++- agent/command-ssh.c | 4 ++-- agent/command.c | 3 ++- agent/divert-scd.c | 3 ++- agent/findkey.c | 3 ++- agent/genkey.c | 3 ++- agent/gpg-agent.c | 3 ++- agent/learncard.c | 3 ++- agent/minip12.c | 3 ++- agent/minip12.h | 3 ++- agent/pkdecrypt.c | 3 ++- agent/pksign.c | 3 ++- agent/preset-passphrase.c | 3 ++- agent/protect-tool.c | 3 ++- agent/protect.c | 3 ++- agent/query.c | 3 ++- agent/t-protect.c | 3 ++- agent/trans.c | 3 ++- agent/trustlist.c | 3 ++- am/cmacros.am | 3 ++- common/Makefile.am | 3 ++- common/asshelp.c | 3 ++- common/asshelp.h | 3 ++- common/b64enc.c | 3 ++- common/dynload.h | 3 ++- common/errors.h | 3 ++- common/estream.c | 37 +++++++++++++++++++------------------ common/estream.h | 37 +++++++++++++++++++------------------ common/exechelp.c | 3 ++- common/exechelp.h | 3 ++- common/gettime.c | 3 ++- common/homedir.c | 3 ++- common/i18n.h | 3 ++- common/iobuf.c | 3 ++- common/iobuf.h | 3 ++- common/isascii.c | 3 ++- common/maperror.c | 3 ++- common/membuf.c | 3 ++- common/membuf.h | 3 ++- common/miscellaneous.c | 3 ++- common/mkerrors | 3 ++- common/mkerrtok | 3 ++- common/sexp-parse.h | 3 ++- common/sexputil.c | 3 ++- common/signal.c | 3 ++- common/simple-gettext.c | 3 ++- common/simple-pwquery.c | 3 ++- common/simple-pwquery.h | 3 ++- common/sysutils.c | 3 ++- common/sysutils.h | 3 ++- common/ttyio.c | 3 ++- common/ttyio.h | 3 ++- common/util.h | 3 ++- common/vasprintf.c | 4 ++-- common/w32reg.c | 3 ++- common/xasprintf.c | 3 ++- common/xreadline.c | 3 ++- common/yesno.c | 3 ++- configure.ac | 3 ++- doc/Makefile.am | 3 ++- doc/gnupg-card-architecture.fig | 3 ++- g10/Makefile.am | 3 ++- g10/call-agent.c | 3 ++- g10/call-agent.h | 3 ++- g10/comment.c | 3 ++- g10/gpg.c | 9 +++++++++ g10/gpg.h | 3 ++- g10/pkglue.c | 3 ++- g10/pkglue.h | 3 ++- include/_regex.h | 4 ++-- include/errors.h | 3 ++- include/memory.h | 3 ++- include/mpi.h | 3 ++- include/util.h | 3 ++- jnlib/Makefile.am | 3 ++- jnlib/argparse.c | 30 ++++++++++++++++-------------- jnlib/argparse.h | 3 ++- jnlib/dotlock.c | 3 ++- jnlib/dotlock.h | 3 ++- jnlib/libjnlib-config.h | 3 ++- jnlib/logging.c | 3 ++- jnlib/logging.h | 3 ++- jnlib/mischelp.h | 3 ++- jnlib/stringhelp.c | 3 ++- jnlib/stringhelp.h | 3 ++- jnlib/strlist.c | 3 ++- jnlib/strlist.h | 3 ++- jnlib/types.h | 3 ++- jnlib/utf8conv.c | 3 ++- jnlib/utf8conv.h | 3 ++- jnlib/w32-afunix.c | 3 ++- jnlib/w32-afunix.h | 3 ++- jnlib/w32-pth.c | 3 ++- jnlib/w32-pth.h | 3 ++- jnlib/xmalloc.c | 3 ++- jnlib/xmalloc.h | 3 ++- kbx/Makefile.am | 3 ++- kbx/kbxutil.c | 3 ++- kbx/keybox-blob.c | 3 ++- kbx/keybox-defs.h | 3 ++- kbx/keybox-dump.c | 3 ++- kbx/keybox-file.c | 3 ++- kbx/keybox-init.c | 3 ++- kbx/keybox-openpgp.c | 3 ++- kbx/keybox-search-desc.h | 3 ++- kbx/keybox-search.c | 3 ++- kbx/keybox-update.c | 3 ++- kbx/keybox-util.c | 3 ++- kbx/keybox.h | 3 ++- kbx/mkerrors | 3 ++- scd/Makefile.am | 3 ++- scd/app-common.h | 3 ++- scd/app-dinsig.c | 3 ++- scd/app-help.c | 3 ++- scd/app-nks.c | 3 ++- scd/app-openpgp.c | 3 ++- scd/app-p15.c | 3 ++- scd/app.c | 3 ++- scd/atr.c | 3 ++- scd/atr.h | 3 ++- scd/card-common.h | 3 ++- scd/card-dinsig.c | 3 ++- scd/card-p15.c | 3 ++- scd/card.c | 3 ++- scd/command.c | 3 ++- scd/sc-copykeys.c | 3 ++- scd/scdaemon.c | 3 ++- scd/scdaemon.h | 3 ++- scd/tlv.c | 3 ++- scd/tlv.h | 3 ++- scripts/compile | 3 ++- scripts/config.guess | 3 ++- sm/ChangeLog | 7 +++++++ sm/Makefile.am | 3 ++- sm/base64.c | 3 ++- sm/call-agent.c | 3 ++- sm/call-dirmngr.c | 3 ++- sm/certchain.c | 3 ++- sm/certcheck.c | 3 ++- sm/certdump.c | 3 ++- sm/certlist.c | 3 ++- sm/certreqgen.c | 3 ++- sm/decrypt.c | 3 ++- sm/delete.c | 3 ++- sm/encrypt.c | 3 ++- sm/export.c | 3 ++- sm/fingerprint.c | 3 ++- sm/gpgsm.c | 16 ++++++++++++---- sm/gpgsm.h | 3 ++- sm/import.c | 3 ++- sm/keydb.c | 3 ++- sm/keydb.h | 3 ++- sm/keylist.c | 7 ++++++- sm/misc.c | 3 ++- sm/qualified.c | 3 ++- sm/server.c | 3 ++- sm/sign.c | 3 ++- sm/verify.c | 3 ++- tests/Makefile.am | 3 ++- tests/asschk.c | 3 ++- tests/pkits/Makefile.am | 3 ++- tests/pkits/common.sh | 3 ++- tests/pkits/import-all-certs | 3 ++- tests/pkits/validate-all-certs | 3 ++- tools/Makefile.am | 3 ++- tools/gpg-connect-agent.c | 3 ++- tools/gpgconf-comp.c | 36 +++++++++++++++++++----------------- tools/gpgconf.c | 3 ++- tools/gpgconf.h | 3 ++- tools/gpgkey2ssh.c | 37 +++++++++++++++++++------------------ tools/gpgparsemail.c | 3 ++- tools/no-libgcrypt.c | 3 ++- tools/symcryptrun.c | 3 ++- tools/watchgnupg.c | 3 ++- 180 files changed, 469 insertions(+), 265 deletions(-) (limited to 'agent/command.c') diff --git a/Makefile.am b/Makefile.am index 9fafb1102..0c5fbe4c3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -15,7 +15,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in diff --git a/NEWS b/NEWS index 6413242c6..679bf7d5b 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,9 @@ Noteworthy changes in version 1.9.21 * [scdaemon] Added --hash=xxx option to the PKSIGN command. + * [gpg-protect-tool] Does now create a MAC for P12 files. This is for + better interoperability. + Noteworthy changes in version 1.9.20 (2005-12-20) ------------------------------------------------- diff --git a/TODO b/TODO index 7958ed18e..da3a76e06 100644 --- a/TODO +++ b/TODO @@ -21,7 +21,7 @@ might want to have an agent context for each service request ** When a certificate chain was sucessfully verified, make ephemeral certs used in this chain permanent. ** Try to keep certificate references somewhere This will help with some of our caching code. We also need to test - that cachining; in particular "regtp_ca_chainlen". + that caching; in particular "regtp_ca_chainlen". * sm/decrypt.c ** replace leading zero in integer hack by a cleaner solution @@ -101,7 +101,6 @@ might want to have an agent context for each service request * sm/ -** --include-certs is as of now still a dummy command line option ** check that we issue NO_SECKEY xxx if a -u key was not found * gpg/ @@ -117,4 +116,4 @@ might want to have an agent context for each service request ** ttyio Add completion support. ** yesno - Update to gpg 1.4.3 version \ No newline at end of file + Update to gpg 1.4.3 version diff --git a/agent/Makefile.am b/agent/Makefile.am index bc96531e0..961f0bb97 100644 --- a/agent/Makefile.am +++ b/agent/Makefile.am @@ -14,7 +14,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in diff --git a/agent/agent.h b/agent/agent.h index 1542d6b9f..fdfe510fb 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef AGENT_H diff --git a/agent/cache.c b/agent/cache.c index 32b6ac0c7..2f468396d 100644 --- a/agent/cache.c +++ b/agent/cache.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/agent/call-scd.c b/agent/call-scd.c index ff241ce41..d0d24f9d5 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/agent/command-ssh.c b/agent/command-ssh.c index 23f083c2f..18375a9ae 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -15,8 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* Only v2 of the ssh-agent protocol is implemented. */ diff --git a/agent/command.c b/agent/command.c index daf9b8698..12770dac8 100644 --- a/agent/command.c +++ b/agent/command.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* FIXME: we should not use the default assuan buffering but setup diff --git a/agent/divert-scd.c b/agent/divert-scd.c index 926df2622..3dc7984e6 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/agent/findkey.c b/agent/findkey.c index 73ffb530d..3f793e5dd 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/agent/genkey.c b/agent/genkey.c index d0319f7b4..04ee865f4 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 22bd5589d..fc2a2001a 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -16,7 +16,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/agent/learncard.c b/agent/learncard.c index 72238507f..8ddf4ee54 100644 --- a/agent/learncard.c +++ b/agent/learncard.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/agent/minip12.c b/agent/minip12.c index 6f99bf24d..912d387d8 100644 --- a/agent/minip12.c +++ b/agent/minip12.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifdef HAVE_CONFIG_H diff --git a/agent/minip12.h b/agent/minip12.h index 2fbb490d7..6275f9ccb 100644 --- a/agent/minip12.h +++ b/agent/minip12.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef MINIP12_H diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c index 1d64c1b15..f61f0f844 100644 --- a/agent/pkdecrypt.c +++ b/agent/pkdecrypt.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/agent/pksign.c b/agent/pksign.c index e9df19351..9863f9de0 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/agent/preset-passphrase.c b/agent/preset-passphrase.c index 6a9f07a3e..013c9411d 100644 --- a/agent/preset-passphrase.c +++ b/agent/preset-passphrase.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/agent/protect-tool.c b/agent/protect-tool.c index 5f59d5e06..bb14ca1e1 100644 --- a/agent/protect-tool.c +++ b/agent/protect-tool.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/agent/protect.c b/agent/protect.c index 45bdae496..19f6ccbc6 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -16,7 +16,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/agent/query.c b/agent/query.c index a5a3d0153..0516bec03 100644 --- a/agent/query.c +++ b/agent/query.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/agent/t-protect.c b/agent/t-protect.c index fee3c561d..9ddd49414 100644 --- a/agent/t-protect.c +++ b/agent/t-protect.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/agent/trans.c b/agent/trans.c index 7fa5e3d6b..5eb7d25c0 100644 --- a/agent/trans.c +++ b/agent/trans.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* To avoid any problems with the gettext implementation (there used diff --git a/agent/trustlist.c b/agent/trustlist.c index edb00650d..d234af692 100644 --- a/agent/trustlist.c +++ b/agent/trustlist.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/am/cmacros.am b/am/cmacros.am index de68b6f31..7b449e2c0 100644 --- a/am/cmacros.am +++ b/am/cmacros.am @@ -15,7 +15,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. localedir = $(datadir)/locale diff --git a/common/Makefile.am b/common/Makefile.am index 34819e93f..085440bb3 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -15,7 +15,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in diff --git a/common/asshelp.c b/common/asshelp.c index 0edaeae0e..1da899522 100644 --- a/common/asshelp.c +++ b/common/asshelp.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/common/asshelp.h b/common/asshelp.h index 2d6dc79e6..9f4b5806b 100644 --- a/common/asshelp.h +++ b/common/asshelp.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_COMMON_ASSHELP_H diff --git a/common/b64enc.c b/common/b64enc.c index 5b7a42ab3..bfc49deb6 100644 --- a/common/b64enc.c +++ b/common/b64enc.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/common/dynload.h b/common/dynload.h index 2c074141f..9b67fa9ed 100644 --- a/common/dynload.h +++ b/common/dynload.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_DYNLOAD_H diff --git a/common/errors.h b/common/errors.h index f34f3ba79..131891f65 100644 --- a/common/errors.h +++ b/common/errors.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_COMMON_ERRORS_H diff --git a/common/estream.c b/common/estream.c index 70b3d9c6e..c2030371b 100644 --- a/common/estream.c +++ b/common/estream.c @@ -1,22 +1,23 @@ /* estream.c - Extended stream I/O/ Library - Copyright (C) 2004 g10 Code GmbH - - This file is part of Libestream. - - Libestream 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 2 of the License, - or (at your option) any later version. - - Libestream is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Libestream; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. */ + * Copyright (C) 2004 g10 Code GmbH + * + * This file is part of Libestream. + * + * Libestream 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 2 of the License, + * or (at your option) any later version. + * + * Libestream is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Libestream; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ #ifdef USE_ESTREAM_SUPPORT_H # include diff --git a/common/estream.h b/common/estream.h index ebe575926..a9b4847c8 100644 --- a/common/estream.h +++ b/common/estream.h @@ -1,22 +1,23 @@ /* estream.h - Extended stream I/O/ Library - Copyright (C) 2004 g10 Code GmbH - - This file is part of Libestream. - - Libestream 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 2 of the License, - or (at your option) any later version. - - Libestream is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Libestream; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. */ + * Copyright (C) 2004 g10 Code GmbH + * + * This file is part of Libestream. + * + * Libestream 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 2 of the License, + * or (at your option) any later version. + * + * Libestream is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Libestream; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ #ifndef ESTREAM_H #define ESTREAM_H diff --git a/common/exechelp.c b/common/exechelp.c index dc0a6b0e1..e64b69022 100644 --- a/common/exechelp.c +++ b/common/exechelp.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/common/exechelp.h b/common/exechelp.h index f00d18dd8..1df029b7e 100644 --- a/common/exechelp.h +++ b/common/exechelp.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_COMMON_EXECHELP_H diff --git a/common/gettime.c b/common/gettime.c index ecdc7df95..c4ea3283a 100644 --- a/common/gettime.c +++ b/common/gettime.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/common/homedir.c b/common/homedir.c index a118cbac1..39d6dce20 100644 --- a/common/homedir.c +++ b/common/homedir.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/common/i18n.h b/common/i18n.h index 0e13dca4d..0187ba265 100644 --- a/common/i18n.h +++ b/common/i18n.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_COMMON_I18N_H diff --git a/common/iobuf.c b/common/iobuf.c index bbb666f67..8f7374f8c 100644 --- a/common/iobuf.c +++ b/common/iobuf.c @@ -16,7 +16,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/common/iobuf.h b/common/iobuf.h index 3b8f4b572..a3dd7f1c5 100644 --- a/common/iobuf.h +++ b/common/iobuf.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_COMMON_IOBUF_H diff --git a/common/isascii.c b/common/isascii.c index 565c71664..b71febe99 100644 --- a/common/isascii.c +++ b/common/isascii.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifdef HAVE_CONFIG_H diff --git a/common/maperror.c b/common/maperror.c index 9efd64338..06546b501 100644 --- a/common/maperror.c +++ b/common/maperror.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/common/membuf.c b/common/membuf.c index 75f6bdb2a..2d35fefab 100644 --- a/common/membuf.c +++ b/common/membuf.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/common/membuf.h b/common/membuf.h index c199363cc..9033be61e 100644 --- a/common/membuf.h +++ b/common/membuf.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_COMMON_MEMBUF_H diff --git a/common/miscellaneous.c b/common/miscellaneous.c index e9f8ed27f..da74f65bc 100644 --- a/common/miscellaneous.c +++ b/common/miscellaneous.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/common/mkerrors b/common/mkerrors index 5a1ef33da..994c61352 100755 --- a/common/mkerrors +++ b/common/mkerrors @@ -17,7 +17,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. cat < diff --git a/common/simple-gettext.c b/common/simple-gettext.c index b6b851c77..56a305fd8 100644 --- a/common/simple-gettext.c +++ b/common/simple-gettext.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* This is a simplified version of gettext written by Ulrich Drepper. diff --git a/common/simple-pwquery.c b/common/simple-pwquery.c index f156ca3f1..e405c1ec0 100644 --- a/common/simple-pwquery.c +++ b/common/simple-pwquery.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* This module is intended as a standalone client implementation to diff --git a/common/simple-pwquery.h b/common/simple-pwquery.h index e3270d6c5..5b941d06f 100644 --- a/common/simple-pwquery.h +++ b/common/simple-pwquery.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef SIMPLE_PWQUERY_H diff --git a/common/sysutils.c b/common/sysutils.c index a8f6f6f5d..3e52cdaa3 100644 --- a/common/sysutils.c +++ b/common/sysutils.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/common/sysutils.h b/common/sysutils.h index 08198f685..c40dbfaa9 100644 --- a/common/sysutils.h +++ b/common/sysutils.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_COMMON_SYSUTILS_H diff --git a/common/ttyio.c b/common/ttyio.c index c9f41c626..38883afa5 100644 --- a/common/ttyio.c +++ b/common/ttyio.c @@ -16,7 +16,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/common/ttyio.h b/common/ttyio.h index 6148d644a..32d159863 100644 --- a/common/ttyio.h +++ b/common/ttyio.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_COMMON_TTYIO_H #define GNUPG_COMMON_TTYIO_H diff --git a/common/util.h b/common/util.h index 295d785c5..29106bf9c 100644 --- a/common/util.h +++ b/common/util.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_COMMON_UTIL_H diff --git a/common/vasprintf.c b/common/vasprintf.c index 9efea33f2..4bed8de66 100644 --- a/common/vasprintf.c +++ b/common/vasprintf.c @@ -15,8 +15,8 @@ Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with libiberty; see the file COPYING.LIB. If -not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, -Boston, MA 02111-1307, USA. */ +not, write to the Free Software Foundation, Inc., 51 Franklin Street, +Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include diff --git a/common/w32reg.c b/common/w32reg.c index a85ac7348..84308e916 100644 --- a/common/w32reg.c +++ b/common/w32reg.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/common/xasprintf.c b/common/xasprintf.c index 46740a2e6..75ae18072 100644 --- a/common/xasprintf.c +++ b/common/xasprintf.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/common/xreadline.c b/common/xreadline.c index 23aa35269..8400df330 100644 --- a/common/xreadline.c +++ b/common/xreadline.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/common/yesno.c b/common/yesno.c index 737071691..9ca513740 100644 --- a/common/yesno.c +++ b/common/yesno.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/configure.ac b/configure.ac index f3066a0a9..d77093a63 100644 --- a/configure.ac +++ b/configure.ac @@ -16,7 +16,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. # Process this file with autoconf to produce a configure script. AC_PREREQ(2.52) diff --git a/doc/Makefile.am b/doc/Makefile.am index 47dd36208..dae053ec2 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -14,7 +14,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in diff --git a/doc/gnupg-card-architecture.fig b/doc/gnupg-card-architecture.fig index e5772cd0f..49351c720 100644 --- a/doc/gnupg-card-architecture.fig +++ b/doc/gnupg-card-architecture.fig @@ -15,7 +15,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. Landscape Center Metric diff --git a/g10/Makefile.am b/g10/Makefile.am index aeb24d7b3..fb54dd9f0 100644 --- a/g10/Makefile.am +++ b/g10/Makefile.am @@ -15,7 +15,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in diff --git a/g10/call-agent.c b/g10/call-agent.c index 55fc62569..e3bd7ed57 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #if 0 /* let Emacs display a red warning */ diff --git a/g10/call-agent.h b/g10/call-agent.h index 71044e38b..d09b87e3a 100644 --- a/g10/call-agent.h +++ b/g10/call-agent.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_G10_CALL_AGENT_H #define GNUPG_G10_CALL_AGENT_H diff --git a/g10/comment.c b/g10/comment.c index b52104cd7..193087107 100644 --- a/g10/comment.c +++ b/g10/comment.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/g10/gpg.c b/g10/gpg.c index 52ae553c1..4235f3f7a 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -356,6 +356,7 @@ enum cmd_and_opt_values oAllowMultisigVerification, oEnableDSA2, oDisableDSA2, + oDebugAllowRun, oNoop }; @@ -701,6 +702,8 @@ static ARGPARSE_OPTS opts[] = { { oNoRequireCrossCert, "no-require-cross-certification", 0, "@"}, { oAutoKeyLocate, "auto-key-locate", 2, "@"}, { oNoAutoKeyLocate, "no-auto-key-locate", 0, "@"}, + + { oDebugAllowRun, "debug_allow_run", 0, "@"}, {0,NULL,0,NULL} }; @@ -1684,6 +1687,7 @@ main (int argc, char **argv ) int with_fpr = 0; /* make an option out of --fingerprint */ int any_explicit_recipient = 0; int require_secmem=0,got_secmem=0; + int allow_run = 0; #ifdef __riscos__ opt.lock_once = 1; @@ -2663,6 +2667,8 @@ main (int argc, char **argv ) case oEnableDSA2: opt.flags.dsa2=1; break; case oDisableDSA2: opt.flags.dsa2=0; break; + case oDebugAllowRun: allow_run = 1; break; + case oNoop: break; default : pargs.err = configfp? 1:2; break; @@ -2716,6 +2722,9 @@ main (int argc, char **argv ) } #endif + if (!allow_run) + log_fatal ("This version of gpg is not ready for use, use gpg 1.4.x\n"); + /* FIXME: We should use logging to a file only in server mode; however we have not yet implemetyed that. Thus we try to get away with --batch as indication for logging to file diff --git a/g10/gpg.h b/g10/gpg.h index 8ef46fdca..100a8e349 100644 --- a/g10/gpg.h +++ b/g10/gpg.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_G10_GPG_H #define GNUPG_G10_GPG_H diff --git a/g10/pkglue.c b/g10/pkglue.c index f062d8366..3f9669d27 100644 --- a/g10/pkglue.c +++ b/g10/pkglue.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/g10/pkglue.h b/g10/pkglue.h index 43b82785b..866960bfd 100644 --- a/g10/pkglue.h +++ b/g10/pkglue.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_G10_PKGLUE_H diff --git a/include/_regex.h b/include/_regex.h index fac441dc6..ddd002484 100644 --- a/include/_regex.h +++ b/include/_regex.h @@ -16,8 +16,8 @@ You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free - Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - 02111-1307 USA. */ + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301, USA. */ #ifndef _REGEX_H #define _REGEX_H 1 diff --git a/include/errors.h b/include/errors.h index ed437fa99..f3269ce5b 100644 --- a/include/errors.h +++ b/include/errors.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_INCLUDE_ERRORS_H #define GNUPG_INCLUDE_ERRORS_H diff --git a/include/memory.h b/include/memory.h index 35719da62..2e2f8fdce 100644 --- a/include/memory.h +++ b/include/memory.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef G10_MEMORY_H diff --git a/include/mpi.h b/include/mpi.h index b732923a2..7402ef6d3 100644 --- a/include/mpi.h +++ b/include/mpi.h @@ -16,7 +16,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. * * Note: This code is heavily based on the GNU MP Library. * Actually it's the same code with only minor changes in the diff --git a/include/util.h b/include/util.h index c579c152e..1d6d01366 100644 --- a/include/util.h +++ b/include/util.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef G10_UTIL_H #define G10_UTIL_H diff --git a/jnlib/Makefile.am b/jnlib/Makefile.am index 69eac4bf7..5fd48495c 100644 --- a/jnlib/Makefile.am +++ b/jnlib/Makefile.am @@ -14,7 +14,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in diff --git a/jnlib/argparse.c b/jnlib/argparse.c index 980d1186c..15a7c546e 100644 --- a/jnlib/argparse.c +++ b/jnlib/argparse.c @@ -1,21 +1,22 @@ /* [argparse.c wk 17.06.97] Argument Parser for option handling * Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. * - * This file is part of GnuPG. + * 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 2 of the License, or - * (at your option) any later version. + * 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 2 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. + * 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include @@ -904,7 +905,7 @@ strusage( int level ) switch( level ) { case 11: p = "foo"; break; case 13: p = "0.0"; break; - case 14: p = "Copyright (C) 2005 Free Software Foundation, Inc."; break; + case 14: p = "Copyright (C) 2006 Free Software Foundation, Inc."; break; case 15: p = "This program comes with ABSOLUTELY NO WARRANTY.\n" "This is free software, and you are welcome to redistribute it\n" @@ -920,7 +921,8 @@ strusage( int level ) "GNU General Public License for more details.\n\n" "You should have received a copy of the GNU General Public License\n" "along with this program; if not, write to the Free Software\n" -"Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.\n"; +"Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,\n" +"USA.\n"; break; case 40: /* short and long usage */ case 41: p = ""; break; diff --git a/jnlib/argparse.h b/jnlib/argparse.h index e8922faa4..531622ea5 100644 --- a/jnlib/argparse.h +++ b/jnlib/argparse.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef LIBJNLIB_ARGPARSE_H diff --git a/jnlib/dotlock.c b/jnlib/dotlock.c index b7f892717..89edb7b91 100644 --- a/jnlib/dotlock.c +++ b/jnlib/dotlock.c @@ -16,7 +16,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/jnlib/dotlock.h b/jnlib/dotlock.h index 2cb39008a..1c0f05cb2 100644 --- a/jnlib/dotlock.h +++ b/jnlib/dotlock.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef LIBJNLIB_DOTLOCK_H diff --git a/jnlib/libjnlib-config.h b/jnlib/libjnlib-config.h index da3991432..ded6e057b 100644 --- a/jnlib/libjnlib-config.h +++ b/jnlib/libjnlib-config.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /**************** diff --git a/jnlib/logging.c b/jnlib/logging.c index c944006a5..20ba02ccd 100644 --- a/jnlib/logging.c +++ b/jnlib/logging.c @@ -16,7 +16,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ diff --git a/jnlib/logging.h b/jnlib/logging.h index b5c0bd741..3ad43b4ec 100644 --- a/jnlib/logging.h +++ b/jnlib/logging.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef LIBJNLIB_LOGGING_H diff --git a/jnlib/mischelp.h b/jnlib/mischelp.h index 54da4cc1f..8e7f9c346 100644 --- a/jnlib/mischelp.h +++ b/jnlib/mischelp.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef LIBJNLIB_MISCHELP_H diff --git a/jnlib/stringhelp.c b/jnlib/stringhelp.c index 27b8a25e8..9df852754 100644 --- a/jnlib/stringhelp.c +++ b/jnlib/stringhelp.c @@ -16,7 +16,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/jnlib/stringhelp.h b/jnlib/stringhelp.h index 405d6dbc4..b8f4dbec0 100644 --- a/jnlib/stringhelp.h +++ b/jnlib/stringhelp.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef LIBJNLIB_STRINGHELP_H diff --git a/jnlib/strlist.c b/jnlib/strlist.c index 52b4d5869..87e121705 100644 --- a/jnlib/strlist.c +++ b/jnlib/strlist.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/jnlib/strlist.h b/jnlib/strlist.h index 3c1252a44..ee9f1fa60 100644 --- a/jnlib/strlist.h +++ b/jnlib/strlist.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef LIBJNLIB_STRLIST_H diff --git a/jnlib/types.h b/jnlib/types.h index 934b7a6ee..89d245943 100644 --- a/jnlib/types.h +++ b/jnlib/types.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef LIBJNLIB_TYPES_H diff --git a/jnlib/utf8conv.c b/jnlib/utf8conv.c index 4df8b7b32..9fba1ed4f 100644 --- a/jnlib/utf8conv.c +++ b/jnlib/utf8conv.c @@ -16,7 +16,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/jnlib/utf8conv.h b/jnlib/utf8conv.h index 6e2ce9944..344c47f92 100644 --- a/jnlib/utf8conv.h +++ b/jnlib/utf8conv.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef LIBJNLIB_UTF8CONF_H diff --git a/jnlib/w32-afunix.c b/jnlib/w32-afunix.c index c93d389da..84d799f1f 100644 --- a/jnlib/w32-afunix.c +++ b/jnlib/w32-afunix.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifdef _WIN32 #include diff --git a/jnlib/w32-afunix.h b/jnlib/w32-afunix.h index 367832299..d0eb8cf7e 100644 --- a/jnlib/w32-afunix.h +++ b/jnlib/w32-afunix.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifdef _WIN32 #ifndef W32AFUNIX_DEFS_H diff --git a/jnlib/w32-pth.c b/jnlib/w32-pth.c index 2f041c490..4107c7cb3 100644 --- a/jnlib/w32-pth.c +++ b/jnlib/w32-pth.c @@ -16,7 +16,8 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. * * ------------------------------------------------------------------ * This code is based on Ralf Engelschall's GNU Pth, a non-preemptive diff --git a/jnlib/w32-pth.h b/jnlib/w32-pth.h index 5ef0ab240..524010d92 100644 --- a/jnlib/w32-pth.h +++ b/jnlib/w32-pth.h @@ -16,7 +16,8 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. * * ------------------------------------------------------------------ * This code is based on Ralf Engelschall's GNU Pth, a non-preemptive diff --git a/jnlib/xmalloc.c b/jnlib/xmalloc.c index 1cfaab9f7..f5b92ba41 100644 --- a/jnlib/xmalloc.c +++ b/jnlib/xmalloc.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/jnlib/xmalloc.h b/jnlib/xmalloc.h index 150ef3664..8bfa7df79 100644 --- a/jnlib/xmalloc.h +++ b/jnlib/xmalloc.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef LIBJNLIB_XMALLOC_H diff --git a/kbx/Makefile.am b/kbx/Makefile.am index f42e517bf..063dbb4c0 100644 --- a/kbx/Makefile.am +++ b/kbx/Makefile.am @@ -15,7 +15,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in diff --git a/kbx/kbxutil.c b/kbx/kbxutil.c index 0569b5a67..19d356007 100644 --- a/kbx/kbxutil.c +++ b/kbx/kbxutil.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/kbx/keybox-blob.c b/kbx/keybox-blob.c index eacc0014a..f3fe31617 100644 --- a/kbx/keybox-blob.c +++ b/kbx/keybox-blob.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ diff --git a/kbx/keybox-defs.h b/kbx/keybox-defs.h index 7bbed8519..ad53c71a7 100644 --- a/kbx/keybox-defs.h +++ b/kbx/keybox-defs.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef KEYBOX_DEFS_H diff --git a/kbx/keybox-dump.c b/kbx/keybox-dump.c index 495fb249e..d28611377 100644 --- a/kbx/keybox-dump.c +++ b/kbx/keybox-dump.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/kbx/keybox-file.c b/kbx/keybox-file.c index 3883ce607..e68e96cf9 100644 --- a/kbx/keybox-file.c +++ b/kbx/keybox-file.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/kbx/keybox-init.c b/kbx/keybox-init.c index 46a29978a..6c01b4f3a 100644 --- a/kbx/keybox-init.c +++ b/kbx/keybox-init.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/kbx/keybox-openpgp.c b/kbx/keybox-openpgp.c index 7401949c9..8ac713979 100644 --- a/kbx/keybox-openpgp.c +++ b/kbx/keybox-openpgp.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* This is a simple OpenPGP parser suitable for all OpenPGP key diff --git a/kbx/keybox-search-desc.h b/kbx/keybox-search-desc.h index 4be59c27d..f3a69d0f1 100644 --- a/kbx/keybox-search-desc.h +++ b/kbx/keybox-search-desc.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* diff --git a/kbx/keybox-search.c b/kbx/keybox-search.c index 2ce3c1923..f95e6eb06 100644 --- a/kbx/keybox-search.c +++ b/kbx/keybox-search.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/kbx/keybox-update.c b/kbx/keybox-update.c index a16c18e23..bb43d287b 100644 --- a/kbx/keybox-update.c +++ b/kbx/keybox-update.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/kbx/keybox-util.c b/kbx/keybox-util.c index ed5d93de0..6eb85ba3f 100644 --- a/kbx/keybox-util.c +++ b/kbx/keybox-util.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/kbx/keybox.h b/kbx/keybox.h index af1fc4516..0f97fb7fc 100644 --- a/kbx/keybox.h +++ b/kbx/keybox.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef KEYBOX_H diff --git a/kbx/mkerrors b/kbx/mkerrors index 5adb7bfdf..d3d096c5d 100755 --- a/kbx/mkerrors +++ b/kbx/mkerrors @@ -17,7 +17,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. cat < diff --git a/scd/app-nks.c b/scd/app-nks.c index 73ec8ea01..1ca8d4187 100644 --- a/scd/app-nks.c +++ b/scd/app-nks.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index 5e9281a38..842881f3a 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. * * $Id$ */ diff --git a/scd/app-p15.c b/scd/app-p15.c index 4203a6840..8a7732c85 100644 --- a/scd/app-p15.c +++ b/scd/app-p15.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* Information pertaining to the BELPIC developer card samples: diff --git a/scd/app.c b/scd/app.c index 363e386ce..e3d42054b 100644 --- a/scd/app.c +++ b/scd/app.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/scd/atr.c b/scd/atr.c index 6475e83f8..bd5a22621 100644 --- a/scd/atr.c +++ b/scd/atr.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/scd/atr.h b/scd/atr.h index 5fdd57457..c70089ca5 100644 --- a/scd/atr.h +++ b/scd/atr.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef ATR_H diff --git a/scd/card-common.h b/scd/card-common.h index cefaf120f..dd7529d5b 100644 --- a/scd/card-common.h +++ b/scd/card-common.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef CARD_COMMON_H diff --git a/scd/card-dinsig.c b/scd/card-dinsig.c index df09bfb57..d50d758f2 100644 --- a/scd/card-dinsig.c +++ b/scd/card-dinsig.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* The German signature law and its bylaw (SigG and SigV) is currently diff --git a/scd/card-p15.c b/scd/card-p15.c index ae3ef148f..63d537d5a 100644 --- a/scd/card-p15.c +++ b/scd/card-p15.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/scd/card.c b/scd/card.c index 9ec2a52c5..7a41ab7bb 100644 --- a/scd/card.c +++ b/scd/card.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/scd/command.c b/scd/command.c index 2ed685587..4629d9edf 100644 --- a/scd/command.c +++ b/scd/command.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/scd/sc-copykeys.c b/scd/sc-copykeys.c index 66b6894e0..395b4625a 100644 --- a/scd/sc-copykeys.c +++ b/scd/sc-copykeys.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/scd/scdaemon.c b/scd/scdaemon.c index e24b42132..b11cc7a91 100644 --- a/scd/scdaemon.c +++ b/scd/scdaemon.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/scd/scdaemon.h b/scd/scdaemon.h index abe9730a7..f9689ee09 100644 --- a/scd/scdaemon.h +++ b/scd/scdaemon.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef SCDAEMON_H diff --git a/scd/tlv.c b/scd/tlv.c index b436d956a..6ddbeaf1f 100644 --- a/scd/tlv.c +++ b/scd/tlv.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/scd/tlv.h b/scd/tlv.h index f587dd9df..877573d25 100644 --- a/scd/tlv.h +++ b/scd/tlv.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef SCD_TLV_H diff --git a/scripts/compile b/scripts/compile index ac07cc541..b6e6dcb0f 100755 --- a/scripts/compile +++ b/scripts/compile @@ -17,7 +17,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA.. # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a diff --git a/scripts/config.guess b/scripts/config.guess index 77c7cbab0..a4929a979 100755 --- a/scripts/config.guess +++ b/scripts/config.guess @@ -17,7 +17,8 @@ timestamp='2004-08-13' # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA.. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a diff --git a/sm/ChangeLog b/sm/ChangeLog index 48e8473fa..f191e7512 100644 --- a/sm/ChangeLog +++ b/sm/ChangeLog @@ -1,3 +1,10 @@ +2006-06-20 Werner Koch + + * gpgsm.c (gpgsm_init_default_ctrl): Take care of the command line + option --include-certs. + + * keylist.c (list_cert_raw): Print the certid. + 2006-05-23 Werner Koch * keydb.c (hextobyte): Deleted as it is now defined in jnlib. diff --git a/sm/Makefile.am b/sm/Makefile.am index b5882ae1d..be53e8d25 100644 --- a/sm/Makefile.am +++ b/sm/Makefile.am @@ -14,7 +14,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in diff --git a/sm/base64.c b/sm/base64.c index 62c2c9ad9..59ab6f24b 100644 --- a/sm/base64.c +++ b/sm/base64.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/sm/call-agent.c b/sm/call-agent.c index 9942672ae..85ec78c63 100644 --- a/sm/call-agent.c +++ b/sm/call-agent.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/sm/call-dirmngr.c b/sm/call-dirmngr.c index 85467d4a2..0de09a9ba 100644 --- a/sm/call-dirmngr.c +++ b/sm/call-dirmngr.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/sm/certchain.c b/sm/certchain.c index 44d72efd3..4a4ac49f6 100644 --- a/sm/certchain.c +++ b/sm/certchain.c @@ -16,7 +16,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/sm/certcheck.c b/sm/certcheck.c index 5fb376712..732356149 100644 --- a/sm/certcheck.c +++ b/sm/certcheck.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/sm/certdump.c b/sm/certdump.c index 1f2ea7b18..0d5146abc 100644 --- a/sm/certdump.c +++ b/sm/certdump.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/sm/certlist.c b/sm/certlist.c index b036a85d7..cde2930ec 100644 --- a/sm/certlist.c +++ b/sm/certlist.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/sm/certreqgen.c b/sm/certreqgen.c index c523c992a..744969719 100644 --- a/sm/certreqgen.c +++ b/sm/certreqgen.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* diff --git a/sm/decrypt.c b/sm/decrypt.c index 9e5518b0f..70d48c983 100644 --- a/sm/decrypt.c +++ b/sm/decrypt.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/sm/delete.c b/sm/delete.c index 7533f7291..0d2f1fd9d 100644 --- a/sm/delete.c +++ b/sm/delete.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/sm/encrypt.c b/sm/encrypt.c index e4c0d5437..07c2ba8ce 100644 --- a/sm/encrypt.c +++ b/sm/encrypt.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/sm/export.c b/sm/export.c index f9d6dac62..b08a017d2 100644 --- a/sm/export.c +++ b/sm/export.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/sm/fingerprint.c b/sm/fingerprint.c index 9441483bf..d6a3900f0 100644 --- a/sm/fingerprint.c +++ b/sm/fingerprint.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/sm/gpgsm.c b/sm/gpgsm.c index 7347bf575..5363b8ad6 100644 --- a/sm/gpgsm.c +++ b/sm/gpgsm.c @@ -1,5 +1,6 @@ /* gpgsm.c - GnuPG for S/MIME - * Copyright (C) 2001, 2002, 2003, 2004 Free Software Foundation, Inc. + * Copyright (C) 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -15,7 +16,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include @@ -461,6 +463,10 @@ static unsigned int debug_value; /* Option --enable-special-filenames */ static int allow_special_filenames; +/* Default value for include-certs. */ +static int default_include_certs = 1; /* Only include the signer's cert. */ + + static char *build_list (const char *text, const char *(*mapf)(int), int (*chkf)(int)); @@ -998,7 +1004,9 @@ main ( int argc, char **argv) ctrl.use_ocsp = opt.enable_ocsp = 1; break; - case oIncludeCerts: ctrl.include_certs = pargs.r.ret_int; break; + case oIncludeCerts: + ctrl.include_certs = default_include_certs = pargs.r.ret_int; + break; case oPolicyFile: xfree (opt.policy_file); @@ -1657,7 +1665,7 @@ gpgsm_exit (int rc) void gpgsm_init_default_ctrl (struct server_control_s *ctrl) { - ctrl->include_certs = 1; /* only include the signer's cert */ + ctrl->include_certs = default_include_certs; ctrl->use_ocsp = opt.enable_ocsp; } diff --git a/sm/gpgsm.h b/sm/gpgsm.h index 438252050..b49f34640 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GPGSM_H diff --git a/sm/import.c b/sm/import.c index 6d00e91ea..b56014a1a 100644 --- a/sm/import.c +++ b/sm/import.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/sm/keydb.c b/sm/keydb.c index d5932135d..81936cf6a 100644 --- a/sm/keydb.c +++ b/sm/keydb.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/sm/keydb.h b/sm/keydb.h index fb4001b64..814ae9f1e 100644 --- a/sm/keydb.h +++ b/sm/keydb.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GNUPG_KEYDB_H diff --git a/sm/keylist.c b/sm/keylist.c index b744a157f..9baf065d0 100644 --- a/sm/keylist.c +++ b/sm/keylist.c @@ -16,7 +16,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include @@ -598,6 +599,10 @@ list_cert_raw (ctrl_t ctrl, KEYDB_HANDLE hd, fprintf (fp, " md5_fpr: %s\n", dn?dn:"error"); xfree (dn); + dn = gpgsm_get_certid (cert); + fprintf (fp, " certid: %s\n", dn?dn:"error"); + xfree (dn); + dn = gpgsm_get_keygrip_hexstring (cert); fprintf (fp, " keygrip: %s\n", dn?dn:"error"); xfree (dn); diff --git a/sm/misc.c b/sm/misc.c index cd072ce6b..86cb506d6 100644 --- a/sm/misc.c +++ b/sm/misc.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/sm/qualified.c b/sm/qualified.c index 07abaadc4..474e1488d 100644 --- a/sm/qualified.c +++ b/sm/qualified.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/sm/server.c b/sm/server.c index 87a06ee4e..57e5d8f38 100644 --- a/sm/server.c +++ b/sm/server.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/sm/sign.c b/sm/sign.c index 74bfe41aa..d656825e8 100644 --- a/sm/sign.c +++ b/sm/sign.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/sm/verify.c b/sm/verify.c index f37cf4a75..4e6574078 100644 --- a/sm/verify.c +++ b/sm/verify.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/tests/Makefile.am b/tests/Makefile.am index 5264b8859..38b64c6ea 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -15,7 +15,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in diff --git a/tests/asschk.c b/tests/asschk.c index 6a05fe1a8..40b95ba7d 100644 --- a/tests/asschk.c +++ b/tests/asschk.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* This is a simple stand-alone Assuan server test program. We don't diff --git a/tests/pkits/Makefile.am b/tests/pkits/Makefile.am index 41fdec497..d53d35a25 100644 --- a/tests/pkits/Makefile.am +++ b/tests/pkits/Makefile.am @@ -15,7 +15,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. ## Process this file with automake to produce Makefile.in diff --git a/tests/pkits/common.sh b/tests/pkits/common.sh index 5e773ea5d..09fb62bc8 100644 --- a/tests/pkits/common.sh +++ b/tests/pkits/common.sh @@ -16,7 +16,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. # reset some environment variables because we do not want to test locals export LANG=C diff --git a/tests/pkits/import-all-certs b/tests/pkits/import-all-certs index d1af5fb03..2d70d06df 100755 --- a/tests/pkits/import-all-certs +++ b/tests/pkits/import-all-certs @@ -15,7 +15,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. . ${srcdir:-.}/common.sh || exit 2 diff --git a/tests/pkits/validate-all-certs b/tests/pkits/validate-all-certs index f482fdb51..08f72af71 100755 --- a/tests/pkits/validate-all-certs +++ b/tests/pkits/validate-all-certs @@ -16,7 +16,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. . ${srcdir:-.}/common.sh || exit 2 diff --git a/tools/Makefile.am b/tools/Makefile.am index d9ef8812a..6b4767a79 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -15,7 +15,8 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. EXTRA_DIST = Manifest watchgnupg.c \ addgnupghome gpgsm-gencert.sh diff --git a/tools/gpg-connect-agent.c b/tools/gpg-connect-agent.c index c9a324fa8..90e321000 100644 --- a/tools/gpg-connect-agent.c +++ b/tools/gpg-connect-agent.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index 2da88bc49..04a61a193 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -1,21 +1,23 @@ /* gpgconf-comp.c - Configuration utility for GnuPG. - 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 2 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, write to the Free Software Foundation, - Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + * 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 2 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, write to the Free Software Foundation, + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ #if HAVE_CONFIG_H #include diff --git a/tools/gpgconf.c b/tools/gpgconf.c index dd505e99d..87ba45ae1 100644 --- a/tools/gpgconf.c +++ b/tools/gpgconf.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/tools/gpgconf.h b/tools/gpgconf.h index 138380b6d..c083c26aa 100644 --- a/tools/gpgconf.h +++ b/tools/gpgconf.h @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifndef GPGCONF_H diff --git a/tools/gpgkey2ssh.c b/tools/gpgkey2ssh.c index e874ab22e..3dcb6516e 100644 --- a/tools/gpgkey2ssh.c +++ b/tools/gpgkey2ssh.c @@ -1,22 +1,23 @@ /* gpgkey2ssh.c - Converter ... - 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 2 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, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. */ + * 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 2 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ #include diff --git a/tools/gpgparsemail.c b/tools/gpgparsemail.c index 566f5747f..30759f9a4 100644 --- a/tools/gpgparsemail.c +++ b/tools/gpgparsemail.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ diff --git a/tools/no-libgcrypt.c b/tools/no-libgcrypt.c index 82f6a8bb5..636df10c6 100644 --- a/tools/no-libgcrypt.c +++ b/tools/no-libgcrypt.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #include diff --git a/tools/symcryptrun.c b/tools/symcryptrun.c index 075e0b444..406cbb2a2 100644 --- a/tools/symcryptrun.c +++ b/tools/symcryptrun.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ diff --git a/tools/watchgnupg.c b/tools/watchgnupg.c index 6cb570fbc..051ca50fe 100644 --- a/tools/watchgnupg.c +++ b/tools/watchgnupg.c @@ -15,7 +15,8 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ #ifdef HAVE_CONFIG_H -- cgit From 6d77c76ef2de1b944141f9b5fbcd9392aaf1a4e7 Mon Sep 17 00:00:00 2001 From: Marcus Brinkmann Date: Sat, 29 Jul 2006 16:40:54 +0000 Subject: 2006-07-29 Marcus Brinkmann * preset-passphrase.c (preset_passphrase): Do not strip off last character of passphrase. (make_hexstring): New function. * command.c (cmd_preset_passphrase): Use parse_hexstring to syntax check passphrase argument. Truncate passphrase at delimiter. --- agent/ChangeLog | 8 ++++++++ agent/command.c | 9 ++++++++- agent/preset-passphrase.c | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 61 insertions(+), 3 deletions(-) (limited to 'agent/command.c') diff --git a/agent/ChangeLog b/agent/ChangeLog index 2048f5c18..97c7741ac 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,11 @@ +2006-07-29 Marcus Brinkmann + + * preset-passphrase.c (preset_passphrase): Do not strip off last + character of passphrase. + (make_hexstring): New function. + * command.c (cmd_preset_passphrase): Use parse_hexstring to syntax + check passphrase argument. Truncate passphrase at delimiter. + 2006-07-24 Werner Koch * minip12.c (build_key_bag): New args SHA1HASH and diff --git a/agent/command.c b/agent/command.c index 12770dac8..1f3923522 100644 --- a/agent/command.c +++ b/agent/command.c @@ -794,7 +794,7 @@ cmd_passwd (ASSUAN_CONTEXT ctx, char *line) return map_to_assuan_status (rc); } -/* PRESET_PASSPHRASE +/* PRESET_PASSPHRASE Set the cached passphrase/PIN for the key identified by the keygrip to passwd for the given time, where -1 means infinite and 0 means @@ -809,6 +809,7 @@ cmd_preset_passphrase (ASSUAN_CONTEXT ctx, char *line) char *grip_clear = NULL; char *passphrase = NULL; int ttl; + size_t len; if (!opt.allow_preset_passphrase) return gpg_error (GPG_ERR_NOT_SUPPORTED); @@ -837,6 +838,12 @@ cmd_preset_passphrase (ASSUAN_CONTEXT ctx, char *line) while (!(*line != ' ' && *line != '\t')) line++; + /* Syntax check the hexstring. */ + rc = parse_hexstring (ctx, line, &len); + if (rc) + return rc; + line[len] = '\0'; + /* If there is a passphrase, use it. Currently, a passphrase is required. */ if (*line) diff --git a/agent/preset-passphrase.c b/agent/preset-passphrase.c index 013c9411d..f876b0647 100644 --- a/agent/preset-passphrase.c +++ b/agent/preset-passphrase.c @@ -152,6 +152,38 @@ map_spwq_error (int err) } +/* Percent-Escape special characters. The string is valid until the + next invocation of the function. */ +static char * +make_hexstring (const char *src) +{ + int len = 2 * strlen (src) + 1; + char *dst; + char *res; + + res = dst = malloc (len); + if (!dst) + { + log_error ("can not escape string: %s\n", + gpg_strerror (gpg_error_from_errno (errno))); + return NULL; + } + +#define _tohex(nr) ((nr) < 10 ? ((nr) + '0') : (((nr) - 10) + 'A')) +#define tohex1(p) _tohex (*((unsigned char *) p) & 15) +#define tohex2(p) _tohex ((*((unsigned char *) p) >> 4) & 15) + + while (*src) + { + *(dst++) = tohex2 (src); + *(dst++) = tohex1 (src); + src++; + } + *dst = '\0'; + return res; +} + + static void preset_passphrase (const char *keygrip) { @@ -159,6 +191,7 @@ preset_passphrase (const char *keygrip) char *line; /* FIXME: Use secure memory. */ char passphrase[500]; + char *passphrase_esc; if (!opt_passphrase) { @@ -173,7 +206,6 @@ preset_passphrase (const char *keygrip) line = strchr (passphrase, '\n'); if (line) { - line--; if (line > passphrase && line[-1] == '\r') line--; *line = '\0'; @@ -182,8 +214,19 @@ preset_passphrase (const char *keygrip) /* FIXME: How to handle empty passwords? */ } + passphrase_esc = make_hexstring (opt_passphrase + ? opt_passphrase : passphrase); + if (!passphrase_esc) + { + /* Error message printed by callee. */ + return; + } + rc = asprintf (&line, "PRESET_PASSPHRASE %s -1 %s\n", keygrip, - opt_passphrase? opt_passphrase : passphrase); + passphrase_esc); + wipememory (passphrase_esc, strlen (passphrase_esc)); + free (passphrase_esc); + if (rc < 0) { log_error ("caching passphrase failed: %s\n", -- cgit

.*o#s7lTnqu.E.s8Drq +s8Vrns8Mfnp\fMZN0oipQB[^"PnB%FOcGipPB)oPs7uZos82irqu.Q7rVH@I,;J;/SuI8WR#1bBM"&e?a5EH?2MNRdY!qYU3irorhIrr*K,qYR.' +F+]rVZ]gp\t0irqcrE:6V<1OHuF$Pl[,4rKR>I&!C"gp\k-lq#(0hr;Zcks7uZoruV1< +qYds+NC`a@rVuoqrr2rtrqu]ns8;3WnFGT-s3LZLqZ$TUm.'Q +"g=i;p\OjcrrN-!huSJ>#L-5N1%W&`Pf^2p&+gnrqkjHrr2os +s8!8'AoWL6ko`+serr2rtrWrGF9Tbj)O-Z=5Q2d*)rjGa_8+uu]s8Dlprr;p)rr<#sjgDG![%b>2d/F=NrVuYlSt2IWJ,~> +%q/[h]>Mb4`l?!=aNVfJ`o5#=b5KBj`l#Aj<)n(H`Q6'<`l@qt1<%GY`6QmaK1hEHcHF5OaMu6@ +b0%fF_TBd9`5g';`6ZZJfZ_iN?\Xc,Q'dr-R@0D.m?J?HS<8i#QB5j3ak"AKa3_rJ`Q6."asP3R +`Q#pAd*G+/Uf:uD,-0MGa2cED]>MCse]YtTb/_WHceQt'nG`%Znc&"Wm;D:[o(2JPjhACB_ns=. +_7f1.na>o7naZ>Ke,Jk(n`\lNeTr\f[\Ns_SO.*_99g5ai=1us2kbka2lc,8:)l0bK7uNrQ#Gjc-4/A]pVkk +6;:*s^!b"%ap?52a1>VO7nm`K~> +$2*9dUt*=)]E!##D:&6NsoEcc>CF)cJIHZ3dc +('FI7rrahf=aGX`5tjR:<_-GGM2dd,<+rhqeC[5(#V9Y/GB8(iearb9qu$Hml0\BIrXA`)s8)Jd +MM6@DQWs:as7HdEu#rW`E#s8Dlqq>("4s8;iqqtm*uMdH>V +s8N&prr2rtrqu]ns82-UnF>K/s3glRs8N#os8V-HnaH1%~> +"gFl9p\XserrN-!huVbsI +rr)lqi5W[Qec#IGrW)oqrrLulrkg8'_8F41`Pf[5_8F+3]TJ#5Pa%;lO-#@2`5KR4_o0L1a2#a4 +aJk;^OHc(1Os5gI_T'*r>%1<+>uPR/WNNUUB4e?nce3/bNLn2-`PfX/o_e^mrqkjHrr2lr%fZA" +UkjSF5a>RLs8W&to`"jg$MeOkOI;`!PEhE!rg!MJmZm[8%[^[/Z1J"qrr;rsrr)lnrr;lp%f6/( +rqrBrLgC#Ws8N#or;Q]q#l`nmZa-p1_tha[$NL,*rVuShS=Q7UJ,~> +%q8^f]>Vk5`l?!=aNVfJ`o5#=b5KCI`l#Ak<)n%H`Q6'=aN2NHaN2E=`6-TYauOQJGg4LTaNhlH +`lQM@re]YtTb/_WHceQt'nFQ8Nnc/(Wm:Z"[p@J7L^pUnk_o0O4 +_7f1.na>o7naZ)@nbMhQftFr.^r+"+_RmeXpYjS.9dLPn&N4m&[C9U+1MCW@Ma;oseGnq%n`\Be +f?)(SaNDrE^<+I9`Q$9MdFF;*IW[`.6.=6*b0Tt+!6Y2[s2PAgf2cgBPb!a3P6R/5Q'[o,Qh-I_ +Q^O8.Q'7>mOH>K%8!VENccF8Ha2Gm>^r==5aN4A'+NqgGbK87q=*HjS`6-0E`lQ6@aNVfG^Uok5 +84-'EHbIkK$HpT9`lPVq91M]lJ,~> +$2J,~> +"gXu9p\b$frrN-!hu:Lo3SaKp5!<)\4&Ge_,?]Rt?`T?5!h`5'1\r;RW5qr[AGrr2rt +rq?BbCdN\u5+HGooDe[bnbrpjoMCL5OIMK"Q'Ra'Pld28Q2[*LPn'.CO+LMmq#(-_s8)Qkr;?Tn +rXSl+qZ$TeJo#aDq>^?hq#16lrr2p&jgDG$[%GG8df'URrVuirp8=FOT! +%qJgf]>_q6`Q#mo7rp`bSp@nC6^qIS%`PK:*_nj1SqruKo3&`lS/%s2bScaj/(QAn= +$2NK:s8W)qqYpKmr;ZfUrr;uurr2iprr^jLs*qt^9foUTN@B_;6&rVccrqYh6*rr;fo +p\jm_fM?dTPa7SuPEhK#Pj"A=Ocl&qPb(P=qu?Zos8N)squ?]q)u]U4p\12$VKlfh5FM->rVuls +qr#E1_peP^s8N!7s82T`_M^!a#QOaqA<<"FPNA).PRitBS!BLW7_ANsrVZ]qrV-*d+9269rq'WL7-saGs8N&m +r;?Qorr2ips7u$Tna>01s3glRs8Durs8V-JnF64'~> +#.128p\k*jr;Qfss53hUr;cirrsA]$n=6J]^%hL'q>UEo&Gu;'q!sq_HY<'Ms8Doqrr;oq(B+%- +s7u]ks8;fJ;0S.dQBd\uQ'[f$k`lID@tY*=66JZm2-:ImK1\A#Jf6?\-TaMPm5_8,^@rri>uk3r9Jrr"[BlFLq9f0JrVuiror"COT +%q\pd]>i"6`Q#m/]0#l&Q`R;oNI8t2Va3)ZMbK.]C +aNDZHaN_lOcGRfBbKA5A3G=83QC!o$Q'ISsrg!GKn!+*?PF%AgM_>"SrlG2`c2P^9aND`LaMu9< +dEL0pAXI@??nQfLc-+5LajA,>^Uh&-bJq]Ia2uKMgsXsHoCMMBr9s[UCAIi"]#);&_SF(-`PfU* +lh9l:nF#i@_Sj=,_8*q(])Vg!n)2HkJlsTtF,PJ\$NL6LDf(CbNOmmEmHaK4eaD,4 +`Q#mBRS`lQBH_nNgL<_,_T +8r14/a9fo/a2k\q9h%lsJ,~> +$2`W:s8W)qqu6Tnr;ZfUrr;uurr2iprr^p=lJMR?qYh!&rr;rsr;O;iP#2)Ts8Drsqu.E0s8Vfm +qt^0^C3ks8;lrr;6NorYth5q#BtRWi/7!ED?0+qu?]q +s81`m_o't$rVulr#ljesoYQB/F`[P*rHJQiG\UUs"TSW#!"T)> +#.C>8pAP!ir;Qfss53hUr;cirrsA]$n=HS^]D;@&q>M$*s8W&tqu+)bOAGiRs8Dusrr;oq(]==5 +pAb*frq0A_Mj]ltPa7Q!QBmf%Qg'bIQ^*u&R9Ic1r;HZqr;Q`prr2lp*<-!UBks7uX!qu$26G*A,QrfmJLlBV=4%[E9GO-booRT4"ApAXs_qu?Wo!<)os +$2a]kM(@T`s8)`ms8N#srs.PlZaR30eG7Mk$2so'rplnXS=d0=~> +%qo'd]#Mn5`l?!=aNVfJ`o5#=b5KBh`l#Gr;H7SA`6-0BrQ>/]$cL',a3`#WRk04HdJh;nc,doE +aND[)a;)nF`73)Xf$c]7I?Bb\QC=,+QB[Strg!GKn!+9HP*_/e6DhuJa3Dj-a=tNOaNDZLb/hTB +`Q6]_/tS^p?"5h@cc=/J`QQ]N\\,D`e]YtTb/_WHceQt'nFQ8Nnc/(XnSIgpq"h>,]u\73^r411 +`50+Pp@7S?mdp&>s7t`i^:hY0`PK@-^q[^ro_\L[p>t;]bclL`3*'^PO![M?F%^n]`nUIWlLjMo +mb,_$`P]^@]u&(>bf\#BcbmQF`AMmCAX6hFdaZ:Zb/hU%`<+'#aSa0laMc6I?"](XPa.MtOctuq +Qh$CYPED&oR@9Y9R[BG!Ob]!]85QA;f[%Rbao03YaT'C*a2uBB`7@#('"Z6+`5p'BaN2BBbf[i; +]ouJf852X8^ +$2r`9s8W)qr;Q]or;ZfUrr;uurr2iprr^s>lJ;F=qYh!)o)Jabs7C5/LdUh3rVlfrqu.E3rqcZm +pA1IJNgQ,kQ^!c$Pa.Q"Q'Ra#Pn'.FNgO1Jp&G'hrr;uoq>1-krYth9q=K2kWG>.M8B15ps8N&t +s8Clo_o't$rVulr'`\(*oYQB/F`VVHG^4U]H$*q5%gr78rrW)u%16!OEccSOH?aCD7h6",!!*-) +quA8R),kL*f+8u6>q +#.UG7p&4mhr;Qfss53hUr;cirrs8W#nXlb`\bZ-trY#8&s8Vloo5@H[6M:6Srr;uss8DrsrtbV1 +s8DZeTg+R)Q].8qQ^p`P]U0^r!t, +WdB8=GCPI2OH5TfNfoaE`kKF-_Sa4$^UT)8Dc9D!76WYDKoD+RR_uqX_o0@_qu7!"k3i3Hrr2rt +qu6U)nFhKY-E=+NP`q;qPaIc(Q^O.rP($6-q#(0f +rVccprX\u*s8W#a7=S>,s82ins8N#trr2rt#lW\hZa[<2g%j"o#ljl#m\6;BTXK@~> +%r,0c\]2e4a2Z*>aNVfJ`o5#=b5KBh`l#Jt;H7M?`666CrQ>/]$I$*&bf8/Q9gs@RrlHD/ccF,G +aNDZH`Q,j6cI((pM(O=@P)>NhQC4),Q]mPpPa.O4Qg^2&QB@&e9SCR3aMQ$Ebf%B:`lQ&6Y`koa5`l6-LaLJaf\^emCb0%cHb0Skkg[P46r:)*)p@n?f]",Jrb/D'5`5T[2 +]^,:]mdTZ9oCDV8\]2A"`Q?6=^AbrV_nbO-lgaB0nac#3h4f7>>$auq3@nTtVSC4&q!785jjW\O +ai26:`m297cHXSWb0e2H`m;`eIsruKSh.\&bKS,M`l5j5`Q$!Ab08#NaMuBOe4>@SR[9;'Octuq +Qh$CZPE(`iR@^+FSt)".Od2/gO`ERHg"+]oa2\+ts2b5_+NMLBaMl+\HT]`Q6-?aNVfE +^:TM-84cQIPJ5Db#g:3(X]J1b:NM$~> +&-(M@s8W)srVZTmr;6Eks5EtVrsA]&r;Q`rfBV\\rr;fn%f65*q>^6ipSqIoanl&5rr;lp%K$2* +rq>,9?^?b;R$a0DPEhY,h"gaFrr2rpqu?ZorqufrrYte7qVk;LSO%@@oC`.] +s8;ors8Clp_o't$rVulr'`\(*oYQB/F`V_NH$Xd^G&_(,#RC;.!s]#4!WE'/#Z>P\FaAC\G&BY] +"98T(!(?tk!!*'""q+ORH?OX]G&qqI2AR,?)^?s`0d[hG!!!6UD0pbTH&^AGrVQNjrr20LqYpNo +rqcZnqu?Ii8q9T9TT+0nnG`If#Pn;kM//HDQKsh7QN*6MPRit@QC!o'R@053=&m*4qYg3g+929; +r5_WIPQ1CWs8Dlqr;?Qns8Mrqs7P[No^:<1s3goGs8W#us53hE!;h9~> +#.UD5o_ndfqZ#=Ls8NB(qX^G[Y.+3&q#1s(s8Vopq#C)d>#!-7s7u]nrr<#trW3&trr3Z*gdGG6 +Ng,ouQ'R]#PEqT#QBma!Pm3G=_8F'4G("dbIX[-4O-,QfPE;eNffK`QN"`*_ns4]s8W)trtkRpn,E:arr2corqufo +;G)C0;4WVgqYgBlqu?Tnrr36#r:GbXQBd]uPld26PPp^TQ^F&&Q^L6krVd9+rVlf; +8n=+bqZ$Tpqu6WqrXAc)s8W#R['R9MSDF"frWrQ%q!+KBS"[ +%r>Bf\AlV1a2l?AaNDZH`nJK@`OfMU[S;W]!TWPfE`6\5JmHW]Ymb,_$ +`P]^@]thk:bfn5Ja25X9d5XCB:fWg)=4,:8rlG,Zrl#DfaND`Nb/VB>b1PNp?]pW=Po5dIOHZ*( +TUq[>OckolQ(4;#P!qR1e]l:Wrl$/&aNVfJa2>m?Ud&1/eAKAK`P]g;`lQ +&-(\@s8W#srqu]nrqZBhs5Eqfr;Q`rr;6Els4ZZ+jo>AZo)9R*j@F0;qu-Qlqu?]ps8W&tp]($\ +`($/[QBqK7s-2c5&!WNHOdMAtR@fX`LA:fGrV?Ens7lTn*rH!9s8)G(P,DPkF'":rs82ips8Dup +i5NUOe,91Err*Z4qtTg#LO"#cH@gNgH$3h1&.AjK!r`09"U5)4!WW608T8foI!^9Z57dl'!sAr4 +rW!9+!WrK-%TmduJ:@K?2InKH"pk;7&/#Qe&e,$F!!b/tGC4RYN7IP"rVlfrs68nHrr<#trr;lq +qtf>6LP%ISW*%[KrVlusrr;rmrX\o+s8)ch9Tk[,S!KD'Q]U0k0Tq`cIqYkUr;ZQls8Dlqqu6Hl +qt+`Q:%nJ`rr)iqrVuosrr;uprr;NYq![Xus8UOH!rr8srr3,anF#Z>J,~> +$+m"8pAb$hrVlfps53eds8Mros8W)qorFmh[.*tko)9L)i^R[1r;Zfrqu?]qrr;lqq#C0_`'foU +iKXt=RZil$Pa%N(NGjLFs82fqr;Q]q"Si#rrVHNn)>Wu>OJH)aDcDVks82ios8N&ri5WaTf)>RH +rW)oqrrLulrkirp`kfI0a1]<4G^+O]H?spdK8YkTOH>O0aiM<6^W"$dJ9-'[FaJ=XIt`iBOcYT_ +`PoR/_S*h*]T[D[I!9dWF)uSWM3*m^M3l2p^r=4-o`+sis8W)pk3rU%ZOcklqPEq;uP3nP6Pk^LbP*V8\B<(\)s7u]prqufps8Dus +ok\b@o(rCdrr<#rrXAc)s8W&K\[AcSRd'^trX/])rVufSURRgJ]mp~> +%rtrk\AlG*`QZQG`QH?E`ne]D`lQBF\7863BX#@ooZ7'T(t6Yg?>%^.aiDNHai)9BaND6Cc-raQ +O-'=("I5=AR@!l;!0cl:&X8`JOd_T%R$rkAD:I.W`Q$!ub^RXQ`luBIcHO5E^ra^H;1tQC=(Xg8 +dEfeV`lH'CeAer.\%1&4`lcHEai_rbdbF9_n*'0)mJ$VTs0fJO^r""._7[Iu`l>d+\_ZlHlg=-5 +oAIBk_7mh/`4s.*_o0I.]!1>algXH5q#C6@[(*fY\[f2TZFIQ^_8 +&-(\Bs8W&ts8;corqZBhs5O"hr;6Els8;]js8Up&,!!*0'qu@67=_VbuG'%[p'EA.?"9So,!<3*9 +!!<^6FaS7VG&q+J"onf,!!<6'!<<0"!"B#>11toYFEiboo`+mhrr3)am.gSZrt550r;QTkmV6`m +>th;c7E +r;Zcprr2lrs8N#trqlWnnaZAAm*G_3df'=JrVHNn"R#=Cl2#o~> +"hg_5p&FshrW3&urr:pUrr3H)r;ZfrqtRLnX0Cpes76-qrUjN?=nVUis8W)r%KHD+qu?ZiEDMm_ +Q'@NnPn07EPF%K#P`goRD#=&.rVulr!<)or"TSB!rqcTn)YF'T5 +`RXqcrr)orrVloT_#D4Z_SNn(_nX03GBe@Xrcg,BG^P.&OHGZb^qde,^qdgbKQ_QhG^"IVG^Fpo +MN!QAP`M?I_8"%/_=dj.rVlfpqW@;Ir;H`srVld(mqH]g +=@]?W=8MjirVloqrr2rrrr91$NL)(s81ZIS"6:iJ,~> +%s_Mt\&ZD+a3DlJ`QH?E`o5#=`XU)3`lQEI\S"W8Aui_ho#N!#fhcKUc,@TBd`TGBa2Q-?bg4^= +6BR=pQ'IPsQ'[r.Qg0i7R$!T!Q("#&LNQ_Sd*9VRaND`Obf\#H`QucLccF)D^=\fISVo^G>r0aI +aihfKa2Q('!]YDA%rkp\2]tM8#`PT. +`4Ne+`Pfp;`50:,`5TU,Zg75Qkk+WJpscjfaMu3B`P]U1_u@SF_naXip[[q:cg99*a2l9Cd(R08 +b/hZD`5]a:aj%k!@!Z0TWf96k_Rdk8da#hK_8F:6b0%fEa1T@9dFQMnL67djPb"&$PN%i]S!0=t +Mbo&Ub0.f;ai)HMb/VQJ^ase!eBQ%O`5]d7_o9jEbf[r?["21I6r?a`_pZX+aU$,6^mnCo5u +&-(Y?s8W)us8;corqZBhs5O"er;6Els8;cls8V-@o&frOnbrpkC3rk"rVuQhs8N#qrrq\F@[<+C +rg*MJs-2c5&!rHFR#mc$Q]YAEr;QTnqtU.)s82fpp\OpipJmui9LEZ/?2+C!p\t3mr/q"2FYH$+1SF*)D8.LG +CO1lfo`+mhrr3)am.gSZrtkY6rqu]mql,p](6a7Zj.0RZj&p +Q2d!PR%'!tc2.J>rr3K%rVHQjpP3m:HM[U?rr)iqs8W,u%0->(rr;NWp%.Fos8UOH$NL)#s8W#Z +nF#Q>J,~> +"i6t6nGiIdrW3&urr:pUrr3H)r;ZfrqtRh!WikFZs76-qrV'rK:WWS9q#'sf"mh+TPDkh,PQ@&8 +jcp@CP*VJsRZs+t;Y9haqu?TnrW)orrYGP5s82fqq"t*koi%K^84%0&>PS6up\t1!rr;oS`l,jH +rVc`p!<)lr@,A]:_o0U3`lQ-/H$=O[H[L0eI!^3dIts,MQBAAla25p7VKm9-I!p9eIX-9cH%(I, +PEM-Bahtm4`5.OZFFJO]I!Bp_I!g?mMNO!]QGpk]^WF@cs8W&srr)]UnGiFcs8Nc1s8W"s:PaBu +L8SjnqZ$QpqYpHls8Drsrso&.s7ZKmnjJP)R@05*Qf42:Q^!Z$RYs;Pq#C?mrt5),rVufiKlV6X +q#(0lrVlisrr2rr$i^2+r6i!Y\$VasqpGECr +%t.buYf+K"a3DlK`QH?E`o5#=`XU)3`lQEI]5^PFA"p`[o#N!#fiDlNU<_'#bg4;H`5fpDe]2W) +N/O*jQ'IPqPEhQ(Qg0iTQ]dZ%OIDK"Jj.f9c,I`?aND`Obf\#HaO%uK`kK:2eB^7@U.%7W<^b<] +bJDQLa2Q-`lQEHa_/W2_SsR:_nYI6qXsa`rQr*c`Q#pAaN)?B`PKC0`koI2q>'[Kkg/VIaiDKBb0[i9 +ai_]G`l,p;`lcTTM`l]E?\tCUNQBAecH+/HaMYp8aNDZH`kfO5dE'njdk???TV%R9P*=mj0U@oa +Pa-_GY0kV=aiM`DbK/#Sd;30"?.-s=`lH0?`P][=c-4>M^U/Jg7n6 +&-(M:s8W&ts8;corqZBhs5O"gr;6Els8;cls8VHDn`TrPnGX=(GZpA-eG]FDs8N&up]'s[<0^0gs8DurrVGHk +_o(%&rVulr&HDY&oYQB/F`MM>ASG0T$3Kbu$4'XRBl.<@&c`(rr2p"kj8*Drr3f5s8W#qrVt_CItUfcUSUc`n,N@ar;Z]jrX\o+ +o`+jbrpc4`Q^!MqP*Y'l)O$8LQ]IK'CRkG%qYg'dq>C'fbY +$HAd^Hos7lWeoi.$:PF7Ys +Pa.Q#jcpLDR[0/&Q][D%qt9pfs8N&urr2lprZ_C=r;Zcrs8MVXEMdhCEHOjAqZ$9hrVQWos8:fp +`5U:*rVlcrrVca+hS@%H`5TU-aLZS@G'J<0H:rU1H@^p6P)m&h]#2L`IrTa]H?adcI!L'aGC"gr +O-,:;_Rn"3_N/e^Jq8#hIs$'bI=-?hKSknPR``Xf_8a=as8W&srr)]UnGiFcs8N)srr3Pd7!sJ[ +4JJ]Lr9jU`r;HZorr;rr&HD\.p&Fsbs7):^PELojPaLBo)3U>QNgH2KR/d$Zrq6 +%te2"W56Ek`m)cJ`QH?E`o5#=`XL#2`lQEI]7!=PA":=(a<8a^?UT>qXjGP=c,drG]ZS:D4FW0N +SXGb0PECukPF%X#Q$nmcQBRT#PDhETcd0eS_SsO;bg"ASaN2TF`llp=b/h`G_ns:1aMka_qtC'i$2r)U]>r(1aNha'a9'>q^])+a^oifu3\NKocd +O,[+cg!%IT]?A+9cJ#H\Ae +&-(J8s8W)us8;forqZBhs5O"hr;6Kns8;cns8VcFn`g)RrUKk)pj#,HK4FZNr;?TopAb0lDgI\2 +Pa%<#QBpQr$'pg?S!f5$QLanOrrW)tqt^49s82imrVuilp.M6_5>ZZ>6K\1Cs8N&ts8Domi5NUO +eboCGrr*T2qtTg#LO"#_Chmrf&.SsM!s/N&!<3)r!"'<=EcZ"S$Np\=#5\B;"UGA;!W`<+(.rUi +An2qk#QXo*!I-Hs8)]moBK#:T(`-^!<<&trr<#t +s8NH's8VKSp%RIrs8UOHs8NB%s8VcSn*]9:J,~> +$-f3Bj88fTrVlfps5M2YhT$PnbsF%Gs68"@e]]trr<#ns8W"6G`n&LOcGio +Q2[*3PmNb@Pae)"QBq(9rr33#s8;orrr)fp,6.Q?qu?]pp[q(NVE@OtBKtVWs8W)ur;ZcrrSY]6 +`RXqcrr)orrVm;_^r+(._oC!0G^4R\rd=Zm(4:RYI"$^*OHHod`Q4^"G'eFZH@(0lrd#'"H?t!l +M2db0aMkp4LO/rH+F83SGBeC\H[9s`I=R<7UW(3gbJ1mds8W&srr)]UnGiId)?'U8rVuiqp47[J +G!C2MEfg=us8W#srVuoqrr +%?4b*TtnXe`m)`I`Q63ZaT'6hb/hTDc-!_V8605d^;TT`+O&/5-!P+Re'H@aai1g4dFME8Mi=!i +Q(XJ-OcY]hQ^?s%;3mH2S!f5$N7H8%c,[W@^rFO@c-4>OaNMWE^VJ(:cIXq>TJoGd?nTIEaN;NI +ai;9Bd`/f2]"?M9`lcHEai_rbdbF9_o_ACbo!cS4>]Y;1t_o0R7`Pf[0^qIFm_S"Ckp$h_6cg99*a2l9Cd(R08b5TI" +a2uKGbfe>ZBm#>a7!4u)ESJa`aMQ$9`l@tu'$A53^rOOAairCL*c7(/S;rVnj-:UJQ]%#jRZrh_ +,JMaRaj%ZBd*L*!L5=C1ccjPTrlPhp`Q$'Ebf[r>YB!B,9hJ[!_pZU5aihoK]o#c\7oc=7~> +"973/rr)lsr;ZcrqZ6]rir8uU%fZM.r;6Kns8:F:lMpn_nGX-oqi[7VXS`%is8N&upn:9BT:D75 +rKcK/%[33EOcuDc0(&Q8qu6Tkq#:9m+SYp3q#>huVenqIEGn)3qtU3ko`+sirVGHk_o(%&rVulr +&c_b'oYQB/F`D;/:`0]I#RL\2!s&H(rW5=c#8L'Y?9Ai("UG>>#R:M7!X]2@$jm1@!sTE8FE(h( +)[6TT!!!B:$kE^O"U>DD$O6q@+(]82FFK5!o`+mhrr3)am.gSZrrE#srY,8,qtWj+Jqm8kW2`GY +s8Dupqu?Kks8;os$haProU15HQ^a&%RGjDPPEhYtQ^!f*0erS*r:9d_r;9HOQn.+ks8N#ts8N#s +s8W)ursJZ's6B(Hp>FX(s3goHrs8N%s7,7@m,S+r~> +$.YfLhu!EQrVlfps5*pAP"!p7Fj8S!TA&PEV5r +rg)]3&rr2p%qT>M'StO_d~> +%@Ugu&he@;+AjC9PB.2*M_T0s: +dDj,Jd`/f2]"?M9`lcHEai_rbdbF9_o_\IK\$<3J^:jKZs1M.@^qme"]=>D_hu;rj\$`od]tM1q +^qIFt]Y(kf\$icS]BoRpo;]L-\@];]^V.1l_8*h#]tLtc\?a$Lo_%_8cg99*a2l9Cd(R08b5TI" +a2lEGbK.oU0P,`j=$`LI1=+OpahYd9aNFJ&'$A;5_9's:bK._HBO@!pOcu)pj-;?_O-,ljR$El! ++rf=jcbRQBe(m39L_RGDc-"2PaiM]L`l?0Fbf[r>XDCU#:.f*,_pZX+aU$&0]S0 +"9700rVccrr;ZcrqZ6]rir8rTs8W,s!<2ut"Olo*r;Z9c(]+10/r,QWq"Oddo`+J[5@g.HNLuK$ +Pi7lLEnqu6U:qZ#5FWM)XdEc,JgrVHQjqu?HkrVcTN`P]UDr;HZp +rXf,,q"2FYH$+.@58bCX*#&n]&H*1X&.]&#rC/MSM$/1`A'.Om#(.fLqI.OQVm*E6s" +68UPN-SR/)1Gq'J0.\V)0f(U<.4-KNE-cVZO4 +#25lNg\_$Nrr2lri;W`Us8Dut$3'l#jd<0Qde`q4rY5>2r@EPZ62('Qs7ZKa:ci'NPDYn0P5ggH +QKFG?Ruif(Oc++*o`+pfrVuco"98B!qu6U9qu>8AUn'\SEG];crVZ]mr;ZKjrr;rT`l,gGrVc`p +!<)lr%,Se;_o09TF*;eXG]`n/rH\Efs*=Wg'7YV>]on/2PDYNdPECobN/s-\NW"niOHPI2^V5nl +QB@2qS!TD(OHP`hPEM#gO-#ZmQ,1DY]#)8Us8W&srr)]UnGiCb&cMb/rVu\D@t9/\3+AeDDYa,, +s8Doss8N!"s8;lkr;Qu$=d5)7Q'C6m$^d*;S!&u"NDr2 +$`%KGPeP2ZaNVlJ`SJW8`XU#3aN2TH^nOG)?AQ85n]2fjd+&eWLbdf6e'>nWa#PhZR@',5S!fV. +PECrhPEhL!PnBLIP+@YjCi@PD`4s=-`Q6-CrldFG`lQ?D`PBL1ftKbGS3:(nCKrsQb07]>cbmc@ +ajA&;^V%2/bJq]Ia2uKMgsXsHoCVRrW3*;8['KY>rO*QP\[o;YZEpmsn%lQRd*1"dcHjbYbKeMY +rlPqq`l5m*kOeGg_TU!@e'udnd*0h_rmDY5daHIeb/WrBpZq"gm+9A"aMuBL]tVV2rlG,^0$2>X +aNi+?<.BaZ4^O_1=k1U9^r+17bf\#HaNDZL_SsC5aiMl^N_GP!Q]dDnj-BY.s,8P!Q^F5%:Gk2X +hqHK&c;&&+11\SQbK7fF`l?6IaMudo4~> +"97*/rr2otrqlTorr:sVs8;oss82lrrr3)KnEK`Is76.*rV66X4Hc:$qXs^_oM@_)B9JRER$Ei& +iKXq8SUEkrr;olrZhI>s8Mrqp19Si>XEFODaDV@r;6Ejs8DurrVGHk_o(%&rVulr +&HDY&oYQB/F`VY:EGT?&A8(sR!+u.Bs(3iqC2%6o5&4nJ@W-$lAnGdnD0'`4ChdZtC2%Bo@Q/FQ +FE;&1C1UshBkqX+D#S,\Ci=6-A7&A#F*31bOOWq&rVlfr"6f+Hrr2p6rr;rsq>C9ejZJ9OK0_MZ +W*@mPq>:3hq>Lp#rr<#os8D`d`E/=kOID?kPq\DiP*_K"S +$/MAQf_YUIrVlfps5UC%qZ$TmpVp3tQ]R`%iKY=GRZa#%PF%>n7W;W'dI[.7;NZ-4nc&Rf +r;Q]q!rW#qrr*?(rVulj]XG2QVn`!(df'UPrr2lk_kEZpU#LS~> +$*7lKOhA]Sa3;d,`o"l:`X^)4aNDZH_l?I:>Ca<)`q.7h`l,a=cnUKA.b`4ue]fd#.VP*8Q^jJ/ +RZm`5"-]":Qg'c2OIVMuPa>^;e\T5I^r+42aND`Lb/hQAcc3r=^WP&'O/H2JDJj8LYKb/,_o9pB +`l-!G`jidr]@G*Eb0%cHb0Skkg[Oh+q!7_Jp%S1Xrql`lrV/2FrVcWgp\2Soo(DGKs82Zfqtg3i +rq?0`q"aa\p\"6uoC2AFnGE(VqYL!Zo_8.WrVulqrtG:uc1UK"h9YH>aiDKBb0[i9aiaV(0?VJ[ +c,IcOb/>ZWL5//TMkE_Qd(mB=`QZQKaMu`m(DPOI;9"OlDW\PE1ulR@0G6PES:C +8_LDoe;c\GC/3+%bK@iC`5BR4/6VC.!]Z&7=#g(&rHW!L3@D@<~> +#QNK3p](6mrVQKnrr:sV"oSB"s8;fnrs$j^BiW/d716+=p]1R32_qj--)%[N:-jr;Q`rr@aE-chaOOWq&rVlfr"6f+Hrr2rrrr)a*ogXRcK7enGX. +#j8(]c2.D=r;QWoi;W`Us8N&us8<3#p6r%`a85`)rXf,+s8;YfM3O/f[`EbNP]TXsPl?sIQK=A; +QBRK!Q^:O]qu6Tsrr<#qrr)lsrr +$G(.]K>#7H`luTHrl3m;rl4uZrlPPe_ReX2&KJL6hg@N0o=;uW-`R0/e@ +PEZ!."-]":Qgp@@PPpdHQOfCGOHu/p9r8nF6/Bo_/"No^VqL)tW^qp\b$er;?9^o()51cg99*a2l9Cd(R08b5TK\b5TI! +c-e4iJVT#76Dou7e&]VLaNVlNaMu"1kO9T$R'd`;d9bK%T0E)fP,ABTK~> +#liQ4pAb-lr;QZm!ri6"iVj#Yrr<#rr;HWtdHp2hrr;Qg&-)M+qt*`HS=3\\GCkrZIJ]>ePms.I +Q&gr^=o&'srr)irq#2NL6krVcTN`P]UDr;HZprXAi(q"2FY +H$+7QG]n90FT?L^FoHOrG^4FZG&_P8=t46-F)Z8HH$]I7!."Nh/U;YaDK'T4B-pr.Is,p[Fa84S +F*;_LG'eLWEdi%KFC?@HE-?GTJ/lJMVl~> +#j8([b5D5U?m#&ACL +OdVH#h3Ir'"-Ja3Nr>%FRS\.Rp&G$irqufrrVuj,s8Mrps8;#g[^E?4f_k]rrX/W(s8M\tURdsN +m=5~> +$GUXeJ%inDaNDZHrl3m;s2GSgaN2NHaMu30? +oCqqL\a/n[mHsT7rq$0[+78CZm-dDW(b/_ZE +^V%.oc-abV`lQBJaLd[;779gDQ,:Pga9g#1_R+)Y92B3KJ,~> +&-(>=o)JXfr;Q]nr;Q]qs5!YTr;QZp"4lc)mJd.VrXei(rVlcq7pUe3NL$0#3K]W]PlR!HPQmSE +Le[XBs7H:(pAadbrL7W*HphdkG&B*3b4ko1r;Zfq#lrr2p"kj8*Drr;rrrVR2X4bAH;M1B/2TVl#fpAb$err<#qrr;m$o1b@5P`qMs +iKaA-rKRn\PtlXmQ]ugd`VT9-q#C +$1=df_YsB3r;Q]orrN,tir8rWs8EB*rr)fpq4jml^\@6s&,6,&rql[`>D&&qP+7[oO8G4BQJn)/ +P5^[MS!o1/p\k-frqud0pAadbrgIW&GX?.aF_rm1b5))8rr3B)s8N&si5W^Rec#IGrW)oqrrLul +rknTL!5nQJ&AuT/^lF5b_Sj@1`50:*^qoud(;[Z&_SOL9V2^2Jahtp4a1oF*_Sa:0rl#ej_p5g4 +aMO7#TuXsca2#Oas8W&srr)]UnGi=`rr3Q/ffak +&'K6%G/(r:aNDZH`l5s=b2UMB`Xg&0aNDZH`lGf%>ZbaJ`5_Di&B_u;a3<)m/4=E.Kp/'k3fs6& +s,m;Es-)lo^;;Cn,DqYnK7/alKdQrpZM8Ho>7r[oC)2Aj3R)DaiDKBb0[i9aiaP&s2tA_.E9T: ++`r +#63TRHQrr)j!hraCkrr;Qgrr3E*rqH3_>A\SPNIj`ifp)l(SrT/!O-i3+ +r;?NorV?Hmruq=?nO0UU2-OsCD0'Ya^[qL'rr2rrs8Domi5NUOeboCGrr*6(qtTg#LO"#`FnTuM +G]n=VHZF1@;%EaA@!-[9F`qtTI!U$\F`hkPH$++G@m:b6)-q*#G'.tRH?sgZG'.hHHuaXRDJW`4% +2q'/FE_PNNR[V#rVlfr"6f+Hrr2rrrr)a*l:->[L4Y#-3..T@Qi-[Uqu6Wqqu6Wn#PUoQR?sA)Q +Jn,-QMm'ZN1H.l>[9][q"aabqtU$fq>UEls8N&u#Q4W%l0n37dJj0orX/](rVuoSl0R^%s*t~> +#559r\GQ+%rr2lr!WN&Wrr2rtrX/])rVccmY,&83oC)\Ys8W'*p%\8cCil#%J4H"'QJe#7Nh28t +OcYtspA=jfs8E)urr2`n&,ZCs7'<.==Di7tEc4:Zp&=t#rr)`orr;rT`l,gGrVc`p!<)lr"l@&4 +_o0Ic_A:&+_T'@0aMEpqOM/HI_8O=0_8*k'_Z%F^_Sa4+a2b!G(Bq>1-jqtg?lr;QitrVlfr$iKu'r7eNe\$;Cls3U`Or;Z`a +W1olRYkNY~> +&(Q89Cq[a0b0%fH`l5s=b2UMB`Xg&0aNDZF`lGi1=BAq7`5_Di&B`AGai2KJf%WPqDjZW?8='+7 +s-*JJrKm#=s-*JJrKdhNSrAqnMNKR=bPocdbW*_eaMu6=`Q#p@d*(%e/>8Lf>An\"DJCts_p$$< +b0A,M`5g0A\A,\ie]YtTb/_WHceQt'nFQ8Io_nC\nIb6Zlh1,G^q7JWq=jIKmd0E4rU^'Z&G,Pc +o_eOA^TkNGo^VSDnacYM+S"pflg*O$lL=K>ag/\/nG2bBkKiMHaiDKBb0[i9aiaV(s2H,$b/hZD +bebcKJ;8i0IlDS==Ei<>b08#LaMu6@aNFM+$d-Q1b0DViPaS,+Qf45/QS"JdPEhDmSrts1;)BZR +e'QF_`5^0H`kT7&_p$HNaMl0Cc,dGN90kpB9!Q@Td`;d=`lGit?r0NoHJa5~> +%0,AIkl:V]s8N#qr;Q]qhuQMNc_Y$(I0?PF[ns +5kb-RrW)omrZhI +$2Cs(ZhjV"s8N#ss8W&urSmhRrsJc(rVu]-T<>NfrUBdtpAY*lr;ZT=O*j&L4c5JiQ2R$+PmNqE +O-?0"N^i+;Sr8bhU<:Ko`59@`s8W&srr)]UnGiIds8W,urr3K-qa,"LK7Sf(6V*rO7d^0Sp&=sj#"Fi< +OdV;siKaA+rKR_ZPE:GtWV$#^r;?Nn!V? +&)<+KA\,n*bK@oI`l5p(2-X +Km5pQS!u%Te^2J:a9'B&aSs?^a9fl/eL--1Odh>oiKaD//X;N`Q'Io*Oap,oi8)l,b0A#KdD*cA +_o'I7b08)N`Pp'Gb.2sn5XA1A\AZD8a9p,1a1nKU:J4W;j+%~> +%0,MMkPtS]r;Q]nr;Q]qhu^Hoq#'pco_n:O=+m!(fp)o.Pa@r" +S;p28rVuNh'Dqh0k"i%E:0h"EE,BVO2u`dMq#(0lr/q"2FYH$+1NF`qnN +F`qnNFa!_+&T_u+=V_/F!!cbLGB\4SF`hm)FTli+E+qiXrW"5Q??:71EH$)GH$4@UG&q_LEcPo) +.gZ@Z"?:.2G'\=jeF`e>rr2p"kj8*Drr;rrrVR9'M`urFLPBDOVe`5cHMmO8r;HWps82fqr!2lh +Q(=,,PaUKqrKdDI&!<-CPsO^3o_\R]rVQHbs7uZor;ZcrrsJ](q=*kBm,%dAs3goHrs8Q&s4lf( +m.gU2~> +"SKF'YkS.ss8N#ts8E#sir8lUs8N&s#lj_JS?9*Yqu-*b!;HKm$i0_srqHHbp/cp9P5(=BPiS): +PE_E*NLc,'q#16jrqufqrZ_7>ro?bOQVCagF)Pl70K/q1qu$Kor;Zfrs8Lrr`5L4)rVlcrrVca! +hS@%H`:CeX`5p$9Mi![`NkiKK_8F4._8?)e'#_]/ag7.dO,Ae7`kfX4_o9L/rPJTQ)o]V6aiU-5 +OH5C%b.>C3_85gEs8DrrrVG[Gs8Drss8W)trt,2-N'2lBKnX)HUhHTXHMmU +&)<=P@(=A'b0%fH`l5pGbf%$+]Y2k=`lcHEai_rbdbF9_oCMMPo+1Znp;"8X^qKC9p%%YDnFHMKs7--\&Gl=f +_RR=i\+falo^M8;naZSLs7%?*me?PRc+:O!\_?uKnbD(hm+9A"aMuBL]tVV2aN2B?`Q64$a:leF +Cb's\L4NctPMVQTPEhE"PEh>k0\"t,cH*lCa2l3H +`l5s?bfn5Nb/hK>bg";@F\+e`8mAmFdDu[ +%0,bUj8]/Wr;Q]nr;Q]qiVjDgrquZkrr<#ss8Ud>jnJfTo)A[h!W2iprs8DtqsjMBO-Q+2PhhT7 +Q]IT%Nf[/8q>UEnrVlikrZ_4=?'MQq_Jr[uF*_ahd.I/6r;?Toq>^ElqV]?1_q"\`s8N!2s82T` +_M]+Y+H$XUUEcQ;GH$OXWEb7`W!sSo/'k;<,EccDD +G^4OYH$OXXGB%V80Fn6g!sUJqH[9aZN7@M"rVlfr"6f+Hrr2rrrqu[*nO%b^Jp`<&H:2M18D=(@ +r;HWps82fqr!39!6^!LrP`Xjh".#=?QMm'ZQ^*f&