diff --git a/NEWS b/NEWS index 5c871dd8..8f700fde 100644 --- a/NEWS +++ b/NEWS @@ -7,9 +7,13 @@ Noteworthy changes in version 1.4.3 (unreleased) * Under Windows the default engines names are first searched in the installation directory of the gpgme DLL. + * New function gpgme_data_identify to detect the type of a message. + * Interface changes relative to the 1.4.2 release: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ gpgme_signers_count NEW. + gpgme_data_type_t NEW. + gpgme_data_identify NEW. Noteworthy changes in version 1.4.2 (2013-05-28) diff --git a/doc/gpgme.texi b/doc/gpgme.texi index 08e2f0f0..4ec0bfe2 100644 --- a/doc/gpgme.texi +++ b/doc/gpgme.texi @@ -1885,6 +1885,7 @@ be used to manipulate both. @menu * Data Buffer I/O Operations:: I/O operations on data buffers. * Data Buffer Meta-Data:: Meta-data manipulation of data buffers. +* Data Buffer Convenience:: Convenience fucntion for data buffers. @end menu @@ -2047,6 +2048,56 @@ The function @code{gpgme_data_set_encoding} changes the encoding of the data object with the handle @var{dh} to @var{enc}. @end deftypefun +@node Data Buffer Convenience +@subsection Data Buffer Convenience Functions +@cindex data buffer, convenience +@cindex type of data +@cindex identify + +@deftp {Data type} {enum gpgme_data_type_t} +@tindex gpgme_data_type_t +The @code{gpgme_data_type_t} type is used to return the detected type +of the content of a data buffer. +@end deftp + +@table @code +@item GPGME_DATA_TYPE_INVALID +This is returned by @code{gpgme_data_identify} if it was not possible +to identify the data. Reasons for this might be a non-seekable stream +or a memory problem. The value is 0. +@item GPGME_DATA_TYPE_UNKNOWN +The type of the data is not known. +@item GPGME_DATA_TYPE_PGP_SIGNED +The data is an OpenPGP signed message. This may be a binary +signature, a detached one or a cleartext signature. +@item GPGME_DATA_TYPE_PGP_OTHER +This is a generic OpenPGP message. In most cases this will be +encrypted data. +@item GPGME_DATA_TYPE_PGP_KEY +This is an OpenPGP key (private or public). +@item GPGME_DATA_TYPE_CMS_SIGNED +This is a CMS signed message. +@item GPGME_DATA_TYPE_CMS_ENCRYPTED +This is a CMS encrypted (enveloped data) message. +@item GPGME_DATA_TYPE_CMS_OTHER +This is used for other CMS message types. +@item GPGME_DATA_TYPE_X509_CERT +The data is a X.509 certificate +@item GPGME_DATA_TYPE_PKCS12 +The data is a PKCS#12 message. This is commonly used to exchange +private keys for X.509. +@end table + +@deftypefun gpgme_data_type_t gpgme_data_identify (@w{gpgme_data_t @var{dh}}) +The function @code{gpgme_data_identify} returns the type of the data +with the handle @var{dh}. If it is not possible to perform the +identification, the function returns zero +(@code{GPGME_DATA_TYPE_INVALID}). Note that depending on how the data +object has been created the identification may not be possible or the +data object may change its internal state (file pointer moved). For +file or memory based data object, the state should not change. +@end deftypefun + @c @c Chapter Contexts diff --git a/src/Makefile.am b/src/Makefile.am index 37e34071..1f951039 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -103,8 +103,9 @@ endif # unresolved symbols to the thread module. main_sources = \ util.h conversion.c get-env.c context.h ops.h \ + parsetlv.c parsetlv.h \ data.h data.c data-fd.c data-stream.c data-mem.c data-user.c \ - data-compat.c \ + data-compat.c data-identify.c \ signers.c sig-notation.c \ wait.c wait-global.c wait-private.c wait-user.c wait.h \ op-support.c \ diff --git a/src/data-identify.c b/src/data-identify.c new file mode 100644 index 00000000..96006335 --- /dev/null +++ b/src/data-identify.c @@ -0,0 +1,247 @@ +/* data-identify.c - Try to identify the data + Copyright (C) 2013 g10 Code GmbH + + This file is part of GPGME. + + GPGME is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of + the License, or (at your option) any later version. + + GPGME is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this program; if not, see . + */ + +#if HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "gpgme.h" +#include "data.h" +#include "util.h" +#include "parsetlv.h" + +/* The size of the sample data we take for detection. */ +#define SAMPLE_SIZE 2048 + + + +/* Note that DATA may be binary but a final nul is required so that + string operations will find a terminator. + + Returns: GPGME_DATA_TYPE_xxxx */ +static gpgme_data_type_t +basic_detection (const char *data, size_t datalen) +{ + tlvinfo_t ti; + const char *s; + size_t n; + int maybe_p12 = 0; + + if (datalen < 24) /* Object is probably too short for detection. */ + return GPGME_DATA_TYPE_UNKNOWN; + + /* This is a common example of a CMS object - it is obvious that we + only need to read a few bytes to get to the OID: + 30 82 0B 59 06 09 2A 86 48 86 F7 0D 01 07 02 A0 82 0B 4A 30 82 0B 46 02 + ----------- ++++++++++++++++++++++++++++++++ + SEQUENCE OID (signedData) + (2 byte len) + + A PKCS#12 message is: + + 30 82 08 59 02 01 03 30 82 08 1F 06 09 2A 86 48 86 F7 0D 01 07 01 A0 82 + ----------- ++++++++ ----------- ++++++++++++++++++++++++++++++++ + SEQUENCE INTEGER SEQUENCE OID (data) + + A X.509 certificate is: + + 30 82 05 B8 30 82 04 A0 A0 03 02 01 02 02 07 15 46 A0 BF 30 07 39 30 0D + ----------- +++++++++++ ----- ++++++++ -------------------------- + SEQUENCE SEQUENCE [0] INTEGER INTEGER SEQU + (tbs) (version) (s/n) (Algo) + + Thus we need to read at least 22 bytes, we add 2 bytes to cope with + length headers stored with 4 bytes. + */ + + + s = data; + n = datalen; + + if (parse_tlv (&s, &n, &ti)) + goto try_pgp; /* Not properly BER encoded. */ + if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_SEQUENCE + && ti.is_cons)) + goto try_pgp; /* A CMS object always starts with a sequence. */ + + if (parse_tlv (&s, &n, &ti)) + goto try_pgp; /* Not properly BER encoded. */ + if (ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_SEQUENCE + && ti.is_cons && n >= ti.length) + { + if (parse_tlv (&s, &n, &ti)) + goto try_pgp; + if (!(ti.cls == ASN1_CLASS_CONTEXT && ti.tag == 0 + && ti.is_cons && ti.length == 3 && n >= ti.length)) + goto try_pgp; + + if (parse_tlv (&s, &n, &ti)) + goto try_pgp; + if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_INTEGER + && !ti.is_cons && ti.length == 1 && n && (*s == 1 || *s == 2))) + goto try_pgp; + s++; + n--; + if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_INTEGER + && !ti.is_cons)) + goto try_pgp; + /* Because the now following S/N may be larger than the sample + data we have, we stop parsing here and don't check for the + algorithm ID. */ + return GPGME_DATA_TYPE_X509_CERT; + } + if (ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_INTEGER + && !ti.is_cons && ti.length == 1 && n && *s == 3) + { + maybe_p12 = 1; + s++; + n--; + if (parse_tlv (&s, &n, &ti)) + goto try_pgp; + if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_SEQUENCE + && ti.is_cons)) + goto try_pgp; + if (parse_tlv (&s, &n, &ti)) + goto try_pgp; + } + if (ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_OBJECT_ID + && !ti.is_cons && ti.length && n >= ti.length) + { + if (ti.length == 9) + { + if (!memcmp (s, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x01", 9)) + { + /* Data. */ + return (maybe_p12 ? GPGME_DATA_TYPE_PKCS12 + /* */ : GPGME_DATA_TYPE_CMS_OTHER); + } + if (!memcmp (s, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x02", 9)) + { + /* Signed Data. */ + return (maybe_p12 ? GPGME_DATA_TYPE_PKCS12 + /* */ : GPGME_DATA_TYPE_CMS_SIGNED); + } + if (!memcmp (s, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x03", 9)) + return GPGME_DATA_TYPE_CMS_ENCRYPTED; /* Enveloped Data. */ + if (!memcmp (s, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x05", 9)) + return GPGME_DATA_TYPE_CMS_OTHER; /* Digested Data. */ + if (!memcmp (s, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x06", 9)) + return GPGME_DATA_TYPE_CMS_OTHER; /* Encrypted Data. */ + } + else if (ti.length == 11) + { + if (!memcmp (s, "\x2A\x86\x48\x86\xF7\x0D\x01\x09\x10\x01\x02", 11)) + return GPGME_DATA_TYPE_CMS_OTHER; /* Auth Data. */ + } + } + + + try_pgp: + /* Check whether this might be a non-armored PGP message. We need + to do this before checking for armor lines, so that we don't get + fooled by armored messages inside a signed binary PGP message. */ + if ((data[0] & 0x80)) + { + /* That might be a binary PGP message. At least it is not plain + ASCII. Of course this might be certain lead-in text of + armored CMS messages. However, I am not sure whether this is + at all defined and in any case it is uncommon. Thus we don't + do any further plausibility checks but stupidly assume no CMS + armored data will follow. */ + return GPGME_DATA_TYPE_UNKNOWN; + } + + /* Now check whether there are armor lines. */ + for (s = data; s && *s; s = (*s=='\n')?(s+1):((s=strchr (s,'\n'))?(s+1):s)) + { + if (!strncmp (s, "-----BEGIN ", 11)) + { + if (!strncmp (s+11, "SIGNED ", 7)) + return GPGME_DATA_TYPE_CMS_SIGNED; + if (!strncmp (s+11, "ENCRYPTED ", 10)) + return GPGME_DATA_TYPE_CMS_ENCRYPTED; + if (!strncmp (s+11, "PGP ", 4)) + { + if (!strncmp (s+15, "SIGNATURE", 9)) + return GPGME_DATA_TYPE_PGP_SIGNED; + if (!strncmp (s+15, "SIGNED MESSAGE", 14)) + return GPGME_DATA_TYPE_PGP_SIGNED; + if (!strncmp (s+15, "PUBLIC KEY BLOCK", 16)) + return GPGME_DATA_TYPE_PGP_KEY; + if (!strncmp (s+15, "PRIVATE KEY BLOCK", 17)) + return GPGME_DATA_TYPE_PGP_KEY; + if (!strncmp (s+15, "SECRET KEY BLOCK", 16)) + return GPGME_DATA_TYPE_PGP_KEY; + if (!strncmp (s+15, "ARMORED FILE", 12)) + return GPGME_DATA_TYPE_UNKNOWN; + return GPGME_DATA_TYPE_PGP_OTHER; /* PGP MESSAGE */ + } + if (!strncmp (s+11, "CERTIFICATE", 11)) + return GPGME_DATA_TYPE_X509_CERT; + if (!strncmp (s+11, "PKCS12", 6)) + return GPGME_DATA_TYPE_PKCS12; + return GPGME_DATA_TYPE_CMS_OTHER; /* Not PGP, thus we assume CMS. */ + } + } + + return GPGME_DATA_TYPE_UNKNOWN; +} + + +/* Try to detect the type of the data. Note that this function works + only on seekable data objects. The function tries to reset the + file pointer but there is no guarantee that it will work. + + FIXME: We may want to add internal buffering so that this function + can be implemented for allmost all kind of data objects. + */ +gpgme_data_type_t +gpgme_data_identify (gpgme_data_t dh, int reserved) +{ + gpgme_data_type_t result; + char *sample; + int n; + gpgme_off_t off; + + /* Check whether we can seek the data object. */ + off = gpgme_data_seek (dh, 0, SEEK_CUR); + if (off == (gpgme_off_t)(-1)) + return GPGME_DATA_TYPE_INVALID; + + /* Allocate a buffer and read the data. */ + sample = malloc (SAMPLE_SIZE); + if (!sample) + return GPGME_DATA_TYPE_INVALID; /* Ooops. */ + n = gpgme_data_read (dh, sample, SAMPLE_SIZE - 1); + if (n < 0) + { + free (sample); + return GPGME_DATA_TYPE_INVALID; /* Ooops. */ + } + sample[n] = 0; /* (Required for our string functions.) */ + + result = basic_detection (sample, n); + free (sample); + gpgme_data_seek (dh, off, SEEK_SET); + + return result; +} diff --git a/src/gpgme-tool.c b/src/gpgme-tool.c index 0ebababb..2bf7654a 100644 --- a/src/gpgme-tool.c +++ b/src/gpgme-tool.c @@ -1435,7 +1435,8 @@ typedef enum status STATUS_INCLUDE_CERTS, STATUS_KEYLIST_MODE, STATUS_RECIPIENT, - STATUS_ENCRYPT_RESULT + STATUS_ENCRYPT_RESULT, + STATUS_IDENTIFY_RESULT } status_t; const char *status_string[] = @@ -1448,7 +1449,8 @@ const char *status_string[] = "INCLUDE_CERTS", "KEYLIST_MODE", "RECIPIENT", - "ENCRYPT_RESULT" + "ENCRYPT_RESULT", + "IDENTIFY_RESULT" }; struct gpgme_tool @@ -2065,11 +2067,6 @@ gt_vfs_create (gpgme_tool_t gt, const char *container_file, int flags) } -static const char hlp_passwd[] = - "PASSWD \n" - "\n" - "Ask the backend to change the passphrase for the key\n" - "specified by USER-ID."; gpg_error_t gt_passwd (gpgme_tool_t gt, char *fpr) { @@ -2086,6 +2083,29 @@ gt_passwd (gpgme_tool_t gt, char *fpr) } +gpg_error_t +gt_identify (gpgme_tool_t gt, gpgme_data_t data) +{ + const char *s = "?"; + + switch (gpgme_data_identify (data, 0)) + { + case GPGME_DATA_TYPE_INVALID: return gpg_error (GPG_ERR_GENERAL); + case GPGME_DATA_TYPE_UNKNOWN : s = "unknown"; break; + case GPGME_DATA_TYPE_PGP_SIGNED : s = "PGP-signed"; break; + case GPGME_DATA_TYPE_PGP_OTHER : s = "PGP"; break; + case GPGME_DATA_TYPE_PGP_KEY : s = "PGP-key"; break; + case GPGME_DATA_TYPE_CMS_SIGNED : s = "CMS-signed"; break; + case GPGME_DATA_TYPE_CMS_ENCRYPTED: s = "CMS-encrypted"; break; + case GPGME_DATA_TYPE_CMS_OTHER : s = "CMS"; break; + case GPGME_DATA_TYPE_X509_CERT : s = "X.509"; break; + case GPGME_DATA_TYPE_PKCS12 : s = "PKCS12"; break; + } + gt_write_status (gt, STATUS_IDENTIFY_RESULT, s, NULL); + return 0; +} + + #define GT_RESULT_ENCRYPT 0x1 #define GT_RESULT_DECRYPT 0x2 #define GT_RESULT_SIGN 0x4 @@ -3374,6 +3394,11 @@ cmd_vfs_create (assuan_context_t ctx, char *line) } +static const char hlp_passwd[] = + "PASSWD \n" + "\n" + "Ask the backend to change the passphrase for the key\n" + "specified by USER-ID."; static gpg_error_t cmd_passwd (assuan_context_t ctx, char *line) { @@ -3430,6 +3455,39 @@ cmd_hash_algo_name (assuan_context_t ctx, char *line) } +static const char hlp_identify[] = + "IDENTIY\n" + "\n" + "Identify the type of data set with the INPUT command."; +static gpg_error_t +cmd_identify (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + gpg_error_t err; + assuan_fd_t inp_fd; + char *inp_fn; + gpgme_data_t inp_data; + + inp_fd = server->input_fd; + inp_fn = server->input_filename; + if (inp_fd == ASSUAN_INVALID_FD && !inp_fn) + return GPG_ERR_ASS_NO_INPUT; + + err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data, + &server->input_stream); + if (err) + return err; + + err = gt_identify (server->gt, inp_data); + + gpgme_data_release (inp_data); + server_reset_fds (server); + + return err; +} + + + /* Tell the assuan library about our commands. */ static gpg_error_t register_commands (assuan_context_t ctx) @@ -3488,6 +3546,7 @@ register_commands (assuan_context_t ctx) { "PUBKEY_ALGO_NAME", cmd_pubkey_algo_name }, { "HASH_ALGO_NAME", cmd_hash_algo_name }, { "PASSWD", cmd_passwd, hlp_passwd }, + { "IDENTIFY", cmd_identify, hlp_identify }, { NULL } }; int idx; diff --git a/src/gpgme.def b/src/gpgme.def index 7610d37e..0478cb62 100644 --- a/src/gpgme.def +++ b/src/gpgme.def @@ -211,5 +211,7 @@ EXPORTS gpgme_signers_count @160 + gpgme_data_identify @161 + ; END diff --git a/src/gpgme.h.in b/src/gpgme.h.in index f644a509..5c4de6be 100644 --- a/src/gpgme.h.in +++ b/src/gpgme.h.in @@ -210,6 +210,22 @@ typedef enum } gpgme_data_encoding_t; +/* Known data types. */ +typedef enum + { + GPGME_DATA_TYPE_INVALID = 0, /* Not detected. */ + GPGME_DATA_TYPE_UNKNOWN = 1, + GPGME_DATA_TYPE_PGP_SIGNED = 0x10, + GPGME_DATA_TYPE_PGP_OTHER = 0x12, + GPGME_DATA_TYPE_PGP_KEY = 0x13, + GPGME_DATA_TYPE_CMS_SIGNED = 0x20, + GPGME_DATA_TYPE_CMS_ENCRYPTED= 0x21, + GPGME_DATA_TYPE_CMS_OTHER = 0x22, + GPGME_DATA_TYPE_X509_CERT = 0x23, + GPGME_DATA_TYPE_PKCS12 = 0x24, + } +gpgme_data_type_t; + /* Public key algorithms from libgcrypt. */ typedef enum @@ -1149,6 +1165,9 @@ char *gpgme_data_get_file_name (gpgme_data_t dh); gpgme_error_t gpgme_data_set_file_name (gpgme_data_t dh, const char *file_name); +/* Try to identify the type of the data in DH. */ +gpgme_data_type_t gpgme_data_identify (gpgme_data_t dh, int reserved); + /* Create a new data buffer which retrieves the data from the callback function READ_CB. Deprecated, please use gpgme_data_new_from_cbs diff --git a/src/libgpgme.vers b/src/libgpgme.vers index 0b2e89db..fe18e6a5 100644 --- a/src/libgpgme.vers +++ b/src/libgpgme.vers @@ -29,6 +29,7 @@ GPGME_1.1 { gpgme_data_set_file_name; gpgme_data_get_file_name; + gpgme_data_identify; gpgme_sig_notation_clear; gpgme_sig_notation_add; diff --git a/src/parsetlv.c b/src/parsetlv.c new file mode 100644 index 00000000..70c95189 --- /dev/null +++ b/src/parsetlv.c @@ -0,0 +1,103 @@ +/* parsetlv.c - ASN.1 TLV functions + * Copyright (C) 2005, 2007, 2008, 2012 g10 Code GmbH + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see . + */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include +#include +#include + +#include "parsetlv.h" + + +/* Simple but pretty complete ASN.1 BER parser. Parse the data at the + address of BUFFER with a length given at the address of SIZE. On + success return 0 and update BUFFER and SIZE to point to the value. + Do not update them on error. The information about the object are + stored in the caller allocated TI structure. */ +int +_gpgme_parse_tlv (char const **buffer, size_t *size, tlvinfo_t *ti) +{ + int c; + unsigned long tag; + const unsigned char *buf = (const unsigned char *)(*buffer); + size_t length = *size; + + ti->cls = 0; + ti->tag = 0; + ti->is_cons = 0; + ti->is_ndef = 0; + ti->length = 0; + ti->nhdr = 0; + + if (!length) + return -1; + c = *buf++; length--; ++ti->nhdr; + + ti->cls = (c & 0xc0) >> 6; + ti->is_cons = !!(c & 0x20); + tag = c & 0x1f; + + if (tag == 0x1f) + { + tag = 0; + do + { + tag <<= 7; + if (!length) + return -1; + c = *buf++; length--; ++ti->nhdr; + tag |= c & 0x7f; + } + while (c & 0x80); + } + ti->tag = tag; + + if (!length) + return -1; + c = *buf++; length--; ++ti->nhdr; + + if ( !(c & 0x80) ) + ti->length = c; + else if (c == 0x80) + ti->is_ndef = 1; + else if (c == 0xff) + return -1; + else + { + unsigned long len = 0; + int count = (c & 0x7f); + + if (count > sizeof (len) || count > sizeof (size_t)) + return -1; + + for (; count; count--) + { + len <<= 8; + if (!length) + return -1; + c = *buf++; length--; ++ti->nhdr; + len |= c & 0xff; + } + ti->length = len; + } + + *buffer = (void*)buf; + *size = length; + return 0; +} diff --git a/src/parsetlv.h b/src/parsetlv.h new file mode 100644 index 00000000..153073c1 --- /dev/null +++ b/src/parsetlv.h @@ -0,0 +1,48 @@ +/* parsetlv.h - TLV functions defintions + * Copyright (C) 2012 g10 Code GmbH + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see . + */ + +#ifndef PARSETLV_H +#define PARSETLV_H + +/* ASN.1 constants. */ +#define ASN1_CLASS_UNIVERSAL 0 +#define ASN1_CLASS_APPLICATION 1 +#define ASN1_CLASS_CONTEXT 2 +#define ASN1_CLASS_PRIVATE 3 +#define ASN1_TAG_INTEGER 2 +#define ASN1_TAG_OBJECT_ID 6 +#define ASN1_TAG_SEQUENCE 16 + + +/* Object used with parse_tlv. */ +struct tlvinfo_s +{ + int cls; /* The class of the tag. */ + int tag; /* The tag. */ + int is_cons; /* True if it is a constructed object. */ + int is_ndef; /* True if the object has an indefinite length. */ + size_t length; /* The length of the value. */ + size_t nhdr; /* The number of octets in the header (tag,length). */ +}; +typedef struct tlvinfo_s tlvinfo_t; + +/*-- parsetlv.c --*/ +int _gpgme_parse_tlv (char const **buffer, size_t *size, tlvinfo_t *ti); +#define parse_tlv(a,b,c) _gpgme_parse_tlv ((a), (b), (c)) + + +#endif /*PARSETLV_H*/