summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWerner Koch <wk@gnupg.org>2004-01-27 17:40:42 +0100
committerWerner Koch <wk@gnupg.org>2004-01-27 17:40:42 +0100
commiteb24d8b751750cf96cb200f80b45ed3806648883 (patch)
treee78feb11795ff62728bc443352006ae33e50ec9c
parent* call-scd.c (atfork_cb): New. (diff)
downloadgnupg2-eb24d8b751750cf96cb200f80b45ed3806648883.tar.xz
gnupg2-eb24d8b751750cf96cb200f80b45ed3806648883.zip
Some minor bug fixes, new test utilities and started support for other
smartcard applications.
-rw-r--r--ChangeLog4
-rw-r--r--README4
-rw-r--r--TODO1
-rw-r--r--agent/ChangeLog6
-rw-r--r--agent/Makefile.am3
-rw-r--r--common/ChangeLog6
-rw-r--r--common/Makefile.am1
-rw-r--r--common/sexp-parse.h (renamed from agent/sexp-parse.h)0
-rw-r--r--common/util.h3
-rw-r--r--configure.ac2
-rw-r--r--scd/ChangeLog65
-rw-r--r--scd/Makefile.am24
-rw-r--r--scd/apdu.c2
-rw-r--r--scd/apdu.h5
-rw-r--r--scd/app-common.h70
-rw-r--r--scd/app-dinsig.c129
-rw-r--r--scd/app-nks.c388
-rw-r--r--scd/app-openpgp.c139
-rw-r--r--scd/app.c131
-rw-r--r--scd/card.c6
-rw-r--r--scd/ccid-driver.c9
-rw-r--r--scd/command.c127
-rw-r--r--scd/iso7816.c192
-rw-r--r--scd/iso7816.h8
-rw-r--r--scd/sc-copykeys.c2
-rw-r--r--scd/sc-investigate.c480
-rw-r--r--scd/scdaemon.c4
-rw-r--r--scd/scdaemon.h3
-rw-r--r--scd/tlv.c219
-rw-r--r--scd/tlv.h84
-rw-r--r--tools/gpgparsemail.c705
-rw-r--r--tools/rfc822parse.c1235
-rw-r--r--tools/rfc822parse.h79
33 files changed, 3860 insertions, 276 deletions
diff --git a/ChangeLog b/ChangeLog
index 0752eec64..84abc4dee 100644
--- 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 42099d6f6..84fc8967b 100644
--- 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 12fd998aa..621b278ba 100644
--- 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.
diff --git a/agent/ChangeLog b/agent/ChangeLog
index bd009ecbe..57f9214f6 100644
--- a/agent/ChangeLog
+++ b/agent/ChangeLog
@@ -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.
@@ -437,7 +441,7 @@
* 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>
diff --git a/agent/Makefile.am b/agent/Makefile.am
index 400aa2fd2..65af03368 100644
--- a/agent/Makefile.am
+++ b/agent/Makefile.am
@@ -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 \
diff --git a/common/ChangeLog b/common/ChangeLog
index 1b454fa13..8e5c615d9 100644
--- a/common/ChangeLog
+++ b/common/ChangeLog
@@ -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.
diff --git a/common/Makefile.am b/common/Makefile.am
index 79dedca34..770ed12d6 100644
--- a/common/Makefile.am
+++ b/common/Makefile.am
@@ -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 \
diff --git a/agent/sexp-parse.h b/common/sexp-parse.h
index 89aa7210f..89aa7210f 100644
--- a/agent/sexp-parse.h
+++ b/common/sexp-parse.h
diff --git a/common/util.h b/common/util.h
index 80a1d01a6..7e134e846 100644
--- a/common/util.h
+++ b/common/util.h
@@ -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))
diff --git a/configure.ac b/configure.ac
index b0c8f2379..cd0486f4b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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
diff --git a/scd/ChangeLog b/scd/ChangeLog
index 0862d356b..3a6a6aea4 100644
--- a/scd/ChangeLog
+++ b/scd/ChangeLog
@@ -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
diff --git a/scd/Makefile.am b/scd/Makefile.am
index a2ecd3a81..c8bf3d0de 100644
--- a/scd/Makefile.am
+++ b/scd/Makefile.am
@@ -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
-
-
-
-
-
diff --git a/scd/apdu.c b/scd/apdu.c
index 02038b65c..e5295f566 100644
--- a/scd/apdu.c
+++ b/scd/apdu.c
@@ -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)
{
diff --git a/scd/apdu.h b/scd/apdu.h
index 21e2b9840..fd7634f13 100644
--- a/scd/apdu.h
+++ b/scd/apdu.h
@@ -27,13 +27,16 @@
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,
diff --git a/scd/app-common.h b/scd/app-common.h
index de1e02cac..cda17700f 100644
--- a/scd/app-common.h
+++ b/scd/app-common.h
@@ -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
index 000000000..4b5b517eb
--- /dev/null
+++ b/scd/app-dinsig.c
@@ -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
index 000000000..0a04f7511
--- /dev/null
+++ b/scd/app-nks.c
@@ -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;
+}
+
+
diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c
index 8f9a303fe..75e3e299e 100644
--- a/scd/app-openpgp.c
+++ b/scd/app-openpgp.c
@@ -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");
diff --git a/scd/app.c b/scd/app.c
index 1f142ea32..6ac18272b 100644
--- a/scd/app.c
+++ b/scd/app.c
@@ -29,49 +29,78 @@
#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;
}
-
-
-
-
-
diff --git a/scd/card.c b/scd/card.c
index 95149074d..53c89f3a4 100644
--- a/scd/card.c
+++ b/scd/card.c
@@ -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)
diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c
index 936672cc6..f910722eb 100644
--- a/scd/ccid-driver.c
+++ b/scd/ccid-driver.c
@@ -98,6 +98,11 @@
# 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;
}
diff --git a/scd/command.c b/scd/command.c
index bc3132a0b..9e571f228 100644
--- a/scd/command.c
+++ b/scd/command.c
@@ -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)
diff --git a/scd/iso7816.c b/scd/iso7816.c
index f4aa18c6f..9a15ce953 100644
--- a/scd/iso7816.c
+++ b/scd/iso7816.c
@@ -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;
+}
+
diff --git a/scd/iso7816.h b/scd/iso7816.h
index 26b8d6aba..98e688693 100644
--- a/scd/iso7816.h
+++ b/scd/iso7816.h
@@ -27,6 +27,10 @@
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*/
diff --git a/scd/sc-copykeys.c b/scd/sc-copykeys.c
index b56b88590..78cb2acc8 100644
--- a/scd/sc-copykeys.c
+++ b/scd/sc-copykeys.c
@@ -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));
diff --git a/scd/sc-investigate.c b/scd/sc-investigate.c
index ecd385690..acef86ead 100644
--- a/scd/sc-investigate.c
+++ b/scd/sc-investigate.c
@@ -24,6 +24,13 @@
#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"
@@ -32,17 +39,25 @@
#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:
+ ;
+}
+
diff --git a/scd/scdaemon.c b/scd/scdaemon.c
index 91ac93227..c6652c8dc 100644
--- a/scd/scdaemon.c
+++ b/scd/scdaemon.c
@@ -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;
diff --git a/scd/scdaemon.h b/scd/scdaemon.h
index e13377af7..2bbf271da 100644
--- a/scd/scdaemon.h
+++ b/scd/scdaemon.h
@@ -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
index 000000000..dbcd24546
--- /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
index 000000000..26a9905f7
--- /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
index 000000000..956cf18d9
--- /dev/null
+++ b/tools/gpgparsemail.c
@@ -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
index 000000000..be1cf4a47
--- /dev/null
+++ b/tools/rfc822parse.c
@@ -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
index 000000000..1293117ac
--- /dev/null
+++ b/tools/rfc822parse.h
@@ -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 */