Some minor bug fixes, new test utilities and started support for other
authorWerner Koch <wk@gnupg.org>
Tue, 27 Jan 2004 16:40:42 +0000 (16:40 +0000)
committerWerner Koch <wk@gnupg.org>
Tue, 27 Jan 2004 16:40:42 +0000 (16:40 +0000)
smartcard applications.

33 files changed:
ChangeLog
README
TODO
agent/ChangeLog
agent/Makefile.am
common/ChangeLog
common/Makefile.am
common/sexp-parse.h [moved from agent/sexp-parse.h with 100% similarity]
common/util.h
configure.ac
scd/ChangeLog
scd/Makefile.am
scd/apdu.c
scd/apdu.h
scd/app-common.h
scd/app-dinsig.c [new file with mode: 0644]
scd/app-nks.c [new file with mode: 0644]
scd/app-openpgp.c
scd/app.c
scd/card.c
scd/ccid-driver.c
scd/command.c
scd/iso7816.c
scd/iso7816.h
scd/sc-copykeys.c
scd/sc-investigate.c
scd/scdaemon.c
scd/scdaemon.h
scd/tlv.c [new file with mode: 0644]
scd/tlv.h [new file with mode: 0644]
tools/gpgparsemail.c [new file with mode: 0644]
tools/rfc822parse.c [new file with mode: 0644]
tools/rfc822parse.h [new file with mode: 0644]

index 0752eec..84abc4d 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2004-01-24  Werner Koch  <wk@gnupg.org>
+
+       * configure.ac: Now requires libassuan 0.6.3.
+
 2003-12-23  Werner Koch  <wk@gnupg.org>
 
        Released 1.9.3.
diff --git a/README b/README
index 42099d6..84fc896 100644 (file)
--- a/README
+++ b/README
@@ -138,8 +138,8 @@ gpgsm:
 
 --with-key-data
 
-  Displays extra information with the --list-keys commands.  Especiall
-  a line tagged "grp" si printed which tells you the keygrip of a
+  Displays extra information with the --list-keys commands.  Especially
+  a line tagged "grp" is printed which tells you the keygrip of a
   key.  This is string is for example used as the filename of the
   secret key.
 
diff --git a/TODO b/TODO
index 12fd998..621b278 100644 (file)
--- a/TODO
+++ b/TODO
@@ -52,6 +52,7 @@ might want to have an agent context for each service request
 * agent/protect-tool.c
 ** Export and import certificates along with the secret key.
 ** Make it more comfortable; i.e. copy files to the correct place.
+** BUG? --p12-export seems to work only with unprotected keys
 
 * Move pkcs-1 encoding into libgcrypt.
 
index bd009ec..57f9214 100644 (file)
@@ -1,3 +1,7 @@
+2004-01-27  Werner Koch  <wk@gnupg.org>
+
+       * sexp-parse.h: Moved to ../common.
+
 2004-01-24  Werner Koch  <wk@gnupg.org>
 
        * call-scd.c (atfork_cb): New.
        * protect.c (agent_get_shadow_info): New.
 
        * protect.c (snext,sskip,smatch): Moved to
-       * sexp-parse.h: new file.
+       * sexp-parse.h: New file.
        * divert-scd.c: New.
        
 2002-02-27  Werner Koch  <wk@gnupg.org>
index 400aa2f..65af033 100644 (file)
@@ -41,8 +41,7 @@ gpg_agent_SOURCES = \
        trustlist.c \
        divert-scd.c \
        call-scd.c \
-       learncard.c \
-       sexp-parse.h
+       learncard.c
 
 
 gpg_agent_LDADD = ../jnlib/libjnlib.a ../common/libcommon.a \
index 1b454fa..8e5c615 100644 (file)
@@ -1,3 +1,9 @@
+2004-01-27  Werner Koch  <wk@gnupg.org>
+
+       * sexp-parse.h: New; moved from../agent.
+
+       * util.h (xtoi_4): New.
+
 2003-12-23  Werner Koch  <wk@gnupg.org>
 
        * maperror.c (map_assuan_err): Prepared for a new error code.
index 79dedca..770ed12 100644 (file)
@@ -29,6 +29,7 @@ AM_CPPFLAGS =  $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS)
 libcommon_a_SOURCES = \
        util.h i18n.h \
        errors.h \
+       sexp-parse.h \
        maperror.c \
        sysutils.c sysutils.h \
        gettime.c \
similarity index 100%
rename from agent/sexp-parse.h
rename to common/sexp-parse.h
index 80a1d01..7e134e8 100644 (file)
@@ -134,13 +134,14 @@ int asprintf (char **result, const char *format, ...) JNLIB_GCC_A_PRINTF(2,3);
      \v, but works for the purposes used here. */
 #define ascii_isspace(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t')
 
-/* the atoi macros assume that the buffer has only valid digits */
+/* 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))
+#define xtoi_4(p)   ((xtoi_2(p) * 256) + xtoi_2((p)+2))
 
 
 
index b0c8f23..cd0486f 100644 (file)
@@ -31,7 +31,7 @@ AC_INIT(gnupg, 1.9.4-cvs, gnupg-devel@gnupg.org)
 development_version=yes
 NEED_GPG_ERROR_VERSION=0.6
 NEED_LIBGCRYPT_VERSION=1.1.91
-NEED_LIBASSUAN_VERSION=0.6.2
+NEED_LIBASSUAN_VERSION=0.6.3
 NEED_KSBA_VERSION=0.9.1
 NEED_OPENSC_VERSION=0.8.0
 
index 0862d35..3a6a6ae 100644 (file)
@@ -1,3 +1,68 @@
+2004-01-27  Werner Koch  <wk@gnupg.org>
+
+       * command.c (cmd_readcert, cmd_readkey): Work on a copy of LINE.
+
+       * app-common.h (app_ctx_s): Added readcert field.
+       * app.c (app_readcert): New.
+       * tlv.c (parse_ber_header): Added; taken from libksba.
+
+2004-01-26  Werner Koch  <wk@gnupg.org>
+
+       * card.c (map_sc_err): Use SCD as the error source.
+
+       * command.c (open_card): ADD arg NAME to allow requesting a
+       specific application.  Changed all callers.
+       (cmd_serialno): Allow optional argument to select the desired
+       application.
+
+       * app-nks.c: New. 
+
+       * scdaemon.h (opt): Add READER_PORT.
+       * scdaemon.c (main): Set it here.
+       * app.c (app_set_default_reader_port): Removed.
+       (select_application): Add NAME arg and figure out a
+       default serial number from the GDO. Add SLOT arg and remove all
+       reader management.
+       (release_application): New.
+       (app_write_learn_status): Output an APPTYPE status line.
+       * command.c (open_card): Adapt for select_application change.
+       * app-openpgp.c (app_select_openpgp): Removed SN and SNLEN args
+       and set it directly.  Changed all callers.
+
+2004-01-25  Werner Koch  <wk@gnupg.org>
+
+       * iso7816.c (iso7816_select_application): P1 kludge for OpenPGP
+       card.
+       * app-openpgp.c (find_tlv): Factor out this function to ..
+       * tlv.c, tlv.h: .. new.
+
+       * scdaemon.h: Introduced app_t and ctrl_t as the new types for APP
+       and CTRL.
+
+2004-01-21  Werner Koch  <wk@gnupg.org>
+
+       * apdu.c (apdu_send_le): Treat SW_EOF_REACHED as a warning.
+
+2004-01-20  Werner Koch  <wk@gnupg.org>
+
+       * iso7816.c (iso7816_read_binary): New.
+       (iso7816_select_file): New.
+       (iso7816_list_directory): New.
+
+       * sc-investigate.c: Add option -i.
+       (select_app, read_line, interactive_shell): New.
+
+2004-01-16  Werner Koch  <wk@gnupg.org>
+
+       * apdu.h: Add SW_FILE_NOT_FOUND.
+       * iso7816.c (map_sw): Map it to GPG_ERR_ENOENT.
+       * iso7816.c (iso7816_select_file): New.
+
+       * app-dinsig.c: New file w/o any real code yet.
+       * Makefile.am (scdaemon_SOURCES,sc_investigate_SOURCES): Add file.
+
+       * sc-investigate.c: Add option --disable-ccid.
+
 2003-12-19  Werner Koch  <wk@gnupg.org>
 
        * apdu.c (apdu_send_le): Send a get_response with the indicated
index a2ecd3a..c8bf3d0 100644 (file)
@@ -26,6 +26,8 @@ bin_PROGRAMS = scdaemon sc-investigate sc-copykeys
 AM_CPPFLAGS = -I$(top_srcdir)/common $(OPENSC_CFLAGS) $(LIBGCRYPT_CFLAGS) \
              $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS)
 
+card_apps = app-openpgp.c app-nks.c app-dinsig.c
+
 scdaemon_SOURCES = \
        scdaemon.c scdaemon.h \
        command.c card.c \
@@ -34,8 +36,9 @@ scdaemon_SOURCES = \
        apdu.c apdu.h \
        ccid-driver.c ccid-driver.h \
        iso7816.c iso7816.h \
-       app.c app-common.h \
-       app-openpgp.c
+       tlv.c tlv.h \
+       app.c app-common.h $(card_apps)
+
 
 scdaemon_LDADD = ../jnlib/libjnlib.a ../common/libcommon.a \
          $(OPENSC_LIBS) $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBASSUAN_LIBS) \
@@ -46,9 +49,9 @@ sc_investigate_SOURCES = \
        apdu.c apdu.h \
        ccid-driver.c ccid-driver.h \
        iso7816.c iso7816.h \
-       app.c app-common.h \
-       app-openpgp.c \
-       atr.c atr.h 
+       tlv.c tlv.h \
+       atr.c atr.h \
+       app.c app-common.h $(card_apps) 
 
 sc_investigate_LDADD = \
        ../jnlib/libjnlib.a ../common/libcommon.a \
@@ -61,17 +64,12 @@ sc_copykeys_SOURCES = \
        apdu.c apdu.h \
        ccid-driver.c ccid-driver.h \
        iso7816.c iso7816.h \
-       app.c app-common.h \
-       app-openpgp.c \
-       atr.c atr.h 
+       tlv.c tlv.h \
+       atr.c atr.h \
+       app.c app-common.h $(card_apps)
 
 sc_copykeys_LDADD = \
        ../jnlib/libjnlib.a ../common/libcommon.a \
        ../common/libsimple-pwquery.a \
        $(OPENSC_LIBS) $(LIBGCRYPT_LIBS) $(LIBUSB_LIBS) \
         -lgpg-error @INTLLIBS@ -ldl
-
-
-
-
-
index 02038b6..e5295f5 100644 (file)
@@ -1168,7 +1168,7 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1,
         log_printhex ("     dump: ", result, resultlen);
     }
 
-  if (sw == SW_SUCCESS)
+  if (sw == SW_SUCCESS || sw == SW_EOF_REACHED)
     {
       if (retbuf)
         {
index 21e2b98..fd7634f 100644 (file)
 enum {
   SW_MORE_DATA      = 0x6100, /* Note: that the low byte must be
                                  masked of.*/
+  SW_EOF_REACHED    = 0x6282,
   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_NOT_SUPPORTED  = 0x6a81,
+  SW_FILE_NOT_FOUND = 0x6a82,
+  SW_RECORD_NOT_FOUND = 0x6a83,
   SW_REF_NOT_FOUND  = 0x6a88,
   SW_BAD_P0_P1      = 0x6b00,
   SW_INS_NOT_SUP    = 0x6d00,
index de1e02c..cda1770 100644 (file)
@@ -29,43 +29,46 @@ struct app_ctx_s {
   int slot;         /* Used reader. */
   unsigned char *serialno; /* Serialnumber in raw form, allocated. */
   size_t serialnolen;      /* Length in octets of serialnumber. */
+  const char *apptype;
   unsigned int card_version;
   int did_chv1;
   int force_chv1;   /* True if the card does not cache CHV1. */
   int did_chv2;
   int did_chv3;
   struct {
-    int (*learn_status) (APP app, CTRL ctrl);
-    int (*getattr) (APP app, CTRL ctrl, const char *name);
-    int (*setattr) (APP app, const char *name,
+    int (*learn_status) (app_t app, ctrl_t ctrl);
+    int (*readcert) (app_t app, const char *certid,
+                     unsigned char **cert, size_t *certlen);
+    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 **),
                     void *pincb_arg,
                     const unsigned char *value, size_t valuelen);
-    int (*sign) (APP app,
+    int (*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 );
-    int (*auth) (APP app, const char *keyidstr,
+    int (*auth) (app_t 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 (*decipher) (app_t 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,
+    int (*genkey) (app_t app, ctrl_t ctrl,
                    const char *keynostr, unsigned int flags,
                    int (*pincb)(void*, const char *, char **),
                    void *pincb_arg);
-    int (*change_pin) (APP app, CTRL ctrl,
+    int (*change_pin) (app_t app, ctrl_t ctrl,
                        const char *chvnostr, int reset_mode,
                        int (*pincb)(void*, const char *, char **),
                        void *pincb_arg);
-    int (*check_pin) (APP app, const char *keyidstr,
+    int (*check_pin) (app_t app, const char *keyidstr,
                       int (pincb)(void*, const char *, char **),
                       void *pincb_arg);
   } fnc;
@@ -74,66 +77,77 @@ struct app_ctx_s {
 };
 
 #if GNUPG_MAJOR_VERSION == 1
-int app_select_openpgp (APP app, unsigned char **sn, size_t *snlen);
-int app_get_serial_and_stamp (APP app, char **serial, time_t *stamp);
+int app_select_openpgp (app_t app);
+int app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp);
 #else
 /*-- app.c --*/
-void app_set_default_reader_port (const char *portstr);
-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_getattr (APP app, CTRL ctrl, const char *name);
-int app_setattr (APP app, const char *name,
+app_t select_application (ctrl_t ctrl, int slot, const char *name);
+void release_application (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,
+                  unsigned char **cert, size_t *certlen);
+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 **),
                  void *pincb_arg,
                  const unsigned char *value, size_t valuelen);
-int app_sign (APP app, const char *keyidstr, int hashalgo,
+int app_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 );
-int app_auth (APP app, const char *keyidstr,
+int app_auth (app_t 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 app_decipher (app_t 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 app_genkey (app_t app, ctrl_t 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 app_get_challenge (app_t app, size_t nbytes, unsigned char *buffer);
+int app_change_pin (app_t app, ctrl_t ctrl,
+                    const char *chvnostr, int reset_mode,
                     int (*pincb)(void*, const char *, char **),
                     void *pincb_arg);
-int app_check_pin (APP app, const char *keyidstr,
+int app_check_pin (app_t app, const char *keyidstr,
                    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_select_openpgp (app_t app);
 
-int app_openpgp_cardinfo (APP app,
+int 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 app, int keyno,
+int 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 **),
                           void *pincb_arg);
-int app_openpgp_readkey (APP app, int keyno,
+int 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);
+
+/*-- app-dinsig.c --*/
+int app_select_dinsig (app_t app);
+
+
 #endif
 
 
diff --git a/scd/app-dinsig.c b/scd/app-dinsig.c
new file mode 100644 (file)
index 0000000..4b5b517
--- /dev/null
@@ -0,0 +1,129 @@
+/* app-dinsig.c - The DINSIG (DIN V 66291-1) card application.
+ *     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
+ */
+
+
+/* The German signature law and its bylaw (SigG and SigV) is currently
+   used with an interface specification described in DIN V 66291-1.
+   The AID to be used is: 'D27600006601'.
+
+   The file IDs for certificates utilize the generic format: 
+        Cxyz
+    C being the hex digit 'C' (12).
+    x being the service indicator:
+         '0' := SigG conform digital signature.
+         '1' := entity authentication.
+         '2' := key encipherment.
+         '3' := data encipherment.
+         '4' := key agreement.
+         other values are reserved for future use.
+    y being the security environment number using '0' for cards
+      not supporting a SE number.
+    z being the certificate type:
+         '0'        := C.CH (base certificate of card holder) or C.ICC.
+         '1' .. '7' := C.CH (business or professional certificate
+                       of card holder.
+         '8' .. 'D' := C.CA (certificate of a CA issue by the Root-CA).
+         'E'        := C.RCA (self certified certificate of the Root-CA).
+         'F'        := reserved.
+   
+   The file IDs used by default are:
+   '1F00'  EF.SSD (security service descriptor). [o,o]
+   '2F02'  EF.GDO (global data objects) [m,m]
+   'A000'  EF.PROT (signature log).  Cyclic file with 20 records of 53 byte.
+           Read and update after user authentication. [o,o]
+   'B000'  EF.PK.RCA.DS (public keys of Root-CA).  Size is 512b or size 
+           of keys. [m (unless a 'C00E' is present),m]
+   'B001'  EF.PK.CA.DS (public keys of CAs).  Size is 512b or size
+           of keys. [o,o]
+   'C00n'  EF.C.CH.DS (digital signature certificate of card holder)
+           with n := 0 .. 7.  Size is 2k or size of cert.  Read and
+           update allowed after user authentication. [m,m]
+   'C00m'  EF.C.CA.DS (digital signature certificate of CA)
+           with m := 8 .. E.  Size is 1k or size of cert.  Read always 
+           allowed, update after user authentication. [o,o]
+   'C100'  EF.C.ICC.AUT (AUT certificate of ICC) [o,m]
+   'C108'  EF.C.CA.AUT (AUT certificate of CA) [o,m]
+   'D000'  EF.DM (display message) [-,m]
+   
+   The letters in brackets indicate optional or mandatory files: The
+   first for card terminals under full control and the second for
+   "business" card terminals.
+
+   FIXME: Needs a lot more explanation.
+
+*/
+
+
+
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <time.h>
+
+#include "scdaemon.h"
+
+#include "iso7816.h"
+#include "app-common.h"
+
+
+
+static int
+do_learn_status (APP app, CTRL ctrl)
+{
+  return 0;
+}
+
+
+
+
+
+/* Select the DINSIG application on the card in SLOT.  This function
+   must be used before any other DINSIG application functions. */
+int
+app_select_dinsig (APP app)
+{
+  static char const aid[] = { 0xD2, 0x76, 0x00, 0x00, 0x66, 0x01 };
+  int slot = app->slot;
+  int rc;
+  
+  rc = iso7816_select_application (slot, aid, sizeof aid);
+  if (!rc)
+    {
+      app->apptype = "DINSIG";
+
+      app->fnc.learn_status = do_learn_status;
+      app->fnc.getattr = NULL;
+      app->fnc.setattr = NULL;
+      app->fnc.genkey = NULL;
+      app->fnc.sign = NULL;
+      app->fnc.auth = NULL;
+      app->fnc.decipher = NULL;
+      app->fnc.change_pin = NULL;
+      app->fnc.check_pin = NULL;
+   }
+
+  return rc;
+}
+
+
diff --git a/scd/app-nks.c b/scd/app-nks.c
new file mode 100644 (file)
index 0000000..0a04f75
--- /dev/null
@@ -0,0 +1,388 @@
+/* app-nks.c - The Telesec NKS 2.0 card application.
+ *     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 <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <time.h>
+
+#include "scdaemon.h"
+
+#include "iso7816.h"
+#include "app-common.h"
+#include "tlv.h"
+
+static struct {
+  int fid;      /* File ID. */
+  int certtype; /* Type of certificate or 0 if it is not a certificate. */
+  int iskeypair; /* If true has the FID of the correspoding certificate. */
+} filelist[] = {
+  { 0x4531, 0,  0xC000 }, 
+  { 0xC000, 101 },
+  { 0x4331, 100 },
+  { 0x4332, 100 },
+  { 0xB000, 110 },
+  { 0x45B1, 0,  0xC200 },
+  { 0xC200, 101 },
+  { 0x43B1, 100 },
+  { 0x43B2, 100 },
+  { 0, 0 }
+};
+
+
+
+/* Given the slot and the File Id FID, return the length of the
+   certificate contained in that file. Returns 0 if the file does not
+   exists or does not contain a certificate. */
+static size_t
+get_length_of_cert (int slot, int fid)
+{
+  gpg_error_t err;
+  unsigned char *buffer;
+  const unsigned char *p;
+  size_t buflen, n;
+  int class, tag, constructed, ndef;
+  size_t objlen, hdrlen;
+
+  err = iso7816_select_file (slot, fid, 0, NULL, NULL);
+  if (err)
+    {
+      log_info ("error selecting FID 0x%04X: %s\n", fid, gpg_strerror (err));
+      return 0;
+    }
+
+  err = iso7816_read_binary (slot, 0, 32, &buffer, &buflen);
+  if (err)
+    {
+      log_info ("error reading certificate from FID 0x%04X: %s\n",
+                 fid, gpg_strerror (err));
+      return 0;
+    }
+  
+  if (!buflen || *buffer == 0xff)
+    {
+      log_info ("no certificate contained in FID 0x%04X\n", fid);
+      xfree (buffer);
+      return 0;
+    }
+
+  p = buffer;
+  n = buflen;
+  err = parse_ber_header (&p, &n, &class, &tag, &constructed,
+                          &ndef, &objlen, &hdrlen);
+  if (err)
+    {
+      log_info ("error parsing certificate in FID 0x%04X: %s\n",
+                fid, gpg_strerror (err));
+      xfree (buffer);
+      return 0;
+    }
+
+  /* All certificates should commence with a SEQUENCE expect fro the
+     special ROOT CA which are enclosed in a SET. */
+  if ( !(class == CLASS_UNIVERSAL &&  constructed
+         && (tag == TAG_SEQUENCE || tag == TAG_SET)))
+    {
+      log_info ("contents of FID 0x%04X does not look like a certificate\n",
+                fid);
+      return 0;
+    }
+  return objlen + hdrlen;
+}
+
+
+
+/* Read the file with FID, assume it contains a public key and return
+   its keygrip in the caller provided 41 byte buffer R_GRIPSTR. */
+static gpg_error_t
+keygripstr_from_pk_file (int slot, int fid, char *r_gripstr)
+{
+  gpg_error_t err;
+  unsigned char grip[20];
+  unsigned char *buffer[2];
+  size_t buflen[2];
+  gcry_sexp_t sexp;
+  int i;
+  
+  err = iso7816_select_file (slot, fid, 0, NULL, NULL);
+  if (err)
+    return err;
+  err = iso7816_read_record (slot, 1, 1, &buffer[0], &buflen[0]);
+  if (err)
+    return err;
+  err = iso7816_read_record (slot, 2, 1, &buffer[1], &buflen[1]);
+  if (err)
+    {
+      xfree (buffer[0]);
+      return err;
+    }
+  
+  for (i=0; i < 2; i++)
+    {
+      /* Check that the value appears like an integer encoded as
+         Simple-TLV.  We don't check the tag because the tests cards I
+         have use 1 for both, the modulus and the exponent - the
+         example in the documentation gives 2 for the exponent. */
+      if (buflen[i] < 3)
+        err = gpg_error (GPG_ERR_TOO_SHORT);
+      else if (buffer[i][1] != buflen[i]-2 )
+        err = gpg_error (GPG_ERR_INV_OBJ);
+    }
+
+  if (!err)
+    err = gcry_sexp_build (&sexp, NULL,
+                           "(public-key (rsa (n %b) (e %b)))",
+                           (int)buflen[0]-2, buffer[0]+2,
+                           (int)buflen[1]-2, buffer[1]+2);
+
+  xfree (buffer[0]);
+  xfree (buffer[1]);
+  if (err)
+    return err;
+
+  if (!gcry_pk_get_keygrip (sexp, grip))
+    {
+      err = gpg_error (GPG_ERR_INTERNAL); /* i.e. RSA not supported by
+                                             libgcrypt. */
+    }
+  else
+    {
+      for (i=0; i < 20; i++)
+        sprintf (r_gripstr+i*2, "%02X", grip[i]);
+    }
+  gcry_sexp_release (sexp);
+  return err;
+}
+
+
+
+static int
+do_learn_status (APP app, CTRL ctrl)
+{
+  gpg_error_t err;
+  char ct_buf[100], id_buf[100];
+  int i;
+
+  /* Output information about all useful objects. */
+  for (i=0; filelist[i].fid; i++)
+    {
+      if (filelist[i].certtype)
+        {
+          size_t len = get_length_of_cert (app->slot, filelist[i].fid);
+
+          if (len)
+            {
+              /* FIXME: We should store the length in the application's
+                 context so that a following readcert does only need to
+                 read that many bytes. */
+              sprintf (ct_buf, "%d", filelist[i].certtype);
+              sprintf (id_buf, "NKS-DF01.%04X", filelist[i].fid);
+              send_status_info (ctrl, "CERTINFO",
+                                ct_buf, strlen (ct_buf), 
+                                id_buf, strlen (id_buf), 
+                                NULL, (size_t)0);
+            }
+        }
+      else if (filelist[i].iskeypair)
+        {
+          char gripstr[40+1];
+
+          err = keygripstr_from_pk_file (app->slot, filelist[i].fid, gripstr);
+          if (err)
+            log_error ("can't get keygrip from FID 0x%04X: %s\n",
+                       filelist[i].fid, gpg_strerror (err));
+          else
+            {
+              sprintf (id_buf, "NKS-DF01.%04X", filelist[i].fid);
+              send_status_info (ctrl, "KEYPAIRINFO",
+                                gripstr, 40, 
+                                id_buf, strlen (id_buf), 
+                                NULL, (size_t)0);
+            }
+        }
+    }
+
+  return 0;
+}
+
+
+
+
+/* Read the certificate with id CERTID (as returned by learn_status in
+   the CERTINFO status lines) and return it in the freshly allocated
+   buffer put into CERT and the length of the certificate put into
+   CERTLEN. */
+static int
+do_readcert (app_t app, const char *certid,
+             unsigned char **cert, size_t *certlen)
+{
+  int i, fid;
+  gpg_error_t err;
+  unsigned char *buffer;
+  const unsigned char *p;
+  size_t buflen, n;
+  int class, tag, constructed, ndef;
+  size_t totobjlen, objlen, hdrlen;
+  int rootca = 0;
+
+  *cert = NULL;
+  *certlen = 0;
+  if (strncmp (certid, "NKS-DF01.", 9) ) 
+    return gpg_error (GPG_ERR_INV_ID);
+  certid += 9;
+  if (!hexdigitp (certid) || !hexdigitp (certid+1)
+      || !hexdigitp (certid+2) || !hexdigitp (certid+3) 
+      || certid[4])
+    return gpg_error (GPG_ERR_INV_ID);
+  fid = xtoi_4 (certid);
+  for (i=0; filelist[i].fid; i++)
+    if ((filelist[i].certtype || filelist[i].iskeypair)
+        && filelist[i].fid == fid)
+      break;
+  if (!filelist[i].fid)
+    return gpg_error (GPG_ERR_NOT_FOUND);
+
+  /* If the requested objects is a plain public key, redirect it to
+     the corresponding certificate.  The whole system is a bit messy
+     becuase we sometime use the key directly or let the caller
+     retrieve the key from the certificate.  The valid point behind
+     that is to support not-yet stored certificates. */
+  if (filelist[i].iskeypair)
+    fid = filelist[i].iskeypair;
+
+
+  /* Read the entire file.  fixme: This could be optimized by first
+     reading the header to figure out how long the certificate
+     actually is. */
+  err = iso7816_select_file (app->slot, fid, 0, NULL, NULL);
+  if (err)
+    {
+      log_error ("error selecting FID 0x%04X: %s\n", fid, gpg_strerror (err));
+      return err;
+    }
+
+  err = iso7816_read_binary (app->slot, 0, 0, &buffer, &buflen);
+  if (err)
+    {
+      log_error ("error reading certificate from FID 0x%04X: %s\n",
+                 fid, gpg_strerror (err));
+      return err;
+    }
+  
+  if (!buflen || *buffer == 0xff)
+    {
+      log_info ("no certificate contained in FID 0x%04X\n", fid);
+      err = gpg_error (GPG_ERR_NOT_FOUND);
+      goto leave;
+    }
+
+  /* Now figure something out about the object. */
+  p = buffer;
+  n = buflen;
+  err = parse_ber_header (&p, &n, &class, &tag, &constructed,
+                          &ndef, &objlen, &hdrlen);
+  if (err)
+    goto leave;
+  if ( class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed )
+    ;
+  else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed )
+    rootca = 1;
+  else
+    return gpg_error (GPG_ERR_INV_OBJ);
+  totobjlen = objlen + hdrlen;
+  assert (totobjlen <= buflen);
+
+  err = parse_ber_header (&p, &n, &class, &tag, &constructed,
+                          &ndef, &objlen, &hdrlen);
+  if (err)
+    goto leave;
+  
+  if (rootca)
+    ;
+  else if (class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed)
+    {
+      const unsigned char *save_p;
+  
+      /* The certificate seems to be contained in a userCertificate
+         container.  Skip this and assume the following sequence is
+         the certificate. */
+      if (n < objlen)
+        {
+          err = gpg_error (GPG_ERR_INV_OBJ);
+          goto leave;
+        }
+      p += objlen;
+      n -= objlen;
+      save_p = p;
+      err = parse_ber_header (&p, &n, &class, &tag, &constructed,
+                              &ndef, &objlen, &hdrlen);
+      if (err) 
+        goto leave;
+      if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) )
+        return gpg_error (GPG_ERR_INV_OBJ);
+      totobjlen = objlen + hdrlen;
+      assert (save_p + totobjlen <= buffer + buflen);
+      memmove (buffer, save_p, totobjlen);
+    }
+  
+  *cert = buffer;
+  buffer = NULL;
+  *certlen = totobjlen;
+
+ leave:
+  xfree (buffer);
+  return err;
+}
+
+
+
+/* Select the NKS 2.0 application on the card in SLOT.  */
+int
+app_select_nks (APP app)
+{
+  static char const aid[] = { 0xD2, 0x76, 0x00, 0x00, 0x03, 0x01, 0x02 };
+  int slot = app->slot;
+  int rc;
+  
+  rc = iso7816_select_application (slot, aid, sizeof aid);
+  if (!rc)
+    {
+      app->apptype = "NKS";
+
+      app->fnc.learn_status = do_learn_status;
+      app->fnc.readcert = do_readcert;
+      app->fnc.getattr = NULL;
+      app->fnc.setattr = NULL;
+      app->fnc.genkey = NULL;
+      app->fnc.sign = NULL;
+      app->fnc.auth = NULL;
+      app->fnc.decipher = NULL;
+      app->fnc.change_pin = NULL;
+      app->fnc.check_pin = NULL;
+   }
+
+  return rc;
+}
+
+
index 8f9a303..75e3e29 100644 (file)
@@ -1,5 +1,5 @@
 /* app-openpgp.c - The OpenPGP card application.
- *     Copyright (C) 2003 Free Software Foundation, Inc.
+ *     Copyright (C) 2003, 2004 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
@@ -42,7 +42,7 @@
 
 #include "iso7816.h"
 #include "app-common.h"
-
+#include "tlv.h"
 
 
 static struct {
@@ -80,94 +80,6 @@ static unsigned long convert_sig_counter_value (const unsigned char *value,
 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
@@ -197,7 +109,7 @@ get_one_do (int slot, int tag, unsigned char **result, size_t *nbytes)
         {
           const unsigned char *s;
 
-          s = find_tlv (buffer, buflen, tag, &valuelen, 0);
+          s = find_tlv (buffer, buflen, tag, &valuelen);
           if (!s)
             value = NULL; /* not found */
           else if (valuelen > buflen - (s - buffer))
@@ -271,7 +183,7 @@ dump_all_do (int slot)
                   if (j==i || data_objects[i].tag != data_objects[j].get_from)
                     continue;
                   value = find_tlv (buffer, buflen,
-                                    data_objects[j].tag, &valuelen, 0);
+                                    data_objects[j].tag, &valuelen);
                   if (!value)
                     ; /* not found */
                   else if (valuelen > buflen - (value - buffer))
@@ -443,7 +355,7 @@ do_getattr (APP app, CTRL ctrl, const char *name)
     {
       /* The serial number is very special.  We could have used the
          AID DO to retrieve it, but we have it already in the app
-         context and the stanmp argument is required anyway which we
+         context and the stamp argument is required anyway which we
          can't by other means. The AID DO is available anyway but not
          hex formatted. */
       char *serial;
@@ -772,7 +684,7 @@ do_genkey (APP app, CTRL ctrl,  const char *keynostr, unsigned int flags,
       log_error ("error reading application data\n");
       return gpg_error (GPG_ERR_GENERAL);
     }
-  fpr = find_tlv (buffer, buflen, 0x00C5, &n, 0);
+  fpr = find_tlv (buffer, buflen, 0x00C5, &n);
   if (!fpr || n != 60)
     {
       rc = gpg_error (GPG_ERR_GENERAL);
@@ -820,7 +732,7 @@ do_genkey (APP app, CTRL ctrl,  const char *keynostr, unsigned int flags,
     }
   log_info ("key generation completed (%d seconds)\n",
             (int)(time (NULL) - start_at));
-  keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen, 0);
+  keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen);
   if (!keydata)
     {
       rc = gpg_error (GPG_ERR_CARD);
@@ -828,7 +740,7 @@ do_genkey (APP app, CTRL ctrl,  const char *keynostr, unsigned int flags,
       goto leave;
     }
  
-  m = find_tlv (keydata, keydatalen, 0x0081, &mlen, 0);
+  m = find_tlv (keydata, keydatalen, 0x0081, &mlen);
   if (!m)
     {
       rc = gpg_error (GPG_ERR_CARD);
@@ -838,7 +750,7 @@ do_genkey (APP app, CTRL ctrl,  const char *keynostr, unsigned int flags,
 /*    log_printhex ("RSA n:", m, mlen); */
   send_key_data (ctrl, "n", m, mlen);
 
-  e = find_tlv (keydata, keydatalen, 0x0082, &elen, 0);
+  e = find_tlv (keydata, keydatalen, 0x0082, &elen);
   if (!e)
     {
       rc = gpg_error (GPG_ERR_CARD);
@@ -913,7 +825,7 @@ compare_fingerprint (APP app, int keyno, unsigned char *sha1fpr)
       log_error ("error reading application data\n");
       return gpg_error (GPG_ERR_GENERAL);
     }
-  fpr = find_tlv (buffer, buflen, 0x00C5, &n, 0);
+  fpr = find_tlv (buffer, buflen, 0x00C5, &n);
   if (!fpr || n != 60)
     {
       xfree (buffer);
@@ -1268,7 +1180,7 @@ do_check_pin (APP 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
-app_select_openpgp (APP app, unsigned char **sn, size_t *snlen)
+app_select_openpgp (APP app)
 {
   static char const aid[] = { 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01 };
   int slot = app->slot;
@@ -1280,10 +1192,17 @@ app_select_openpgp (APP app, unsigned char **sn, size_t *snlen)
   rc = iso7816_select_application (slot, aid, sizeof aid);
   if (!rc)
     {
+      app->apptype = "OPENPGP";
+
       app->did_chv1 = 0;
       app->did_chv2 = 0;
       app->did_chv3 = 0;
 
+      /* The OpenPGP card returns the serial number as part of the
+         AID; because we prefer to use OpenPGP serial numbers, we
+         repalce a possibly already set one from a EF.GDO with this
+         one.  Note, that for current OpenPGP cards, no EF.GDO exists
+         and thus it won't matter at all. */
       rc = iso7816_get_data (slot, 0x004F, &buffer, &buflen);
       if (rc)
         goto leave;
@@ -1293,15 +1212,12 @@ app_select_openpgp (APP app, unsigned char **sn, size_t *snlen)
           log_printhex ("", buffer, buflen);
         }
 
-      if (sn)
-        {
-          *sn = buffer;
-          *snlen = buflen;
-          app->card_version = buffer[6] << 8;
-          app->card_version |= buffer[7];
-        }
-      else
-        xfree (buffer);
+      app->card_version = buffer[6] << 8;
+      app->card_version |= buffer[7];
+      xfree (app->serialno);
+      app->serialno = buffer;
+      app->serialnolen = buflen;
+      buffer = NULL;
 
       relptr = get_one_do (app->slot, 0x00C4, &buffer, &buflen);
       if (!relptr)
@@ -1316,6 +1232,7 @@ app_select_openpgp (APP app, unsigned char **sn, size_t *snlen)
         dump_all_do (slot);
 
       app->fnc.learn_status = do_learn_status;
+      app->fnc.readcert = NULL;
       app->fnc.getattr = do_getattr;
       app->fnc.setattr = do_setattr;
       app->fnc.genkey = do_genkey;
@@ -1498,7 +1415,7 @@ app_openpgp_readkey (APP app, int keyno, unsigned char **m, size_t *mlen,
       goto leave;
     }
 
-  keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen, 0);
+  keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen);
   if (!keydata)
     {
       log_error ("response does not contain the public key data\n");
@@ -1506,7 +1423,7 @@ app_openpgp_readkey (APP app, int keyno, unsigned char **m, size_t *mlen,
       goto leave;
     }
  
-  a = find_tlv (keydata, keydatalen, 0x0081, &alen, 0);
+  a = find_tlv (keydata, keydatalen, 0x0081, &alen);
   if (!a)
     {
       log_error ("response does not contain the RSA modulus\n");
@@ -1517,7 +1434,7 @@ app_openpgp_readkey (APP app, int keyno, unsigned char **m, size_t *mlen,
   *m = xmalloc (alen);
   memcpy (*m, a, alen);
   
-  a = find_tlv (keydata, keydatalen, 0x0082, &alen, 0);
+  a = find_tlv (keydata, keydatalen, 0x0082, &alen);
   if (!e)
     {
       log_error ("response does not contain the RSA public exponent\n");
index 1f142ea..6ac1827 100644 (file)
--- a/scd/app.c
+++ b/scd/app.c
 #include "app-common.h"
 #include "apdu.h"
 #include "iso7816.h"
-#include "dynload.h"
+#include "tlv.h"
 
-static char *default_reader_port;
 
-void
-app_set_default_reader_port (const char *portstr)
-{
-  xfree (default_reader_port);
-  default_reader_port = portstr? xstrdup (portstr): NULL;
-}
-
-
-/* The select the best fitting application and return a context.
-   Returns NULL if no application was found or no card is present. */
+/* If called with NAME as NULL, select the best fitting application
+   and return a context; otherwise select the application with NAME
+   and return a context.  SLOT identifies the reader device. Returns
+   NULL if no application was found or no card is present. */
 APP
-select_application (void)
+select_application (ctrl_t ctrl, int slot, const char *name)
 {
-  int slot;
   int rc;
   APP app;
-
-  slot = apdu_open_reader (default_reader_port);
-  if (slot == -1)
-    {
-      log_error ("card reader not available\n");
-      return NULL;
-    }
+  unsigned char *result = NULL;
+  size_t resultlen;
 
   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);
+
+  /* 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)
+    {
+      size_t n;
+      const unsigned char *p;
+
+      p = find_tlv (result, resultlen, 0x5A, &n);
+      if (p && n && n >= (resultlen - (p - result)))
+        {
+          /* The GDO file is pretty short, thus we simply reuse it for
+             storing the serial number. */
+          memmove (result, p, n);
+          app->serialno = result;
+          app->serialnolen = n;
+        }
+      else
+        xfree (result);
+      result = NULL;
+    }
+
+
+  rc = gpg_error (GPG_ERR_NOT_FOUND);
+
+  if (!name || !strcmp (name, "openpgp"))
+    rc = app_select_openpgp (app);
+  if (rc && (!name || !strcmp (name, "nks")))
+    rc = app_select_nks (app);
+  if (rc && (!name || !strcmp (name, "dinsig")))
+    rc = app_select_dinsig (app);
+  if (rc && name)
+    rc = gpg_error (GPG_ERR_NOT_SUPPORTED);
+
   if (rc)
     {
-/*        apdu_close_reader (slot); */
-      log_info ("selecting openpgp failed: %s\n", gpg_strerror (rc));
+      if (name)
+        log_info ("can't select application `%s': %s\n",
+                  name, gpg_strerror (rc));
+      else
+        log_info ("no supported card application found: %s\n",
+                  gpg_strerror (rc));
       xfree (app);
       return NULL;
     }
@@ -81,23 +110,36 @@ select_application (void)
 }
 
 
+void
+release_application (app_t app)
+{
+  if (!app)
+    return;
+
+  xfree (app->serialno);
+  xfree (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. */
+   free SERIAL unless the function returns an error.  If STAMP is not
+   of interest, NULL may be passed. */
 int 
-app_get_serial_and_stamp (APP app, char **serial, time_t *stamp)
+app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp)
 {
   unsigned char *buf, *p;
   int i;
 
-  if (!app || !serial || !stamp)
+  if (!app || !serial)
     return gpg_error (GPG_ERR_INV_VALUE);
 
   *serial = NULL;
-  *stamp = 0; /* not available */
+  if (stamp)
+    *stamp = 0; /* not available */
 
   buf = xtrymalloc (app->serialnolen * 2 + 1);
   if (!buf)
@@ -121,10 +163,34 @@ app_write_learn_status (APP app, CTRL ctrl)
     return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
   if (!app->fnc.learn_status)
     return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+
+  if (app->apptype)
+    send_status_info (ctrl, "APPTYPE",
+                      app->apptype, strlen (app->apptype), NULL, 0);
+
   return app->fnc.learn_status (app, ctrl);
 }
 
 
+/* Read the certificate with id CERTID (as returned by learn_status in
+   the CERTINFO status lines) and return it in the freshly allocated
+   buffer put into CERT and the length of the certificate put into
+   CERTLEN. */
+int
+app_readcert (app_t app, const char *certid,
+              unsigned char **cert, size_t *certlen)
+{
+  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);
+}
+
+
 /* Perform a GETATTR operation.  */
 int 
 app_getattr (APP app, CTRL ctrl, const char *name)
@@ -317,8 +383,3 @@ app_check_pin (APP app, const char *keyidstr,
   return rc;
 }
 
-
-
-
-
-
index 9514907..53c89f3 100644 (file)
@@ -53,7 +53,10 @@ map_sc_err (int rc)
 #endif
     default: e = GPG_ERR_CARD; break;
     }
-  return gpg_err_make (GPG_ERR_SOURCE_UNKNOWN, e);
+  /* It does not make much sense to further distingusih the error
+     source between OpenSC and SCD.  Thus we use SCD as source
+     here. */
+  return gpg_err_make (GPG_ERR_SOURCE_SCD, e);
 }
 
 /* Get the keygrip from CERT, return 0 on success */
@@ -462,6 +465,7 @@ card_enum_keypairs (CARD card, int idx,
       100 := Regular X.509 cert
       101 := Trusted X.509 cert
       102 := Useful X.509 cert
+      110 := Root CA cert (DINSIG)
  */
 int
 card_enum_certs (CARD card, int idx, char **certid, int *certtype)
index 936672c..f910722 100644 (file)
 #  include "scdaemon.h"
 # endif
 
+/* Disable all debgging output for now. */
+#undef DBG_CARD_IO
+#define DBG_CARD_IO 0
+
+
 # define DEBUGOUT(t)         do { if (DBG_CARD_IO) \
                                   log_debug (DRVNAME t); } while (0)
 # define DEBUGOUT_1(t,a)     do { if (DBG_CARD_IO) \
@@ -944,7 +949,9 @@ ccid_transceive (ccid_driver_t handle,
             {
               if (n > maxresplen)
                 {
-                  DEBUGOUT ("provided buffer too short for received data\n");
+                  DEBUGOUT_2 ("provided buffer too short for received data "
+                              "(%u/%u)\n",
+                              (unsigned int)n, (unsigned int)maxresplen);
                   return -1;
                 }
               
index bc3132a..9e571f2 100644 (file)
@@ -1,5 +1,5 @@
 /* command.c - SCdaemon 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.
  *
@@ -31,6 +31,7 @@
 #include "scdaemon.h"
 #include <ksba.h>
 #include "app-common.h"
+#include "apdu.h" /* Required for apdu_*_reader (). */
 
 /* maximum length aloowed as a PIN; used for INQUIRE NEEDPIN */
 #define MAXLEN_PIN 100
@@ -90,17 +91,34 @@ option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value)
    function returns an Assuan error, so don't map the error a second
    time */
 static AssuanError
-open_card (CTRL ctrl)
+open_card (CTRL ctrl, const char *apptype)
 {
+  int slot;
+
   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 ();
+  slot = apdu_open_reader (opt.reader_port);
+  if (slot != -1)
+    {
+      ctrl->app_ctx = select_application (ctrl, slot, apptype);
+      if (!ctrl->app_ctx)
+        apdu_close_reader (slot);
+    }
   if (!ctrl->app_ctx)
     { /* No application found - fall back to old mode. */
-      int rc = card_open (&ctrl->card_ctx);
+      /* Note that we should rework the old code to use the
+         application paradigma too. */
+      int rc;
+      
+      /* If an APPTYPE was requested and it is not pkcs#15, we return
+         an error here. */
+      if (apptype && !(!strcmp (apptype, "P15") || !strcmp (apptype, "p15")))
+        rc = gpg_error (GPG_ERR_NOT_SUPPORTED);
+      else 
+        rc = card_open (&ctrl->card_ctx);
       if (rc)
         return map_to_assuan_status (rc);
     }
@@ -143,11 +161,17 @@ percent_plus_unescape (unsigned char *string)
 
 
 
-/* SERIALNO 
+/* SERIALNO [APPTYPE] 
 
    Return the serial number of the card using a status reponse.  This
    functon should be used to check for the presence of a card.
 
+   If APPTYPE is given, an application of that type is selected and an
+   error is returned if the application is not supported or available.
+   The default is to auto-select the application using a hardwired
+   preference system.  Note, that a future extension to this function
+   may allow to specify a list and order of applications to try.
+
    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.
@@ -165,7 +189,7 @@ cmd_serialno (ASSUAN_CONTEXT ctx, char *line)
   char *serial;
   time_t stamp;
 
-  if ((rc = open_card (ctrl)))
+  if ((rc = open_card (ctrl, *line? line:NULL)))
     return rc;
 
   if (ctrl->app_ctx)
@@ -223,6 +247,7 @@ cmd_serialno (ASSUAN_CONTEXT ctx, char *line)
       100 := Regular X.509 cert
       101 := Trusted X.509 cert
       102 := Useful X.509 cert
+      110 := Root CA cert (DINSIG)
 
    For certain cards, more information will be returned:
 
@@ -240,7 +265,7 @@ cmd_serialno (ASSUAN_CONTEXT ctx, char *line)
      S DISP-NAME <name_of_card_holder>
 
    The name of the card holder as stored on the card; percent
-   aescaping takes place, spaces are encoded as '+'
+   escaping takes place, spaces are encoded as '+'
 
      S PUBKEY-URL <url>
 
@@ -254,7 +279,7 @@ cmd_learn (ASSUAN_CONTEXT ctx, char *line)
   int rc = 0;
   int idx;
 
-  if ((rc = open_card (ctrl)))
+  if ((rc = open_card (ctrl, NULL)))
     return rc;
 
   /* Unless the force option is used we try a shortcut by identifying
@@ -305,10 +330,15 @@ cmd_learn (ASSUAN_CONTEXT ctx, char *line)
     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++)
+  /* If we are using the modern application paradigma, let the
+     application print out its collection of useful status
+     information. */
+  if (!rc && ctrl->app_ctx)
+    rc = app_write_learn_status (ctrl->app_ctx, ctrl);
+
+  /* Return information about the certificates.  FIXME: Move this into
+     an app-p15.c*/
+  for (idx=0; !rc && !ctrl->app_ctx; idx++)
     {
       char *certid;
       int certtype;
@@ -333,11 +363,9 @@ cmd_learn (ASSUAN_CONTEXT ctx, char *line)
   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++)
+  /* Return information about the keys. FIXME: Move this into an
+     app-p15.c */
+  for (idx=0; !rc && !ctrl->app_ctx; idx++)
     {
       unsigned char keygrip[20];
       char *keyid;
@@ -346,7 +374,7 @@ cmd_learn (ASSUAN_CONTEXT ctx, char *line)
       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
+          /* 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
@@ -383,10 +411,6 @@ cmd_learn (ASSUAN_CONTEXT ctx, char *line)
   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);
 }
 
@@ -403,17 +427,24 @@ cmd_readcert (ASSUAN_CONTEXT ctx, char *line)
   unsigned char *cert;
   size_t ncert;
 
-  if ((rc = open_card (ctrl)))
+  if ((rc = open_card (ctrl, NULL)))
     return rc;
 
+  line = xstrdup (line); /* Need a copy of the line. */
   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));
+      rc = app_readcert (ctrl->app_ctx, line, &cert, &ncert);
+      if (rc)
+        log_error ("app_readcert failed: %s\n", gpg_strerror (rc));
+    }
+  else
+    {
+      rc = card_read_cert (ctrl->card_ctx, line, &cert, &ncert);
+      if (rc)
+        log_error ("card_read_cert failed: %s\n", gpg_strerror (rc));
     }
+  xfree (line);
+  line = NULL;
   if (!rc)
     {
       rc = assuan_send_data (ctx, cert, ncert);
@@ -440,18 +471,26 @@ cmd_readkey (ASSUAN_CONTEXT ctx, char *line)
   ksba_cert_t kc = NULL;
   ksba_sexp_t p;
 
-  if ((rc = open_card (ctrl)))
+  if ((rc = open_card (ctrl, NULL)))
     return rc;
 
+  line = xstrdup (line); /* Need a copy of the line. */
   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;
+      rc = app_readcert (ctrl->app_ctx, line, &cert, &ncert);
+      if (rc)
+        log_error ("app_readcert failed: %s\n", gpg_strerror (rc));
+    }
+  else
+    {
+      rc = card_read_cert (ctrl->card_ctx, line, &cert, &ncert);
+      if (rc)
+        log_error ("card_read_cert failed: %s\n", gpg_strerror (rc));
     }
+  xfree (line);
+  line = NULL;
+  if (rc)
+    goto leave;
       
   rc = ksba_cert_new (&kc);
   if (rc)
@@ -569,7 +608,7 @@ cmd_pksign (ASSUAN_CONTEXT ctx, char *line)
   size_t outdatalen;
   char *keyidstr;
 
-  if ((rc = open_card (ctrl)))
+  if ((rc = open_card (ctrl, NULL)))
     return rc;
 
   /* We have to use a copy of the key ID because the function may use
@@ -619,7 +658,7 @@ cmd_pkauth (ASSUAN_CONTEXT ctx, char *line)
   size_t outdatalen;
   char *keyidstr;
 
-  if ((rc = open_card (ctrl)))
+  if ((rc = open_card (ctrl, NULL)))
     return rc;
 
   if (!ctrl->app_ctx)
@@ -665,7 +704,7 @@ cmd_pkdecrypt (ASSUAN_CONTEXT ctx, char *line)
   size_t outdatalen;
   char *keyidstr;
 
-  if ((rc = open_card (ctrl)))
+  if ((rc = open_card (ctrl, NULL)))
     return rc;
 
   keyidstr = xtrystrdup (line);
@@ -718,7 +757,7 @@ cmd_getattr (ASSUAN_CONTEXT ctx, char *line)
   int rc;
   char *keyword;
 
-  if ((rc = open_card (ctrl)))
+  if ((rc = open_card (ctrl, NULL)))
     return rc;
 
   keyword = line;
@@ -757,7 +796,7 @@ cmd_setattr (ASSUAN_CONTEXT ctx, char *orig_line)
   size_t nbytes;
   char *line, *linebuf;
 
-  if ((rc = open_card (ctrl)))
+  if ((rc = open_card (ctrl, NULL)))
     return rc;
 
   /* We need to use a copy of LINE, because PIN_CB uses the same
@@ -823,7 +862,7 @@ cmd_genkey (ASSUAN_CONTEXT ctx, char *line)
     line++;
   *line = 0;
 
-  if ((rc = open_card (ctrl)))
+  if ((rc = open_card (ctrl, NULL)))
     return rc;
 
   if (!ctrl->app_ctx)
@@ -854,7 +893,7 @@ cmd_random (ASSUAN_CONTEXT ctx, char *line)
     return set_error (Parameter_Error, "number of requested bytes missing");
   nbytes = strtoul (line, NULL, 0);
 
-  if ((rc = open_card (ctrl)))
+  if ((rc = open_card (ctrl, NULL)))
     return rc;
 
   if (!ctrl->app_ctx)
@@ -904,7 +943,7 @@ cmd_passwd (ASSUAN_CONTEXT ctx, char *line)
     line++;
   *line = 0;
 
-  if ((rc = open_card (ctrl)))
+  if ((rc = open_card (ctrl, NULL)))
     return rc;
 
   if (!ctrl->app_ctx)
@@ -931,7 +970,7 @@ cmd_checkpin (ASSUAN_CONTEXT ctx, char *line)
   int rc;
   char *keyidstr;
 
-  if ((rc = open_card (ctrl)))
+  if ((rc = open_card (ctrl, NULL)))
     return rc;
 
   if (!ctrl->app_ctx)
index f4aa18c..9a15ce9 100644 (file)
@@ -51,6 +51,8 @@
 #define CMD_INTERNAL_AUTHENTICATE 0x88
 #define CMD_GENERATE_KEYPAIR      0x47
 #define CMD_GET_CHALLENGE         0x84
+#define CMD_READ_BINARY 0xB0
+#define CMD_READ_RECORD 0xB2
 
 static gpg_error_t
 map_sw (int sw)
@@ -66,6 +68,8 @@ map_sw (int sw)
     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_FILE_NOT_FOUND: ec = GPG_ERR_ENOENT; break;
+    case SW_RECORD_NOT_FOUND:ec= GPG_ERR_NOT_FOUND; 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;
@@ -91,18 +95,79 @@ map_sw (int sw)
    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
+   for okay or a GPG error code.  Note that ISO error codes are
    internally mapped. */
 gpg_error_t
 iso7816_select_application (int slot, const char *aid, size_t aidlen)
 {
+  static char const openpgp_aid[] = { 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01 };
   int sw;
+  int p1 = 0x0C; /* No FCI to be returned. */
+  
+  if (aidlen == sizeof openpgp_aid
+      && !memcmp (aid, openpgp_aid, sizeof openpgp_aid))
+    p1 = 0; /* The current openpgp cards don't allow 0x0c. */
 
-  sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, 4, 0, aidlen, aid);
+  sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, 4, p1, aidlen, aid);
   return map_sw (sw);
 }
 
 
+gpg_error_t
+iso7816_select_file (int slot, int tag, int is_dir,
+                     unsigned char **result, size_t *resultlen)
+{
+  int sw, p0, p1;
+  unsigned char tagbuf[2];
+
+  tagbuf[0] = (tag >> 8) & 0xff;
+  tagbuf[1] = tag & 0xff;
+
+  if (result || resultlen)
+    {
+      *result = NULL;
+      *resultlen = 0;
+      return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+    }
+  else
+    {
+      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 );
+      return map_sw (sw);
+    }
+
+  return 0;
+}
+
+
+/* This is a private command currently only working for TCOS cards. */
+gpg_error_t
+iso7816_list_directory (int slot, int list_dirs,
+                        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, 0x80, 0xAA, list_dirs? 1:2, 0, -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);
+}
+
+
+
 /* 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
@@ -381,3 +446,126 @@ iso7816_get_challenge (int slot, int length, unsigned char *buffer)
 
   return 0;
 }
+
+/* Perform a READ BINARY command requesting a maximum of NMAX bytes
+   from OFFSET.  With NMAX = 0 the entire file is read. The result is
+   stored in a newly allocated buffer at the address passed by RESULT.
+   Returns the length of this data at the address of RESULTLEN. */
+gpg_error_t
+iso7816_read_binary (int slot, size_t offset, size_t nmax,
+                     unsigned char **result, size_t *resultlen)
+{
+  int sw;
+  unsigned char *buffer;
+  size_t bufferlen;
+
+  if (!result || !resultlen)
+    return gpg_error (GPG_ERR_INV_VALUE);
+  *result = NULL;
+  *resultlen = 0;
+
+  /* We can only encode 15 bits in p0,p1 to indicate an offset. Thus
+     we check for this limit. */
+  if (offset > 32767 || nmax > 254)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  do
+    {
+      buffer = NULL;
+      bufferlen = 0;
+      /* Fixme: Either the ccid driver of the TCOS cards have problems
+         with an Le of 0. */
+      sw = apdu_send_le (slot, 0x00, CMD_READ_BINARY,
+                      ((offset>>8) & 0xff), (offset & 0xff) , -1, NULL,
+                      nmax? nmax : 254, &buffer, &bufferlen);
+
+      if (sw != SW_SUCCESS && sw != SW_EOF_REACHED)
+        {
+          /* Make sure that pending buffers are released. */
+          xfree (buffer);
+          xfree (*result);
+          *result = NULL;
+          *resultlen = 0;
+          return map_sw (sw);
+        }
+      if (*result) /* Need to extend the buffer. */
+        {
+          unsigned char *p = xtryrealloc (*result, *resultlen + bufferlen);
+          if (!p)
+            {
+              gpg_error_t err = gpg_error_from_errno (errno);
+              xfree (buffer);
+              xfree (*result);
+              *result = NULL;
+              *resultlen = 0;
+              return err;
+            }
+          *result = p;
+          memcpy (*result + *resultlen, buffer, bufferlen);
+          *resultlen += bufferlen;
+          xfree (buffer);
+          buffer = NULL;
+        }
+      else /* Transfer the buffer into our result. */
+        {
+          *result = buffer;
+          *resultlen = bufferlen;
+        }
+      offset += bufferlen;
+      if (offset > 32767)
+        break; /* We simply truncate the result for too large
+                  files. */
+    }
+  while (!nmax && sw != SW_EOF_REACHED);
+  
+  return 0;
+}
+
+/* Perform a READ RECORD command. RECNO gives the record number to
+   read with 0 indicating the current record.  RECCOUNT must be 1 (not
+   all cards support reading of more than one record).  The result is
+   stored in a newly allocated buffer at the address passed by RESULT.
+   Returns the length of this data at the address of RESULTLEN. */
+gpg_error_t
+iso7816_read_record (int slot, int recno, int reccount,
+                     unsigned char **result, size_t *resultlen)
+{
+  int sw;
+  unsigned char *buffer;
+  size_t bufferlen;
+
+  if (!result || !resultlen)
+    return gpg_error (GPG_ERR_INV_VALUE);
+  *result = NULL;
+  *resultlen = 0;
+
+  /* We can only encode 15 bits in p0,p1 to indicate an offset. Thus
+     we check for this limit. */
+  if (recno < 0 || recno > 255 || reccount != 1)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  buffer = NULL;
+  bufferlen = 0;
+  /* Fixme: Either the ccid driver of the TCOS cards have problems
+     with an Le of 0. */
+  sw = apdu_send_le (slot, 0x00, CMD_READ_RECORD,
+                     recno, 
+                     0x04,
+                     -1, NULL,
+                     254, &buffer, &bufferlen);
+
+  if (sw != SW_SUCCESS && sw != SW_EOF_REACHED)
+    {
+      /* Make sure that pending buffers are released. */
+      xfree (buffer);
+      xfree (*result);
+      *result = NULL;
+      *resultlen = 0;
+      return map_sw (sw);
+    }
+  *result = buffer;
+  *resultlen = bufferlen;
+  
+  return 0;
+}
+
index 26b8d6a..98e6886 100644 (file)
 
 gpg_error_t iso7816_select_application (int slot,
                                         const char *aid, size_t aidlen);
+gpg_error_t iso7816_select_file (int slot, int tag, int is_dir,
+                                 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_verify (int slot,
                             int chvno, const char *chv, size_t chvlen);
 gpg_error_t iso7816_change_reference_data (int slot, int chvno,
@@ -56,5 +60,9 @@ gpg_error_t iso7816_read_public_key (int slot,
 gpg_error_t iso7816_get_challenge (int slot,
                                    int length, unsigned char *buffer);
 
+gpg_error_t iso7816_read_binary (int slot, size_t offset, size_t nmax,
+                                 unsigned char **result, size_t *resultlen);
+gpg_error_t iso7816_read_record (int slot, int recno, int reccount,
+                                 unsigned char **result, size_t *resultlen);
 
 #endif /*ISO7816_H*/
index b56b885..78cb2ac 100644 (file)
@@ -165,7 +165,7 @@ main (int argc, char **argv )
 
   /* FIXME: Use select_application. */
   appbuf.slot = slot;
-  rc = app_select_openpgp (&appbuf, &appbuf.serialno, &appbuf.serialnolen);
+  rc = app_select_openpgp (&appbuf);
   if (rc)
     {
       log_error ("selecting openpgp failed: %s\n", gpg_strerror (rc));
index ecd3856..acef86e 100644 (file)
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
+#include <ctype.h>
+#include <unistd.h>
+
+#ifdef HAVE_READLINE_READLINE_H
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
 
 #define JNLIB_NEED_LOG_LOGV
 #include "scdaemon.h"
 #include "apdu.h" /* for open_reader */
 #include "atr.h"
 #include "app-common.h"
+#include "iso7816.h"
 
 #define _(a) (a)
 
+#define CONTROL_D ('D' - 'A' + 1)
+
 
 enum cmd_and_opt_values 
-{ oVerbose       = 'v',
+{ 
+  oInteractive    = 'i',
+  oVerbose       = 'v',
   oReaderPort     = 500,
   octapiDriver,
   oDebug,
   oDebugAll,
 
+  oDisableCCID,
+
+
   oGenRandom,
 
 aTest };
@@ -52,15 +67,27 @@ static ARGPARSE_OPTS opts[] = {
   
   { 301, NULL, 0, "@Options:\n " },
 
+  { oInteractive, "interactive", 0, "start in interactive explorer mode"},
   { oVerbose, "verbose",   0, "verbose" },
   { oReaderPort, "reader-port", 2, "|N|connect to reader at port N"},
   { octapiDriver, "ctapi-driver", 2, "NAME|use NAME as ctAPI driver"},
+  { oDisableCCID, "disable-ccid", 0,
+#ifdef HAVE_LIBUSB
+                                    "do not use the internal CCID driver"
+#else
+                                    "@"
+#endif
+  },
   { 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 void interactive_shell (int slot);
+
+
 static const char *
 my_strusage (int level)
 {
@@ -111,10 +138,8 @@ main (int argc, char **argv )
   ARGPARSE_ARGS pargs;
   int slot, rc;
   const char *reader_port = NULL;
-  struct app_ctx_s appbuf;
   unsigned long gen_random = 0;
-
-  memset (&appbuf, 0, sizeof appbuf);
+  int interactive = 0;
 
   set_strusage (my_strusage);
   gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
@@ -143,7 +168,9 @@ main (int argc, char **argv )
         case oDebugAll: opt.debug = ~0; break;
         case oReaderPort: reader_port = pargs.r.ret_str; break;
         case octapiDriver: opt.ctapi_driver = pargs.r.ret_str; break;
+        case oDisableCCID: opt.disable_ccid = 1; break;
         case oGenRandom: gen_random = pargs.r.ret_ulong; break;
+        case oInteractive: interactive = 1; break;
         default : pargs.err = 2; break;
        }
     }
@@ -151,7 +178,7 @@ main (int argc, char **argv )
     exit(2);
 
   if (opt.verbose < 2)
-    opt.verbose = 2; /* hack to let select_openpgp print some info. */
+    opt.verbose = 2; /* Hack to let select_openpgp print some info. */
 
   if (argc)
     usage (1);
@@ -167,40 +194,61 @@ main (int argc, char **argv )
         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));
+  if (interactive)
+    interactive_shell (slot);
   else
     {
-      appbuf.initialized = 1;
-      log_info ("openpgp application selected\n");
+      struct app_ctx_s appbuf;
 
-      if (gen_random)
+      /* Fixme: We better use app.c directly. */
+      memset (&appbuf, 0, sizeof appbuf);
+      appbuf.slot = slot;
+      rc = app_select_openpgp (&appbuf);
+      if (rc)
         {
-          size_t nbytes;
-          unsigned char *buffer;
-          
-          buffer = xmalloc (4096);
-          do 
+          log_info ("selecting openpgp failed: %s\n", gpg_strerror (rc));
+          memset (&appbuf, 0, sizeof appbuf);
+          appbuf.slot = slot;
+          rc = app_select_dinsig (&appbuf);
+          if (rc)
+            log_info ("selecting dinsig failed: %s\n", gpg_strerror (rc));
+          else
             {
-              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
+              appbuf.initialized = 1;
+              log_info ("dinsig application selected\n");
+            }
+        }
+      else
+        {
+          appbuf.initialized = 1;
+          log_info ("openpgp application selected\n");
+
+          if (gen_random)
+            {
+              size_t nbytes;
+              unsigned char *buffer;
+          
+              buffer = xmalloc (4096);
+              do 
                 {
-                  if (fwrite (buffer, nbytes, 1, stdout) != 1)
-                    log_error ("writing to stdout failed: %s\n",
-                               strerror (errno));
-                  gen_random -= nbytes;
+                  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);
             }
-          while (gen_random && !log_get_errorcount (0));
-          xfree (buffer);
         }
     }
-
+  
   return log_get_errorcount (0)? 2:0;
 }
 
@@ -211,3 +259,377 @@ send_status_info (CTRL ctrl, const char *keyword, ...)
 {
   /* DUMMY */
 }
+
+
+
+/* Dump BUFFER of length NBYTES in a nicely human readable format. */ 
+static void
+dump_buffer (const unsigned char *buffer, size_t nbytes)
+{
+  int i;
+
+  while (nbytes)
+    {
+      for (i=0; i < 16 && i < nbytes; i++)
+        printf ("%02X%s ", buffer[i], i==8? " ":"");
+      for (; i < 16; i++)
+        printf ("  %s ", i==8? " ":"");
+      putchar (' ');
+      putchar (' ');
+      for (i=0; i < 16 && i < nbytes; i++)
+        if (isprint (buffer[i]))
+          putchar (buffer[i]);
+        else
+          putchar ('.');
+      nbytes -= i;
+      buffer += i;
+      for (; i < 16; i++)
+        putchar (' ');
+      putchar ('\n');
+    }
+}
+
+
+static void
+dump_or_store_buffer (const char *arg,
+                      const unsigned char *buffer, size_t nbytes)
+{
+  const char *s = strchr (arg, '>');
+  int append;
+  FILE *fp;
+
+  if (!s)
+    {
+      dump_buffer (buffer, nbytes);
+      return;
+    }
+  if ((append = (*++s == '>')))
+    s++;
+  fp = fopen (s, append? "ab":"wb");
+  if (!fp)
+    {
+      log_error ("failed to create `%s': %s\n", s, strerror (errno));
+      return;
+    }
+  if (nbytes && fwrite (buffer, nbytes, 1, fp) != 1)
+      log_error ("failed to write to `%s': %s\n", s, strerror (errno));
+  if (fclose (fp))
+      log_error ("failed to close `%s': %s\n", s, strerror (errno));
+}
+
+
+/* Convert STRING into a a newly allocated buffer and return the
+   length of the buffer in R_LENGTH.  Detect xx:xx:xx... sequence and
+   unhexify that one. */
+static unsigned char *
+pin_to_buffer (const char *string, size_t *r_length)
+{
+  unsigned char *buffer = xmalloc (strlen (string)+1);
+  const char *s;
+  size_t n;
+
+  for (s=string, n=0; *s; s += 3)
+    {
+      if (hexdigitp (s) && hexdigitp (s+1) && (s[2]==':'||!s[2]))
+        {
+          buffer[n++] = xtoi_2 (s);
+          if (!s[2])
+            break;
+        }
+      else
+        {
+          memcpy (buffer, string, strlen (string));
+          *r_length = strlen (string);
+          return buffer;
+        }
+    }
+  *r_length = n;
+  return buffer;
+}
+
+
+static char *
+read_line (int use_readline, char *prompt)
+{
+  static char buf[256];
+
+#ifdef HAVE_READLINE
+  if (use_readline)
+    {
+      char *line = readline (prompt);
+      if (line)
+        trim_spaces (line);
+      if (line && strlen (line) > 2 )
+        add_history (line);
+      return line;
+    }
+#endif
+  /* Either we don't have readline or we are not running
+     interactively */
+#ifndef HAVE_READLINE
+  printf ("%s", prompt );
+#endif
+  fflush(stdout);
+  if (!fgets(buf, sizeof(buf), stdin))
+    return NULL;
+  if (!strlen(buf))
+    return NULL;
+  if (buf[strlen (buf)-1] == '\n')
+    buf[strlen (buf)-1] = 0;
+  trim_spaces (buf);
+  return buf;
+}
+
+/* Run a shell for interactive exploration of the card. */
+static void
+interactive_shell (int slot)
+{
+  enum cmdids
+    {
+      cmdNOP = 0,
+      cmdQUIT, cmdHELP,
+      cmdSELECT,
+      cmdCHDIR,
+      cmdLS,
+      cmdAPP,
+      cmdREAD,
+      cmdREADREC,
+      cmdDEBUG,
+      cmdVERIFY,
+      cmdCHANGEREF,
+
+      cmdINVCMD
+    };
+  static struct 
+  {
+    const char *name;
+    enum cmdids id;
+    const char *desc;
+  } cmds[] = {
+    { "quit"   , cmdQUIT  , "quit this menu" },
+    { "q"      , cmdQUIT  , NULL   },
+    { "help"   , cmdHELP  , "show this help" },
+    { "?"      , cmdHELP  , NULL   },
+    { "debug"  , cmdDEBUG, "set debugging flags" },
+    { "select" , cmdSELECT, "select file (EF)" },
+    { "s"      , cmdSELECT, NULL },
+    { "chdir"  , cmdCHDIR, "change directory (select DF)"},
+    { "cd"     , cmdCHDIR,  NULL },
+    { "ls"     , cmdLS,    "list directory (some cards only)"},
+    { "app"    , cmdAPP,   "select application"},
+    { "read"   , cmdREAD,  "read binary" },
+    { "rb"     , cmdREAD,  NULL },
+    { "readrec", cmdREADREC,  "read record(s)" },
+    { "rr"     , cmdREADREC,  NULL },
+    { "verify" , cmdVERIFY, "verify CHVNO PIN" },
+    { "ver"    , cmdVERIFY, NULL },
+    { "changeref", cmdCHANGEREF, "change reference data" },
+    { NULL, cmdINVCMD } 
+  };
+  enum cmdids cmd = cmdNOP;
+  int use_readline = isatty (fileno(stdin));
+  char *line;
+  gpg_error_t err = 0;
+  unsigned char *result = NULL;
+  size_t resultlen;
+
+#ifdef HAVE_READLINE
+  if (use_readline)
+    using_history ();
+#endif
+
+  for (;;)
+    {
+      int arg_number;
+      const char *arg_string = "";
+      const char *arg_next = "";
+      char *p;
+      int i;
+      
+      if (err)
+        printf ("command failed: %s\n", gpg_strerror (err));
+      err = 0;
+      xfree (result);
+      result = NULL;
+
+      printf ("\n");
+      do
+        {
+          line = read_line (use_readline, "cmd> ");
+       }
+      while ( line && *line == '#' );
+
+      arg_number = 0; 
+      if (!line || *line == CONTROL_D)
+        cmd = cmdQUIT; 
+      else if (!*line)
+        cmd = cmdNOP;
+      else {
+        if ((p=strchr (line,' ')))
+          {
+            char *endp;
+
+            *p++ = 0;
+            trim_spaces (line);
+            trim_spaces (p);
+            arg_number = strtol (p, &endp, 0);
+            arg_string = p;
+            if (endp != p)
+              {
+                arg_next = endp;
+                while ( spacep (arg_next) )
+                  arg_next++;
+              }
+          }
+
+        for (i=0; cmds[i].name; i++ )
+          if (!ascii_strcasecmp (line, cmds[i].name ))
+            break;
+        
+        cmd = cmds[i].id;
+      }
+      
+      switch (cmd)
+        {
+        case cmdHELP:
+          for (i=0; cmds[i].name; i++ )
+            if (cmds[i].desc)
+              printf("%-10s %s\n", cmds[i].name, _(cmds[i].desc) );
+          break;
+
+        case cmdQUIT:
+          goto leave;
+
+        case cmdNOP:
+          break;
+
+        case cmdDEBUG:
+          if (!*arg_string)
+            opt.debug = opt.debug? 0 : 2048;
+          else
+            opt.debug = arg_number;
+          break;
+
+        case cmdSELECT:
+          err = iso7816_select_file (slot, arg_number, 0, NULL, NULL);
+          break;
+
+        case cmdCHDIR:
+          err = iso7816_select_file (slot, arg_number, 1, NULL, NULL);
+          break;
+
+        case cmdLS:
+          err = iso7816_list_directory (slot, 1, &result, &resultlen);
+          if (!err || gpg_err_code (err) == GPG_ERR_ENOENT)
+            err = iso7816_list_directory (slot, 0, &result, &resultlen);
+          /* FIXME: Do something with RESULT. */
+          break;
+
+        case cmdAPP:
+          {
+            app_t app;
+
+            app = select_application (NULL, slot, *arg_string? arg_string:NULL);
+            if (app)
+              {
+                char *sn;
+
+                app_get_serial_and_stamp (app, &sn, NULL);
+                log_info ("application `%s' ready; sn=%s\n",
+                          app->apptype?app->apptype:"?", sn? sn:"[none]");
+                release_application (app);
+              }
+          }
+          break;
+
+        case cmdREAD:
+          err = iso7816_read_binary (slot, 0, 0, &result, &resultlen);
+          if (!err)
+            dump_or_store_buffer (arg_string, result, resultlen);
+          break;
+
+        case cmdREADREC:
+          if (*arg_string == '*' && (!arg_string[1] || arg_string[1] == ' '))
+            {
+              /* Fixme: Can't write to a file yet. */
+              for (i=1, err=0; !err; i++)
+                {
+                  xfree (result); result = NULL;
+                  err = iso7816_read_record (slot, i, 1, &result, &resultlen);
+                  if (!err)
+                    dump_buffer (result, resultlen);
+                }
+              if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
+                err = 0;
+            }
+          else
+            {
+              err = iso7816_read_record (slot, arg_number, 1,
+                                         &result, &resultlen);
+              if (!err)
+                dump_or_store_buffer (arg_string, result, resultlen);
+            }
+          break;
+
+        case cmdVERIFY:
+          if (arg_number < 0 || arg_number > 255 || (arg_number & 127) > 31)
+            printf ("error: invalid CHVNO\n");
+          else 
+            {
+              unsigned char *pin;
+              size_t pinlen;
+
+              pin = pin_to_buffer (arg_next, &pinlen);
+              err = iso7816_verify (slot, arg_number, pin, pinlen);
+              xfree (pin);
+            }
+            break;
+
+        case cmdCHANGEREF:
+          {
+            const char *newpin = arg_next;
+            
+            while ( *newpin && !spacep (newpin) )
+              newpin++;
+            while ( spacep (newpin) )
+              newpin++;
+
+            if (arg_number < 0 || arg_number > 255 || (arg_number & 127) > 31)
+              printf ("error: invalid CHVNO\n");
+            else if (!*arg_next || !*newpin || newpin == arg_next)
+              printf ("usage: changeref CHVNO OLDPIN NEWPIN\n");
+            else
+              {
+                char *oldpin = xstrdup (arg_next);
+                unsigned char *oldpin_buf, *newpin_buf;
+                size_t oldpin_len, newpin_len;
+
+                for (p=oldpin; *p && !spacep (p); p++ )
+                  ;
+                *p = 0;
+                oldpin_buf = pin_to_buffer (oldpin, &oldpin_len);
+                newpin_buf = pin_to_buffer (newpin, &newpin_len);
+
+                err = iso7816_change_reference_data (slot, arg_number,
+                                                     oldpin_buf, oldpin_len,
+                                                     newpin_buf, newpin_len);
+
+                xfree (newpin_buf);
+                xfree (oldpin_buf);
+                xfree (oldpin);
+              }
+          }
+          break;
+
+        case cmdINVCMD:
+        default:
+          printf ("\n");
+          printf ("Invalid command  (try \"help\")\n");
+          break;
+        } /* End command switch. */
+    } /* End of main menu loop. */
+
+ leave:
+  ;
+}
+
index 91ac932..c6652c8 100644 (file)
@@ -100,7 +100,7 @@ static ARGPARSE_OPTS opts[] = {
   { 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")},
-  { oDisableCCID, "disable-ccidc", 0,
+  { oDisableCCID, "disable-ccid", 0,
 #ifdef HAVE_LIBUSB
                                     N_("do not use the internal CCID driver")
 #else
@@ -397,7 +397,7 @@ main (int argc, char **argv )
         case oServer: pipe_server = 1; break;
         case oDaemon: is_daemon = 1; break;
 
-        case oReaderPort: app_set_default_reader_port (pargs.r.ret_str); break;
+        case oReaderPort: opt.reader_port = pargs.r.ret_str; break;
         case octapiDriver: opt.ctapi_driver = pargs.r.ret_str; break;
         case opcscDriver: opt.pcsc_driver = pargs.r.ret_str; break;
         case oDisableCCID: opt.disable_ccid = 1; break;
index e13377a..2bbf271 100644 (file)
@@ -55,6 +55,7 @@ struct {
   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_opensc;  /* Disable the use of the OpenSC framework. */
   int disable_ccid;    /* Disable the use of the internal CCID driver. */
   int allow_admin;     /* Allow the use of admin commands for certain
@@ -96,8 +97,10 @@ struct server_control_s {
 };
 
 typedef struct server_control_s *CTRL;
+typedef struct server_control_s *ctrl_t;
 typedef struct card_ctx_s *CARD;
 typedef struct app_ctx_s *APP;
+typedef struct app_ctx_s *app_t;
 
 /*-- scdaemon.c --*/
 void scd_exit (int rc);
diff --git a/scd/tlv.c b/scd/tlv.c
new file mode 100644 (file)
index 0000000..dbcd245
--- /dev/null
+++ b/scd/tlv.c
@@ -0,0 +1,219 @@
+/* tlv.c - Tag-Length-Value Utilities
+ *     Copyright (C) 2003, 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 <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <gpg-error.h>
+
+#include "tlv.h"
+
+static const unsigned char *
+do_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 a too deep
+             nesting. */
+          const unsigned char *tmp_s;
+          size_t tmp_len;
+          
+          tmp_s = do_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;
+    }
+}
+
+
+/* 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. */
+const unsigned char *
+find_tlv (const unsigned char *buffer, size_t length,
+          int tag, size_t *nbytes)
+{
+  return do_find_tlv (buffer, length, tag, nbytes, 0);
+}
+
+
+
+
+/* ASN.1 BER parser: Parse BUFFER of length SIZE and return the tag
+   and the length part from the TLV triplet.  Update BUFFER and SIZE
+   on success. */
+gpg_error_t
+parse_ber_header (unsigned char const **buffer, size_t *size,
+                  int *r_class, int *r_tag, 
+                  int *r_constructed, int *r_ndef,
+                  size_t *r_length, size_t *r_nhdr)
+{
+  int c;
+  unsigned long tag;
+  const unsigned char *buf = *buffer;
+  size_t length = *size;
+
+  *r_ndef = 0;
+  *r_length = 0;
+  *r_nhdr = 0;
+
+  /* Get the tag. */
+  if (!length)
+    return gpg_error (GPG_ERR_EOF);
+  c = *buf++; length--; ++*r_nhdr;
+
+  *r_class = (c & 0xc0) >> 6;
+  *r_constructed = !!(c & 0x20);
+  tag = c & 0x1f;
+
+  if (tag == 0x1f)
+    {
+      tag = 0;
+      do
+        {
+          /* Simple check against overflow.  We limit our maximim tag
+             value more than needed but that should not be a problem
+             because I have nver encountered such large value.  We
+             assume at least 32 bit integers. */
+          if (tag > (1 << 24))
+            return gpg_error (GPG_ERR_TOO_LARGE);
+          tag <<= 7;
+          if (!length)
+            return gpg_error (GPG_ERR_EOF);
+          c = *buf++; length--; ++*r_nhdr;
+          tag |= c & 0x7f;
+
+        }
+      while (c & 0x80);
+    }
+  *r_tag = tag;
+
+  /* Get the length. */
+  if (!length)
+    return gpg_error (GPG_ERR_EOF);
+  c = *buf++; length--; ++*r_nhdr;
+
+  if ( !(c & 0x80) )
+    *r_length = c;
+  else if (c == 0x80)
+    *r_ndef = 1;
+  else if (c == 0xff)
+    return gpg_error (GPG_ERR_BAD_BER);
+  else
+    {
+      unsigned long len = 0;
+      int count = c & 0x7f;
+
+      for (; count; count--)
+        {
+          /* Simple check against overflow.  We limit our maximim
+             length more than needed but that should not be a problem
+             because I have never encountered such large value and
+             well they are managed in memory and thus we would run
+             into memory problems anyway.  We assume at least 32 bit
+             integers. */
+          if (len > (1 << 24))
+            return gpg_error (GPG_ERR_TOO_LARGE);
+          len <<= 8;
+          if (!length)
+            return gpg_error (GPG_ERR_EOF);
+          c = *buf++; length--; ++*r_nhdr;
+          len |= c & 0xff;
+        }
+      *r_length = len;
+    }
+  
+  /* Without this kludge some example certs can't be parsed. */
+  if (*r_class == CLASS_UNIVERSAL && !*r_tag)
+    *r_length = 0;
+  
+  *buffer = buf;
+  *size = length;
+  return 0;
+}
diff --git a/scd/tlv.h b/scd/tlv.h
new file mode 100644 (file)
index 0000000..26a9905
--- /dev/null
+++ b/scd/tlv.h
@@ -0,0 +1,84 @@
+/* tlv.h - Tag-Length-Value Utilities
+ *     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
+ */
+
+#ifndef SCD_TLV_H
+#define SCD_TLV_H 1
+
+
+enum tlv_tag_class {
+  CLASS_UNIVERSAL = 0,
+  CLASS_APPLICATION = 1,
+  CLASS_CONTEXT = 2,
+  CLASS_PRIVATE =3
+};
+
+enum tlv_tag_type {
+  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
+};
+
+
+
+/* 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.*/
+const unsigned char *find_tlv (const unsigned char *buffer, size_t length,
+                               int tag, size_t *nbytes);
+
+
+/* ASN.1 BER parser: Parse BUFFER of length SIZE and return the tag
+   and the length part from the TLV triplet.  Update BUFFER and SIZE
+   on success. */
+gpg_error_t parse_ber_header (unsigned char const **buffer, size_t *size,
+                              int *r_class, int *r_tag, 
+                              int *r_constructed,
+                              int *r_ndef, size_t *r_length, size_t *r_nhdr);
+
+
+
+#endif /* SCD_TLV_H */
diff --git a/tools/gpgparsemail.c b/tools/gpgparsemail.c
new file mode 100644 (file)
index 0000000..956cf18
--- /dev/null
@@ -0,0 +1,705 @@
+/* gpgparsemail.c - Standalone crypto mail parser
+ *     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
+ */
+
+
+/* This utility prints an RFC8222, possible MIME structured, message
+   in an annotated format with the first column having an indicator
+   for the content of the line..  Several options are available to
+   scrutinize the message.  S/MIME and OpenPGP suuport is included. */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <time.h>
+#include <signal.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+
+#include "rfc822parse.h"
+
+
+#define PGM "gpgparsemail"
+
+/* Option flags. */
+static int verbose;
+static int debug;
+static int opt_crypto;    /* Decrypt or verify messages. */
+static int opt_no_header; /* Don't output the header lines. */
+
+/* Structure used to communicate with the parser callback. */
+struct parse_info_s {
+  int show_header;             /* Show the header lines. */
+  int show_data;               /* Show the data lines. */
+  unsigned int skip_show;      /* Temporary disable above for these
+                                   number of lines. */
+  int show_data_as_note;       /* The next data line should be shown
+                                  as a note. */
+  int show_boundary;
+  int nesting_level;
+
+  int gpgsm_mime;              /* gpgsm shall be used from S/MIME. */
+  char *signing_protocol;
+  int hashing_level;           /* The nesting level we are hashing. */
+  int hashing;                 
+  FILE *hash_file;
+  FILE *sig_file;
+  int  verify_now;             /* Falg set when all signature data is
+                                  available. */
+};
+
+
+/* Print diagnostic message and exit with failure. */
+static void
+die (const char *format, ...)
+{
+  va_list arg_ptr;
+
+  fflush (stdout);
+  fprintf (stderr, "%s: ", PGM);
+
+  va_start (arg_ptr, format);
+  vfprintf (stderr, format, arg_ptr);
+  va_end (arg_ptr);
+  putc ('\n', stderr);
+
+  exit (1);
+}
+
+
+/* Print diagnostic message. */
+static void
+err (const char *format, ...)
+{
+  va_list arg_ptr;
+
+  fflush (stdout);
+  fprintf (stderr, "%s: ", PGM);
+
+  va_start (arg_ptr, format);
+  vfprintf (stderr, format, arg_ptr);
+  va_end (arg_ptr);
+  putc ('\n', stderr);
+}
+
+static void *
+xmalloc (size_t n)
+{
+  void *p = malloc (n);
+  if (!p)
+    die ("out of core: %s", strerror (errno));
+  return p;
+}
+
+/* static void * */
+/* xcalloc (size_t n, size_t m) */
+/* { */
+/*   void *p = calloc (n, m); */
+/*   if (!p) */
+/*     die ("out of core: %s", strerror (errno)); */
+/*   return p; */
+/* } */
+
+/* static void * */
+/* xrealloc (void *old, size_t n) */
+/* { */
+/*   void *p = realloc (old, n); */
+/*   if (!p) */
+/*     die ("out of core: %s", strerror (errno)); */
+/*   return p; */
+/* } */
+
+static char *
+xstrdup (const char *string)
+{
+  void *p = malloc (strlen (string)+1);
+  if (!p)
+    die ("out of core: %s", strerror (errno));
+  strcpy (p, string);
+  return p;
+}
+
+static char *
+stpcpy (char *a,const char *b)
+{
+  while (*b)
+    *a++ = *b++;
+  *a = 0;
+  
+  return (char*)a;
+}
+
+
+static int
+run_gnupg (int smime, int sig_fd, int data_fd, int *close_list)
+{
+  int rp[2];
+  pid_t pid;
+  int i, c, is_status;
+  unsigned int pos;
+  char status_buf[10];
+  const char *cmd = smime? "gpgsm":"gpg";
+  FILE *fp;
+
+  if (pipe (rp) == -1)
+    die ("error creating a pipe: %s", strerror (errno));
+
+  pid = fork ();
+  if (pid == -1)
+    die ("error forking process: %s", strerror (errno));
+
+  if (!pid)
+    { /* Child. */
+      char data_fd_buf[50];
+      int fd;
+
+      /* Connect our signature fd to stdin. */
+      if (sig_fd != 0)
+        {
+          if (dup2 (sig_fd, 0) == -1)
+            die ("dup2 stdin failed: %s", strerror (errno));
+        }
+      
+      /* Keep our data fd and format it for gpg/gpgsm use. */
+      sprintf (data_fd_buf, "-&%d", data_fd);
+
+      /* Send stdout to the bit bucket. */
+      fd = open ("/dev/null", O_WRONLY);
+      if (fd == -1)
+        die ("can't open `/dev/null': %s", strerror (errno));
+      if (fd != 1)
+       {
+          if (dup2 (fd, 1) == -1)
+            die ("dup2 stderr failed: %s", strerror (errno));
+        }
+      
+      /* Connect stderr to our pipe. */
+      if (rp[1] != 2)
+       {
+         if (dup2 (rp[1], 2) == -1)
+           die ("dup2 stderr failed: %s", strerror (errno));
+       }
+
+      /* Close other files. */
+      for (i=0; (fd=close_list[i]) != -1; i++)
+        if (fd > 2 && fd != data_fd)
+          close (fd);
+      errno = 0;
+
+      execlp (cmd, cmd,
+              "--enable-special-filenames",
+              "--status-fd", "2",
+              "--assume-base64",
+              "--verify",
+              "--",
+              "-", data_fd_buf,
+              NULL);
+
+      die ("failed to exec the crypto command: %s", strerror (errno));
+    }
+
+  /* Parent. */ 
+  close (rp[1]);
+
+  fp = fdopen (rp[0], "r");
+  if (!fp)
+    die ("can't fdopen pipe for reading: %s", strerror (errno));
+
+  pos = 0;
+  is_status = 0;
+  assert (sizeof status_buf > 9);
+  while ((c=getc (fp)) != EOF)
+    {
+      if (pos < 9)
+        status_buf[pos] = c;
+      else 
+        {
+          if (pos == 9)
+            {
+              is_status = !memcmp (status_buf, "[GNUPG:] ", 9);
+              if (is_status)
+                fputs ( "c ", stdout);
+              else if (verbose)
+                fputs ( "# ", stdout);
+              fwrite (status_buf, 9, 1, stdout);
+            }
+          putchar (c);
+        }
+      if (c == '\n')
+        {
+          if (verbose && pos < 9)
+            {
+              fputs ( "# ", stdout);
+              fwrite (status_buf, pos+1, 1, stdout);
+            }
+          pos = 0;
+        }
+      else
+        pos++;
+    }
+  if (pos)
+    {
+      if (verbose && pos < 9)
+        {
+          fputs ( "# ", stdout);
+          fwrite (status_buf, pos+1, 1, stdout);
+        }
+      putchar ('\n');
+    }
+  fclose (fp);
+
+  while ( (i=waitpid (pid, NULL, 0)) == -1 && errno == EINTR)
+    ;
+  if (i == -1)
+    die ("waiting for child failed: %s", strerror (errno));
+
+  return 0;
+}
+
+
+
+
+/* Verify the signature in the current temp files. */
+static void
+verify_signature (struct parse_info_s *info)
+{
+  int close_list[10];
+
+  assert (info->hash_file);
+  assert (info->sig_file);
+  rewind (info->hash_file);
+  rewind (info->sig_file);
+
+/*   printf ("# Begin hashed data\n"); */
+/*   while ( (c=getc (info->hash_file)) != EOF) */
+/*     putchar (c); */
+/*   printf ("# End hashed data signature\n"); */
+/*   printf ("# Begin signature\n"); */
+/*   while ( (c=getc (info->sig_file)) != EOF) */
+/*     putchar (c); */
+/*   printf ("# End signature\n"); */
+/*   rewind (info->hash_file); */
+/*   rewind (info->sig_file); */
+
+  close_list[0] = -1;
+  run_gnupg (1, fileno (info->sig_file), fileno (info->hash_file), close_list);
+}
+
+
+
+
+
+/* Prepare for a multipart/signed. 
+   FIELD_CTX is the parsed context of the content-type header.*/
+static void
+mime_signed_begin (struct parse_info_s *info, rfc822parse_t msg,
+                   rfc822parse_field_t field_ctx)
+{
+  const char *s;
+  s = rfc822parse_query_parameter (field_ctx, "protocol", 1);
+  if (s)
+    {
+      printf ("h signed.protocol: %s\n", s);
+      if (!strcmp (s, "application/pkcs7-signature")
+          || !strcmp (s, "application/x-pkcs7-signature"))
+        {
+          if (info->gpgsm_mime)
+            err ("note: ignoring nested pkcs7-signature");
+          else
+            {
+              info->gpgsm_mime = 1;
+              free (info->signing_protocol);
+              info->signing_protocol = xstrdup (s);
+            }
+        }
+      else if (verbose)
+        printf ("# this protocol is not supported\n");
+    }
+}
+
+
+/* Prepare for a multipart/encrypted. 
+   FIELD_CTX is the parsed context of the content-type header.*/
+static void
+mime_encrypted_begin (struct parse_info_s *info, rfc822parse_t msg,
+                      rfc822parse_field_t field_ctx)
+{
+  const char *s;
+  s = rfc822parse_query_parameter (field_ctx, "protocol", 0);
+  if (s)
+    printf ("h encrypted.protocol: %s\n", s);
+}
+
+
+
+/* Print the event received by the parser for debugging as comment
+   line. */
+static void
+show_event (rfc822parse_event_t event)
+{
+  const char *s;
+
+  switch (event)
+    {
+    case RFC822PARSE_OPEN: s= "Open"; break;
+    case RFC822PARSE_CLOSE: s= "Close"; break;
+    case RFC822PARSE_CANCEL: s= "Cancel"; break;
+    case RFC822PARSE_T2BODY: s= "T2Body"; break;
+    case RFC822PARSE_FINISH: s= "Finish"; break;
+    case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break;
+    case RFC822PARSE_LEVEL_DOWN: s= "Level_Down"; break;
+    case RFC822PARSE_LEVEL_UP: s= "Level_Up"; break;
+    case RFC822PARSE_BOUNDARY: s= "Boundary"; break;
+    case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break;
+    case RFC822PARSE_BEGIN_HEADER: s= "Begin_Header"; break;
+    case RFC822PARSE_PREAMBLE: s= "Preamble"; break;
+    case RFC822PARSE_EPILOGUE: s= "Epilogue"; break;
+    default: s= "[unknown event]"; break;
+    }
+  printf ("# *** got RFC822 event %s\n", s);
+}
+
+/* This function is called by the parser to communicate events.  This
+   callback comminucates with the main program using a structure
+   passed in OPAQUE. Should retrun 0 or set errno and return -1. */
+static int
+message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg)
+{
+  struct parse_info_s *info = opaque;
+
+  if (debug)
+    show_event (event);
+  if (event == RFC822PARSE_OPEN)
+    {
+      /* Initialize for a new message. */
+      info->show_header = 1;
+    }
+  else if (event == RFC822PARSE_T2BODY)
+    {
+      rfc822parse_field_t ctx;
+
+      ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
+      if (ctx)
+        {
+          const char *s1, *s2;
+          s1 = rfc822parse_query_media_type (ctx, &s2);
+          if (s1)
+            {
+              printf ("h media: %*s%s %s\n", 
+                      info->nesting_level*2, "", s1, s2);
+              if (info->gpgsm_mime == 3)
+                {
+                  char *buf = xmalloc (strlen (s1) + strlen (s2) + 2);
+                  strcpy (stpcpy (stpcpy (buf, s1), "/"), s2);
+                  assert (info->signing_protocol);
+                  if (strcmp (buf, info->signing_protocol))
+                    err ("invalid S/MIME structure; expected `%s', found `%s'",
+                         info->signing_protocol, buf);
+                  else
+                    {
+                      printf ("c begin_signature\n");
+                      info->gpgsm_mime++;
+                      if (opt_crypto)
+                        {
+                          assert (!info->sig_file);
+                          info->sig_file = tmpfile ();
+                          if (!info->sig_file)
+                            die ("error creating temp file: %s",
+                                 strerror (errno));
+                        }
+                    }
+                  free (buf);
+                }
+              else if (!strcmp (s1, "multipart"))
+                {
+                  if (!strcmp (s2, "signed"))
+                    mime_signed_begin (info, msg, ctx);
+                  else if (!strcmp (s2, "encrypted"))
+                    mime_encrypted_begin (info, msg, ctx);
+                }
+            }
+          else
+            printf ("h media: %*s none\n", info->nesting_level*2, "");
+
+          rfc822parse_release_field (ctx);
+        }
+      else
+        printf ("h media: %*stext plain [assumed]\n",
+                info->nesting_level*2, "");
+      info->show_header = 0;
+      info->show_data = 1;
+      info->skip_show = 1;
+    }
+  else if (event == RFC822PARSE_PREAMBLE)
+    info->show_data_as_note = 1;
+  else if (event == RFC822PARSE_LEVEL_DOWN)
+    {
+      printf ("b down\n");
+      info->nesting_level++;
+    }
+  else if (event == RFC822PARSE_LEVEL_UP)
+    {
+      printf ("b up\n");
+      if (info->nesting_level)
+        info->nesting_level--;
+      else 
+        err ("invalid structure (bad nesting level)");
+    }
+  else if (event == RFC822PARSE_BOUNDARY || event == RFC822PARSE_LAST_BOUNDARY)
+    {
+      info->show_data = 0;
+      info->show_boundary = 1;
+      if (event == RFC822PARSE_BOUNDARY)
+        {
+          info->show_header = 1;
+          info->skip_show = 1;
+          printf ("b part\n");
+        }
+      else 
+        printf ("b last\n");
+
+      if (info->gpgsm_mime == 2 && info->nesting_level == info->hashing_level)
+        {
+          printf ("c end_hash\n");
+          info->gpgsm_mime++;
+          info->hashing = 0;
+        }
+      else if (info->gpgsm_mime == 4)
+        {
+          printf ("c end_signature\n");
+          info->verify_now = 1;
+        }
+    }
+  else if (event == RFC822PARSE_BEGIN_HEADER)
+    {
+      if (info->gpgsm_mime == 1)
+        {
+          printf ("c begin_hash\n");
+          info->hashing = 1;
+          info->hashing_level = info->nesting_level;
+          info->gpgsm_mime++;
+
+          if (opt_crypto)
+            {
+              assert (!info->hash_file);
+              info->hash_file = tmpfile ();
+              if (!info->hash_file)
+                die ("failed to create temporary file: %s", strerror (errno));
+            }
+        }
+    }
+
+  return 0;
+}
+
+
+/* Read a message from FP and process it according to the global
+   options. */
+static void
+parse_message (FILE *fp)
+{
+  char line[5000];
+  size_t length;
+  rfc822parse_t msg;
+  unsigned int lineno = 0;
+  int no_cr_reported = 0;
+  struct parse_info_s info;
+
+  memset (&info, 0, sizeof info);
+
+  msg = rfc822parse_open (message_cb, &info);
+  if (!msg)
+    die ("can't open parser: %s", strerror (errno));
+
+  /* Fixme: We should not use fgets becuase it can't cope with
+     embedded nul characters. */
+  while (fgets (line, sizeof (line), fp))
+    {
+      lineno++;
+      if (lineno == 1 && !strncmp (line, "From ", 5))
+        continue;  /* We better ignore a leading From line. */
+
+      length = strlen (line);
+      if (length && line[length - 1] == '\n')
+       line[--length] = 0;
+      else
+        err ("line number %u too long or last line not terminated", lineno);
+      if (length && line[length - 1] == '\r')
+       line[--length] = 0;
+      else if (verbose && !no_cr_reported)
+        {
+          err ("non canonical ended line detected (line %u)", lineno);
+          no_cr_reported = 1;
+        }
+
+
+      if (rfc822parse_insert (msg, line, length))
+       die ("parser failed: %s", strerror (errno));
+      
+      if (info.hashing)
+        {
+          /* Delay hashing of the CR/LF because the last line ending
+             belongs to the next boundary. */
+          if (debug)
+            printf ("# hashing %s`%s'\n", info.hashing==2?"CR,LF+":"", line);
+          if (opt_crypto)
+            {
+              if (info.hashing == 2)
+                fputs ("\r\n", info.hash_file);
+              fputs (line, info.hash_file);
+              if (ferror (info.hash_file))
+                die ("error writing to temporary file: %s", strerror (errno));
+            }
+
+          info.hashing = 2;
+        }
+
+      if (info.sig_file && opt_crypto)
+        {
+          if (info.verify_now)
+            {
+              verify_signature (&info);
+              fclose (info.hash_file);
+              info.hash_file = NULL;
+              fclose (info.sig_file);
+              info.sig_file = NULL;
+              info.gpgsm_mime = 0;
+            }
+          else
+            {
+              fputs (line, info.sig_file);
+              fputs ("\r\n", info.sig_file);
+              if (ferror (info.sig_file))
+                die ("error writing to temporary file: %s", strerror (errno));
+            }
+        }
+      
+      if (info.show_boundary)
+        {
+          if (!opt_no_header)
+            printf (":%s\n", line);
+          info.show_boundary = 0;
+        }
+
+      if (info.skip_show)
+        info.skip_show--;
+      else if (info.show_data)
+        {
+          if (info.show_data_as_note)
+            {
+              if (verbose)
+                printf ("# DATA: %s\n", line);
+              info.show_data_as_note = 0;
+            }
+          else
+            printf (" %s\n", line);
+        }
+      else if (info.show_header && !opt_no_header)
+        printf (".%s\n", line);
+
+    }
+
+  rfc822parse_close (msg);
+}
+
+
+int 
+main (int argc, char **argv)
+{
+  int last_argc = -1;
+  if (argc)
+    {
+      argc--; argv++;
+    }
+  while (argc && last_argc != argc )
+    {
+      last_argc = argc;
+      if (!strcmp (*argv, "--"))
+        {
+          argc--; argv++;
+          break;
+        }
+      else if (!strcmp (*argv, "--help"))
+        {
+          puts (
+                "Usage: " PGM " [OPTION] [FILE]\n"
+                "Parse a mail message into an annotated format.\n\n"
+                "  --crypto    decrypt or verify messages\n"
+                "  --no-header don't output the header lines\n"
+                "  --verbose   enable extra informational output\n"
+                "  --debug     enable additional debug output\n"
+                "  --help      display this help and exit\n\n"
+                "With no FILE, or when FILE is -, read standard input.\n\n"
+                "Report bugs to <bug-gnupg@gnu.org>.");
+          exit (0);
+        }
+      else if (!strcmp (*argv, "--verbose"))
+        {
+          verbose = 1;
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--debug"))
+        {
+          verbose = debug = 1;
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--crypto"))
+        {
+          opt_crypto = 1;
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--no-header"))
+        {
+          opt_no_header = 1;
+          argc--; argv++;
+        }
+    }          
+  if (argc > 1)
+    die ("usage: " PGM " [OPTION] [FILE] (try --help for more information)\n");
+
+  signal (SIGPIPE, SIG_IGN);
+
+  if (argc && strcmp (*argv, "-"))
+    {
+      FILE *fp = fopen (*argv, "rb");
+      if (!fp)
+        die ("can't open `%s': %s", *argv, strerror (errno));
+      parse_message (fp);
+      fclose (fp);
+    }
+  else
+    parse_message (stdin);
+
+  return 0;
+}
+
+
+/*
+Local Variables:
+compile-command: "gcc -Wall -g -o gpgparsemail rfc822parse.c gpgparsemail.c"
+End:
+*/
diff --git a/tools/rfc822parse.c b/tools/rfc822parse.c
new file mode 100644 (file)
index 0000000..be1cf4a
--- /dev/null
@@ -0,0 +1,1235 @@
+/* rfc822parse.c - Simple mail and MIME parser
+ *     Copyright (C) 1999, 2000 Werner Koch, Duesseldorf
+ *      Copyright (C) 2003, g10 Code GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is 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
+ */
+
+
+/* According to RFC822 binary 0 are allowed at many places. We
+ * do not handle this correct especially in the field parsing code.  It
+ * should be easy to fix and the API provides a interfcaes which returns
+ * the length but in addition makes sure that returned strings are always
+ * ended by a \0.  
+ *
+ * Furthermore, the case of field names is changed and thus it is not
+ * always a good idea to use these modified header
+ * lines (e.g. signatures may break).
+ * 
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <assert.h>
+
+#include "rfc822parse.h"
+
+enum token_type
+{
+  tSPACE,
+  tATOM,
+  tQUOTED,
+  tDOMAINLIT,
+  tSPECIAL
+};
+
+/* For now we directly use our TOKEN as the parse context */
+typedef struct rfc822parse_field_context *TOKEN;
+struct rfc822parse_field_context
+{
+  TOKEN next;
+  enum token_type type;
+  struct {
+    unsigned int cont:1;
+    unsigned int lowered:1;
+  } flags;
+  /*TOKEN owner_pantry; */
+  char data[1];
+};
+
+struct hdr_line
+{
+  struct hdr_line *next;
+  int cont;     /* This is a continuation of the previous line. */
+  unsigned char line[1];
+};
+
+typedef struct hdr_line *HDR_LINE;
+
+
+struct part
+{
+  struct part *right;     /* The next part. */
+  struct part *down;      /* A contained part. */
+  HDR_LINE hdr_lines;       /* Header lines os that part. */
+  HDR_LINE *hdr_lines_tail; /* Helper for adding lines. */
+  char *boundary;           /* Only used in the first part. */
+};
+typedef struct part *part_t;
+
+struct rfc822parse_context
+{
+  rfc822parse_cb_t callback;
+  void *callback_value;
+  int callback_error;
+  int in_body;
+  int in_preamble;      /* Wether we are before the first boundary. */
+  part_t parts;         /* The tree of parts. */
+  part_t current_part;  /* Whom we are processing (points into parts). */
+  const char *boundary; /* Current boundary. */
+};
+
+static HDR_LINE find_header (rfc822parse_t msg, const char *name,
+                            int which, HDR_LINE * rprev);
+
+
+static size_t
+length_sans_trailing_ws (const unsigned char *line, size_t len)
+{
+  const unsigned char *p, *mark;
+  size_t n;
+  
+  for (mark=NULL, p=line, n=0; n < len; n++, p++)
+    {
+      if (strchr (" \t\r\n", *p ))
+        {
+          if( !mark )
+            mark = p;
+        }
+      else
+        mark = NULL;
+    }
+  
+  if (mark) 
+    return mark - line;
+  return len;
+}
+
+
+static void
+lowercase_string (unsigned char *string)
+{
+  for (; *string; string++)
+    if (*string >= 'A' && *string <= 'Z')
+      *string = *string - 'A' + 'a';
+}
+
+/* Transform a header name into a standard capitalized format; i.e
+   "Content-Type".  Conversion stops at the colon.  As usual we don't
+   use the localized versions of ctype.h.
+ */
+static void
+capitalize_header_name (unsigned char *name)
+{
+  int first = 1;
+
+  for (; *name && *name != ':'; name++)
+    if (*name == '-')
+      first = 1;
+    else if (first)
+      {
+        if (*name >= 'a' && *name <= 'z')
+          *name = *name - 'a' + 'A';
+        first = 0;
+      }
+    else if (*name >= 'A' && *name <= 'Z')
+      *name = *name - 'A' + 'a';
+}
+
+
+static char *
+stpcpy (char *a,const char *b)
+{
+  while (*b)
+    *a++ = *b++;
+  *a = 0;
+  
+  return (char*)a;
+}
+
+
+/* If a callback has been registerd, call it for the event of type
+   EVENT. */
+static int
+do_callback (rfc822parse_t msg, rfc822parse_event_t event)
+{
+  int rc;
+
+  if (!msg->callback || msg->callback_error)
+    return 0;
+  rc = msg->callback (msg->callback_value, event, msg);
+  if (rc)
+    msg->callback_error = rc;
+  return rc;
+}
+
+static part_t
+new_part (void)
+{
+  part_t part;
+
+  part = calloc (1, sizeof *part);
+  if (part)
+    {
+      part->hdr_lines_tail = &part->hdr_lines;
+    }
+  return part;
+}
+
+
+static void
+release_part (part_t part)
+{
+  part_t tmp;
+  HDR_LINE hdr, hdr2;
+
+  for (; part; part = tmp)
+    {
+      tmp = part->right;
+      if (part->down)
+        release_part (part->down);
+      for (hdr = part->hdr_lines; hdr; hdr = hdr2)
+        {
+          hdr2 = hdr->next;
+          free (hdr);
+        }
+      free (part->boundary);
+      free (part);
+    }
+}
+
+
+static void
+release_handle_data (rfc822parse_t msg)
+{
+  release_part (msg->parts);
+  msg->parts = NULL;
+  msg->current_part = NULL;
+  msg->boundary = NULL;
+}
+
+
+/* Create a new parsing context for an entire rfc822 message and
+   return it.  CB and CB_VALUE may be given to callback for certain
+   events.  NULL is returned on error with errno set appropriately. */
+rfc822parse_t
+rfc822parse_open (rfc822parse_cb_t cb, void *cb_value)
+{
+  rfc822parse_t msg = calloc (1, sizeof *msg);
+  if (msg)
+    {
+      msg->parts = msg->current_part = new_part ();
+      if (!msg->parts)
+        {
+          free (msg);
+          msg = NULL;
+        }
+      else
+        {
+          msg->callback = cb;
+          msg->callback_value = cb_value;
+          if (do_callback (msg, RFC822PARSE_OPEN))
+            {
+              release_handle_data (msg);
+              free (msg);
+              msg = NULL;
+            }
+        }
+    }
+  return msg;
+}
+
+
+void
+rfc822parse_cancel (rfc822parse_t msg)
+{
+  if (msg)
+    {
+      do_callback (msg, RFC822PARSE_CANCEL);
+      release_handle_data (msg);
+      free (msg);
+    }
+}
+
+
+void
+rfc822parse_close (rfc822parse_t msg)
+{
+  if (msg)
+    {
+      do_callback (msg, RFC822PARSE_CLOSE);
+      release_handle_data (msg);
+      free (msg);
+    }
+}
+
+static part_t
+find_parent (part_t tree, part_t target)
+{
+  part_t part;
+
+  for (part = tree->down; part; part = part->right)
+    {
+      if (part == target)
+        return tree; /* Found. */
+      if (part->down)
+        {
+          part_t tmp = find_parent (part, target);
+          if (tmp)
+            return tmp;
+        }
+    }
+  return NULL;
+}
+
+static void
+set_current_part_to_parent (rfc822parse_t msg)
+{
+  part_t parent;
+
+  assert (msg->current_part);
+  parent = find_parent (msg->parts, msg->current_part);
+  if (!parent)
+    return; /* Already at the top. */
+
+#ifndef NDEBUG
+  {
+    part_t part;
+    for (part = parent->down; part; part = part->right)
+      if (part == msg->current_part)
+        break;
+    assert (part);
+  }
+#endif
+  msg->current_part = parent;
+
+  parent = find_parent (msg->parts, parent);
+  msg->boundary = parent? parent->boundary: NULL;
+}
+
+
+
+/****************
+ * We have read in all header lines and are about to receive the body
+ * part.  The delimiter line has already been processed.
+ *
+ * FIXME: we's better return an error in case of memory failures.
+ */
+static int
+transition_to_body (rfc822parse_t msg)
+{
+  rfc822parse_field_t ctx;
+  int rc;
+
+  rc = do_callback (msg, RFC822PARSE_T2BODY);
+  if (!rc)
+    {
+      /* Store the boundary if we have multipart type. */
+      ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
+      if (ctx)
+        {
+          const char *s;
+
+          s = rfc822parse_query_media_type (ctx, NULL);
+          if (s && !strcmp (s,"multipart"))
+            {
+              s = rfc822parse_query_parameter (ctx, "boundary", 0);
+              if (s)
+                {
+                  assert (!msg->current_part->boundary);
+                  msg->current_part->boundary = malloc (strlen (s) + 1);
+                  if (msg->current_part->boundary) 
+                    {
+                      part_t part;
+                  
+                      strcpy (msg->current_part->boundary, s);
+                      msg->boundary = msg->current_part->boundary;
+                      part = new_part ();
+                      if (!part)
+                        {
+                          int save_errno = errno;
+                          rfc822parse_release_field (ctx);
+                          errno = save_errno;
+                          return -1;
+                        }
+                      rc = do_callback (msg, RFC822PARSE_LEVEL_DOWN);
+                      assert (!msg->current_part->down);
+                      msg->current_part->down = part;
+                      msg->current_part = part;
+                      msg->in_preamble = 1;
+                    }
+                }
+            }
+          rfc822parse_release_field (ctx);
+        }
+    }
+
+  return rc;
+}
+
+/* We have just passed a MIME boundary and need to prepare for new part.
+   headers. */
+static int
+transition_to_header (rfc822parse_t msg)
+{
+  part_t part;
+
+  assert (msg->current_part);
+  assert (!msg->current_part->right);
+
+  part = new_part ();
+  if (!part)
+    return -1;
+
+  msg->current_part->right = part;
+  msg->current_part = part;
+  return 0;
+}
+
+
+static int
+insert_header (rfc822parse_t msg, const unsigned char *line, size_t length)
+{
+  HDR_LINE hdr;
+
+  assert (msg->current_part);
+  if (!length)
+    {
+      msg->in_body = 1;
+      return transition_to_body (msg);
+    }
+
+  if (!msg->current_part->hdr_lines)
+    do_callback (msg, RFC822PARSE_BEGIN_HEADER);
+
+  length = length_sans_trailing_ws (line, length);
+  hdr = malloc (sizeof (*hdr) + length);
+  if (!hdr)
+    return -1;
+  hdr->next = NULL;
+  hdr->cont = (*line == ' ' || *line == '\t');
+  memcpy (hdr->line, line, length);
+  hdr->line[length] = 0; /* Make it a string. */
+  
+  /* Transform a field name into canonical format. */
+  if (!hdr->cont && strchr (line, ':'))
+     capitalize_header_name (hdr->line);
+
+  *msg->current_part->hdr_lines_tail = hdr;
+  msg->current_part->hdr_lines_tail = &hdr->next;
+
+  /* Lets help the caller to prevent mail loops and issue an event for
+   * every Received header. */
+  if (length >= 9 && !memcmp (line, "Received:", 9))
+     do_callback (msg, RFC822PARSE_RCVD_SEEN);
+  return 0;
+}
+
+
+/****************
+ * Note: We handle the body transparent to allow binary zeroes in it.
+ */
+static int
+insert_body (rfc822parse_t msg, const unsigned char *line, size_t length)
+{
+  int rc = 0;
+
+  if (length > 2 && *line == '-' && line[1] == '-' && msg->boundary)
+    {
+      size_t blen = strlen (msg->boundary);
+
+      if (length == blen + 2
+          && !memcmp (line+2, msg->boundary, blen))
+        {
+          rc = do_callback (msg, RFC822PARSE_BOUNDARY);
+          msg->in_body = 0;
+          if (!rc && !msg->in_preamble)
+            rc = transition_to_header (msg);
+          msg->in_preamble = 0;
+        }
+      else if (length == blen + 4
+          && line[length-2] =='-' && line[length-1] == '-'
+          && !memcmp (line+2, msg->boundary, blen))
+        {
+          rc = do_callback (msg, RFC822PARSE_LAST_BOUNDARY);
+          msg->boundary = NULL; /* No current boundary anymore. */
+          set_current_part_to_parent (msg);
+
+          /* Fixme: The next should acctually be sent right before the
+             next boundary, so that we can mark the epilogue. */
+          if (!rc)
+            rc = do_callback (msg, RFC822PARSE_LEVEL_UP);
+        }
+    }
+  if (msg->in_preamble && !rc)
+    rc = do_callback (msg, RFC822PARSE_PREAMBLE);
+
+  return rc;
+}
+
+/* Insert the next line into the parser. Return 0 on success or true
+   on error with errno set appropriately. */
+int
+rfc822parse_insert (rfc822parse_t msg, const unsigned char *line, size_t length)
+{
+  return (msg->in_body 
+          ? insert_body (msg, line, length)
+          : insert_header (msg, line, length));
+}
+
+
+/* Tell the parser that we have finished the message. */
+int
+rfc822parse_finish (rfc822parse_t msg)
+{
+  return do_callback (msg, RFC822PARSE_FINISH);
+}
+
+
+
+/****************
+ * Get a copy of a header line. The line is returned as one long
+ * string with LF to separate the continuation line. Caller must free
+ * the return buffer.  which may be used to enumerate over all lines.
+ * Wildcards are allowed.  This function works on the current headers;
+ * i.e. the regular mail headers or the MIME headers of the current
+ * part.
+ *
+ * WHICH gives the mode:
+ *  -1 := Take the last occurence
+ *   n := Take the n-th  one.
+ * 
+ * Returns a newly allocated buffer or NULL on error.  errno is set in
+ * case of a memory failure or set to 0 if the requested field is not
+ * available.
+ */
+char *
+rfc822parse_get_field (rfc822parse_t msg, const char *name, int which)
+{
+  HDR_LINE h, h2;
+  char *buf, *p;
+  size_t n;
+
+  h = find_header (msg, name, which, NULL);
+  if (!h)
+    {
+      errno = 0;
+      return NULL; /* no such field */
+    }
+
+  n = strlen (h->line) + 1;
+  for (h2 = h->next; h2 && h2->cont; h2 = h2->next)
+    n += strlen (h2->line) + 1;
+
+  buf = p = malloc (n);
+  if (buf)
+    {
+      p = stpcpy (p, h->line);
+      *p++ = '\n';
+      for (h2 = h->next; h2 && h2->cont; h2 = h2->next)
+        {
+          p = stpcpy (p, h2->line);
+          *p++ = '\n';
+        }
+      p[-1] = 0;
+    }
+  return buf;
+}
+
+
+/****************
+ * Enumerate all header.  Caller has to provide the address of a pointer
+ * which has to be initialzed to NULL, the caller should then never change this
+ * pointer until he has closed the enumeration by passing again the address
+ * of the pointer but with msg set to NULL.
+ * The function returns pointers to all the header lines or NULL when
+ * all lines have been enumerated or no headers are available.
+ */
+const char *
+rfc822parse_enum_header_lines (rfc822parse_t msg, void **context)
+{
+  HDR_LINE l;
+
+  if (!msg) /* Close. */
+    return NULL;       
+
+  if (*context == msg || !msg->current_part)
+    return NULL;
+
+  l = *context ? (HDR_LINE) *context : msg->current_part->hdr_lines;
+
+  if (l)
+    {
+      *context = l->next ? (void *) (l->next) : (void *) msg;
+      return l->line;
+    }
+  *context = msg; /* Mark end of list. */
+  return NULL;
+}
+
+
+
+/****************
+ * Find a header field.  If the Name does end in an asterisk this is meant
+ * to be a wildcard.
+ *
+ *  which  -1 : Retrieve the last field
+ *        >0 : Retrieve the n-th field
+
+ * RPREV may be used to return the predecessor of the returned field;
+ * which may be NULL for the very first one. It has to be initialzed
+ * to either NULL in which case the search start at the first header line,
+ * or it may point to a headerline, where the search should start
+ */
+static HDR_LINE
+find_header (rfc822parse_t msg, const char *name, int which, HDR_LINE *rprev)
+{
+  HDR_LINE hdr, prev = NULL, mark = NULL;
+  unsigned char *p;
+  size_t namelen, n;
+  int found = 0;
+  int glob = 0;
+
+  if (!msg->current_part)
+    return NULL;
+
+  namelen = strlen (name);
+  if (namelen && name[namelen - 1] == '*')
+    {
+      namelen--;
+      glob = 1;
+    }
+
+  hdr = msg->current_part->hdr_lines;
+  if (rprev && *rprev)
+    {
+      /* spool forward to the requested starting place.
+       * we cannot simply set this as we have to return
+       * the previous list element too */
+      for (; hdr && hdr != *rprev; prev = hdr, hdr = hdr->next)
+       ;
+    }
+
+  for (; hdr; prev = hdr, hdr = hdr->next)
+    {
+      if (hdr->cont)
+       continue;
+      if (!(p = strchr (hdr->line, ':')))
+       continue;               /* invalid header, just skip it. */
+      n = p - hdr->line;
+      if (!n)
+       continue;               /* invalid name */
+      if ((glob ? (namelen <= n) : (namelen == n))
+         && !memcmp (hdr->line, name, namelen))
+       {
+         found++;
+         if (which == -1)
+           mark = hdr;
+         else if (found == which)
+           {
+             if (rprev)
+               *rprev = prev;
+             return hdr;
+           }
+       }
+    }
+  if (mark && rprev)
+    *rprev = prev;
+  return mark;
+}
+
+
+
+static const char *
+skip_ws (const char *s)
+{
+  while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
+    s++;
+  return s;
+}
+
+
+static void
+release_token_list (TOKEN t)
+{
+  while (t)
+    {
+      TOKEN t2 = t->next;
+      /* fixme: If we have owner_pantry, put the token back to
+       * this pantry so that it can be reused later */
+      free (t);
+      t = t2;
+    }
+}
+
+
+static TOKEN
+new_token (enum token_type type, const char *buf, size_t length)
+{
+  TOKEN t;
+
+  /* fixme: look through our pantries to find a suitable
+   * token for reuse */
+  t = malloc (sizeof *t + length);
+  if (t)
+    {
+      t->next = NULL;
+      t->type = type;
+      memset (&t->flags, 0, sizeof (t->flags));
+      t->data[0] = 0;
+      if (buf)
+        {
+          memcpy (t->data, buf, length);
+          t->data[length] = 0; /* Make sure it is a C string. */
+        }
+      else
+        t->data[0] = 0;
+    }
+  return t;
+}
+
+static TOKEN
+append_to_token (TOKEN old, const char *buf, size_t length)
+{
+  size_t n = strlen (old->data);
+  TOKEN t;
+
+  t = malloc (sizeof *t + n + length);
+  if (t)
+    {
+      t->next = old->next;
+      t->type = old->type;
+      t->flags = old->flags;
+      memcpy (t->data, old->data, n);
+      memcpy (t->data + n, buf, length);
+      t->data[n + length] = 0;
+      old->next = NULL;
+      release_token_list (old);
+    }
+  return t;
+}
+
+
+
+/*
+   Parse a field into tokens as defined by rfc822.
+ */
+static TOKEN
+parse_field (HDR_LINE hdr)
+{
+  static const char specials[] = "<>@.,;:\\[]\"()";
+  static const char specials2[] = "<>@.,;:";
+  static const char tspecials[] = "/?=<>@,;:\\[]\"()";
+  static const char tspecials2[] = "/?=<>@.,;:";
+  static struct 
+  {
+    const unsigned char *name;
+    size_t namelen;
+  } tspecial_header[] = {
+    { "Content-Type", 12},
+    { "Content-Transfer-Encoding", 25},
+    { NULL, 0}
+  };
+  const char *delimiters;
+  const char *delimiters2;
+  const unsigned char *line, *s, *s2;
+  size_t n;
+  int i, invalid = 0;
+  TOKEN t, tok, *tok_tail;
+
+  errno = 0;
+  if (!hdr)
+    return NULL;
+
+  tok = NULL;
+  tok_tail = &tok;
+
+  line = hdr->line;
+  if (!(s = strchr (line, ':')))
+    return NULL; /* oops */
+
+  n = s - line;
+  if (!n)
+    return NULL; /* oops: invalid name */
+
+  delimiters = specials;
+  delimiters2 = specials2;
+  for (i = 0; tspecial_header[i].name; i++)
+    {
+      if (n == tspecial_header[i].namelen
+         && !memcmp (line, tspecial_header[i].name, n))
+       {
+         delimiters = tspecials;
+         delimiters2 = tspecials2;
+         break;
+       }
+    }
+
+  s++; /* Move over the colon. */
+  for (;;)
+    {
+      if (!*s)
+       {
+         if (!hdr->next || !hdr->next->cont)
+           break;
+         hdr = hdr->next;
+         s = hdr->line;
+       }
+
+      if (*s == '(')
+       {
+         int level = 1;
+         int in_quote = 0;
+
+         invalid = 0;
+         for (s++;; s++)
+           {
+             if (!*s)
+               {
+                 if (!hdr->next || !hdr->next->cont)
+                   break;
+                 hdr = hdr->next;
+                 s = hdr->line;
+               }
+
+             if (in_quote)
+               {
+                 if (*s == '\"')
+                   in_quote = 0;
+                 else if (*s == '\\' && s[1])  /* what about continuation? */
+                   s++;
+               }
+             else if (*s == ')')
+               {
+                 if (!--level)
+                   break;
+               }
+             else if (*s == '(')
+               level++;
+             else if (*s == '\"')
+               in_quote = 1;
+           }
+         if (!*s)
+           ; /* Actually this is an error, but we don't care about it. */
+         else
+           s++;
+       }
+      else if (*s == '\"' || *s == '[')
+       {
+         /* We do not check for non-allowed nesting of domainliterals */
+         int term = *s == '\"' ? '\"' : ']';
+         invalid = 0;
+         s++;
+         t = NULL;
+
+         for (;;)
+           {
+             for (s2 = s; *s2; s2++)
+               {
+                 if (*s2 == term)
+                   break;
+                 else if (*s2 == '\\' && s2[1]) /* what about continuation? */
+                   s2++;
+               }
+              
+             t = (t
+                   ? append_to_token (t, s, s2 - s)
+                   : new_token (term == '\"'? tQUOTED : tDOMAINLIT, s, s2 - s));
+              if (!t)
+                goto failure;
+                   
+             if (*s2 || !hdr->next || !hdr->next->cont)
+               break;
+             hdr = hdr->next;
+             s = hdr->line;
+           }
+         *tok_tail = t;
+         tok_tail = &t->next;
+         s = s2;
+         if (*s)
+           s++; /* skip the delimiter */
+       }
+      else if ((s2 = strchr (delimiters2, *s)))
+       { /* Special characters which are not handled above. */
+         invalid = 0;
+         t = new_token (tSPECIAL, s, 1);
+          if (!t)
+            goto failure;
+         *tok_tail = t;
+         tok_tail = &t->next;
+         s++;
+       }
+      else if (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
+       {
+         invalid = 0;
+         s = skip_ws (s + 1);
+       }
+      else if (*s > 0x20 && !(*s & 128))
+       { /* Atom. */
+         invalid = 0;
+         for (s2 = s + 1; *s2 > 0x20
+              && !(*s2 & 128) && !strchr (delimiters, *s2); s2++)
+           ;
+         t = new_token (tATOM, s, s2 - s);
+          if (!t)
+            goto failure;
+         *tok_tail = t;
+         tok_tail = &t->next;
+         s = s2;
+       }
+      else
+       { /* Invalid character. */
+         if (!invalid)
+           { /* For parsing we assume only one space. */
+             t = new_token (tSPACE, NULL, 0);
+              if (!t)
+                goto failure;
+             *tok_tail = t;
+             tok_tail = &t->next;
+             invalid = 1;
+           }
+         s++;
+       }
+    }
+
+  return tok;
+
+ failure:
+  {
+    int save = errno;
+    release_token_list (tok);
+    errno = save;
+  }
+  return NULL;
+}
+
+
+
+
+/****************
+ * Find and parse a header field.
+ * WHICH indicates what to do if there are multiple instance of the same
+ * field (like "Received"); the following value are defined:
+ *  -1 := Take the last occurence
+ *   0 := Reserved
+ *   n := Take the n-th one.
+ * Returns a handle for further operations on the parse context of the field
+ * or NULL if the field was not found.
+ */
+rfc822parse_field_t
+rfc822parse_parse_field (rfc822parse_t msg, const char *name, int which)
+{
+  HDR_LINE hdr;
+
+  if (!which)
+    return NULL;
+
+  hdr = find_header (msg, name, which, NULL);
+  if (!hdr)
+    return NULL;
+  return parse_field (hdr);
+}
+
+void
+rfc822parse_release_field (rfc822parse_field_t ctx)
+{
+  if (ctx)
+    release_token_list (ctx);
+}
+
+
+
+/****************
+ * Check whether T points to a parameter.
+ * A parameter starts with a semicolon and it is assumed that t
+ * points to exactly this one.
+ */
+static int
+is_parameter (TOKEN t)
+{
+  t = t->next;
+  if (!t || t->type != tATOM)
+    return 0;
+  t = t->next;
+  if (!t || !(t->type == tSPECIAL && t->data[0] == '='))
+    return 0;
+  t = t->next;
+  if (!t)
+    return 1; /* We assume that an non existing value is an empty one. */
+  return t->type == tQUOTED || t->type == tATOM;
+}
+
+/*
+   Some header (Content-type) have a special syntax where attribute=value
+   pairs are used after a leading semicolon.  The parse_field code
+   knows about these fields and changes the parsing to the one defined
+   in RFC2045.
+   Returns a pointer to the value which is valid as long as the
+   parse context is valid; NULL is returned in case that attr is not
+   defined in the header, a missing value is reppresented by an empty string.
+   With LOWER_VALUE set to true, a matching field valuebe be
+   lowercased.
+   Note, that ATTR should be lowercase.
+ */
+const char *
+rfc822parse_query_parameter (rfc822parse_field_t ctx, const char *attr,
+                             int lower_value)
+{
+  TOKEN t, a;
+
+  for (t = ctx; t; t = t->next)
+    {
+      /* skip to the next semicolon */
+      for (; t && !(t->type == tSPECIAL && t->data[0] == ';'); t = t->next)
+       ;
+      if (!t)
+       return NULL;
+      if (is_parameter (t))
+       { /* Look closer. */
+         a = t->next; /* We know that this is an atom */
+          if ( !a->flags.lowered )
+            {
+              lowercase_string (a->data);
+              a->flags.lowered = 1;
+            }
+         if (!strcmp (a->data, attr))
+           { /* found */
+             t = a->next->next;
+             /* Either T is now an atom, a quoted string or NULL in
+              * which case we return an empty string. */
+
+              if ( lower_value && t && !t->flags.lowered )
+                {
+                  lowercase_string (t->data);
+                  t->flags.lowered = 1;
+                }
+             return t ? t->data : "";
+           }
+       }
+    }
+  return NULL;
+}
+
+/****************
+ * This function may be used for the Content-Type header to figure out
+ * the media type and subtype.  Note, that the returned strings are
+ * guaranteed to be lowercase as required by MIME.
+ *
+ * Returns: a pointer to the media type and if subtype is not NULL,
+ *         a pointer to the subtype.
+ */
+const char *
+rfc822parse_query_media_type (rfc822parse_field_t ctx, const char **subtype)
+{
+  TOKEN t = ctx;
+  const char *type;
+
+  if (t->type != tATOM)
+    return NULL;
+  if (!t->flags.lowered)
+    {
+      lowercase_string (t->data);
+      t->flags.lowered = 1;
+    }
+  type = t->data;
+  t = t->next;
+  if (!t || t->type != tSPECIAL || t->data[0] != '/')
+    return NULL;
+  t = t->next;
+  if (!t || t->type != tATOM)
+    return NULL;
+
+  if (subtype)
+    {
+      if (!t->flags.lowered)
+        {
+          lowercase_string (t->data);
+          t->flags.lowered = 1;
+        }
+      *subtype = t->data;
+    }
+  return type;
+}
+
+
+
+
+
+#ifdef TESTING
+
+/* Internal debug function to print the structure of the message. */
+static void
+dump_structure (rfc822parse_t msg, part_t part, int indent)
+{
+  if (!part)
+    {
+      printf ("*** Structure of this message:\n");
+      part = msg->parts;
+    }
+
+  for (; part; part = part->right)
+    {
+      rfc822parse_field_t ctx;
+      part_t save_part; /* ugly hack - we should have a function to
+                           get part inforation. */
+      const char *s;
+      
+      save_part = msg->current_part;
+      msg->current_part = part;
+      ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
+      msg->current_part = save_part;
+      if (ctx)
+        {
+          const char *s1, *s2;
+          s1 = rfc822parse_query_media_type (ctx, &s2);
+          if (s1)
+            printf ("***   %*s %s/%s", indent*2, "", s1, s2);
+          else
+            printf ("***   %*s [not found]", indent*2, "");
+
+          s = rfc822parse_query_parameter (ctx, "boundary", 0);
+          if (s)
+            printf (" (boundary=\"%s\")", s);
+          rfc822parse_release_field (ctx);
+        }
+      else
+        printf ("***   %*s text/plain [assumed]", indent*2, "");
+      putchar('\n');
+
+      if (part->down)
+        dump_structure (msg, part->down, indent + 1);
+    }
+  
+}
+
+
+
+static void
+show_param (rfc822parse_field_t ctx, const char *name)
+{
+  const char *s;
+
+  if (!ctx)
+    return;
+  s = rfc822parse_query_parameter (ctx, name, 0);
+  if (s)
+    printf ("***   %s: `%s'\n", name, s);
+}
+
+
+
+static void
+show_event (rfc822parse_event_t event)
+{
+  const char *s;
+
+  switch (event)
+    {
+    case RFC822PARSE_OPEN: s= "Open"; break;
+    case RFC822PARSE_CLOSE: s= "Close"; break;
+    case RFC822PARSE_CANCEL: s= "Cancel"; break;
+    case RFC822PARSE_T2BODY: s= "T2Body"; break;
+    case RFC822PARSE_FINISH: s= "Finish"; break;
+    case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break;
+    case RFC822PARSE_BOUNDARY: s= "Boundary"; break;
+    case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break;
+    default: s= "***invalid event***"; break;
+    }
+  printf ("*** got RFC822 event %s\n", s);
+}
+
+static int
+msg_cb (void *dummy_arg, rfc822parse_event_t event, rfc822parse_t msg)
+{
+  show_event (event);
+  if (event == RFC822PARSE_T2BODY)
+    {
+      rfc822parse_field_t ctx;
+      void *ectx;
+      const char *line;
+
+      for (ectx=NULL; (line = rfc822parse_enum_header_lines (msg, &ectx)); )
+        {
+          printf ("*** HDR: %s\n", line);
+       }
+      rfc822parse_enum_header_lines (NULL, &ectx); /* Close enumerator. */
+
+      ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
+      if (ctx)
+        {
+          const char *s1, *s2;
+          s1 = rfc822parse_query_media_type (ctx, &s2);
+          if (s1)
+            printf ("***   media: `%s/%s'\n", s1, s2);
+          else
+            printf ("***   media: [not found]\n");
+          show_param (ctx, "boundary");
+          show_param (ctx, "protocol");
+          rfc822parse_release_field (ctx);
+        }
+      else
+        printf ("***   media: text/plain [assumed]\n");
+      
+    }
+
+
+  return 0;
+}
+
+
+
+int
+main (int argc, char **argv)
+{
+  char line[5000];
+  size_t length;
+  rfc822parse_t msg;
+
+  msg = rfc822parse_open (msg_cb, NULL);
+  if (!msg)
+    abort ();
+
+  while (fgets (line, sizeof (line), stdin))
+    {
+      length = strlen (line);
+      if (length && line[length - 1] == '\n')
+       line[--length] = 0;
+      if (length && line[length - 1] == '\r')
+       line[--length] = 0;
+      if (rfc822parse_insert (msg, line, length))
+       abort ();
+    }
+
+  dump_structure (msg, NULL, 0);
+
+  rfc822parse_close (msg);
+  return 0;
+}
+#endif
+
+/*
+Local Variables:
+compile-command: "gcc -Wall -g -DTESTING -o rfc822parse rfc822parse.c"
+End:
+*/
diff --git a/tools/rfc822parse.h b/tools/rfc822parse.h
new file mode 100644 (file)
index 0000000..1293117
--- /dev/null
@@ -0,0 +1,79 @@
+/* rfc822parse.h - Simple mail and MIME parser
+ *     Copyright (C) 1999 Werner Koch, Duesseldorf
+ *      Copyright (C) 2003, g10 Code GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef RFC822PARSE_H
+#define RFC822PARSE_H
+
+struct rfc822parse_context;
+typedef struct rfc822parse_context *rfc822parse_t;
+
+typedef enum 
+  {
+    RFC822PARSE_OPEN = 1,
+    RFC822PARSE_CLOSE,
+    RFC822PARSE_CANCEL,
+    RFC822PARSE_T2BODY,
+    RFC822PARSE_FINISH,
+    RFC822PARSE_RCVD_SEEN,
+    RFC822PARSE_LEVEL_DOWN,
+    RFC822PARSE_LEVEL_UP,
+    RFC822PARSE_BOUNDARY,
+    RFC822PARSE_LAST_BOUNDARY,
+    RFC822PARSE_BEGIN_HEADER,
+    RFC822PARSE_PREAMBLE,
+    RFC822PARSE_EPILOGUE
+  } 
+rfc822parse_event_t;
+
+struct rfc822parse_field_context;
+typedef struct rfc822parse_field_context *rfc822parse_field_t;
+
+
+typedef int (*rfc822parse_cb_t) (void *opaque,
+                                 rfc822parse_event_t event,
+                                 rfc822parse_t msg);
+
+
+rfc822parse_t rfc822parse_open (rfc822parse_cb_t cb, void *opaque_value);
+
+void rfc822parse_close (rfc822parse_t msg);
+
+void rfc822parse_cancel (rfc822parse_t msg);
+int rfc822parse_finish (rfc822parse_t msg);
+
+int rfc822parse_insert (rfc822parse_t msg,
+                        const unsigned char *line, size_t length);
+
+char *rfc822parse_get_field (rfc822parse_t msg, const char *name, int which);
+
+const char *rfc822parse_enum_header_lines (rfc822parse_t msg, void **context);
+
+rfc822parse_field_t rfc822parse_parse_field (rfc822parse_t msg,
+                                             const char *name,
+                                             int which);
+
+void rfc822parse_release_field (rfc822parse_field_t field);
+
+const char *rfc822parse_query_parameter (rfc822parse_field_t ctx,
+                                         const char *attr, int lower_value);
+
+const char *rfc822parse_query_media_type (rfc822parse_field_t ctx,
+                                          const char **subtype);
+
+#endif /*RFC822PARSE_H */