From 38e7c4c50ad8a1026886f975c5a8be2ac8438e35 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 16 Dec 2005 15:52:48 +0000 Subject: [PATCH] Fixed importing certs created by newer versions of Mozilla. --- NEWS | 5 + TODO | 4 + agent/ChangeLog | 10 ++ agent/minip12.c | 285 +++++++++++++++++++++++++++++++++++-------- doc/tools.texi | 12 ++ tools/ChangeLog | 2 + tools/Makefile.am | 6 +- tools/gpgparsemail.c | 6 +- tools/rfc822parse.c | 11 +- 9 files changed, 285 insertions(+), 56 deletions(-) diff --git a/NEWS b/NEWS index edf29885d..350920580 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,11 @@ Noteworthy changes in version 1.9.20 * [scdaemon] Support for keypads of some readers. Tested only with SPR532. New option --disable-keypad. + * New debug tool gpgparsemail. + + * Importing pkcs#12 files created be recent versions of Mozilla works + again. + Noteworthy changes in version 1.9.19 (2005-09-12) ------------------------------------------------- diff --git a/TODO b/TODO index 50f58cee9..7a1f989b4 100644 --- a/TODO +++ b/TODO @@ -94,3 +94,7 @@ might want to have an agent context for each service request * sm/ ** --include-certs is as of now still a dummy command line option +** check that we issue NO_SECKEY xxx if a -u key was not found + +* gpg/ +** issue a NO_SECKEY xxxx if a -u key was not found. diff --git a/agent/ChangeLog b/agent/ChangeLog index 105178730..0aef39487 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,13 @@ +2005-12-16 Werner Koch + + * minip12.c (cram_octet_string): New + (p12_parse): Use it for NDEFed bags. + (parse_bag_data): Ditto. + (string_to_key, set_key_iv, crypt_block): New arg SALTLEN. + (p12_build): Use old value 8 for new arg. + (parse_bag_encrypted_data, parse_bag_data): Allow for salts of 8 + to 16 bytes. Add new arg R_CONSUMED. + 2005-11-24 Werner Koch * minip12.c (p12_parse): Fixed for case that the key object comes diff --git a/agent/minip12.c b/agent/minip12.c index 55f3946bf..e65bf0617 100644 --- a/agent/minip12.c +++ b/agent/minip12.c @@ -141,7 +141,8 @@ struct tag_info /* Parse the buffer at the address BUFFER which is of SIZE and return the tag and the length part from the TLV triplet. Update BUFFER - and SIZE on success. */ + and SIZE on success. Checks that the encoded length does not + exhaust the length of the provided buffer. */ static int parse_tag (unsigned char const **buffer, size_t *size, struct tag_info *ti) { @@ -221,8 +222,76 @@ parse_tag (unsigned char const **buffer, size_t *size, struct tag_info *ti) } +/* Given an ASN.1 chunk of a structure like: + + 24 NDEF: OCTET STRING -- This is not passed to us + 04 1: OCTET STRING -- INPUT point s to here + : 30 + 04 1: OCTET STRING + : 80 + [...] + 04 2: OCTET STRING + : 00 00 + : } -- This denotes a Null tag and are the last + -- two bytes in INPUT. + + Create a new buffer with the content of that octet string. INPUT + is the orginal buffer with a length as stored at LENGTH. Returns + NULL on error or a new malloced buffer with the length of this new + buffer stored at LENGTH and the number of bytes parsed from input + are added to the value stored at INPUT_CONSUMED. INPUT_CONSUMED is + allowed to be passed as NULL if the caller is not interested in + this value. */ +static unsigned char * +cram_octet_string (const unsigned char *input, size_t *length, + size_t *input_consumed) +{ + const unsigned char *s = input; + size_t n = *length; + unsigned char *output, *d; + struct tag_info ti; + + /* Allocate output buf. We know that it won't be longer than the + input buffer. */ + d = output = gcry_malloc (n); + if (!output) + goto bailout; + + for (;;) + { + if (parse_tag (&s, &n, &ti)) + goto bailout; + if (ti.class == UNIVERSAL && ti.tag == TAG_OCTET_STRING + && !ti.ndef && !ti.is_constructed) + { + memcpy (d, s, ti.length); + s += ti.length; + d += ti.length; + n -= ti.length; + } + else if (ti.class == UNIVERSAL && !ti.tag && !ti.is_constructed) + break; /* Ready */ + else + goto bailout; + } + + + *length = d - output; + if (input_consumed) + *input_consumed += s - input; + return output; + + bailout: + if (input_consumed) + *input_consumed += s - input; + gcry_free (output); + return NULL; +} + + + static int -string_to_key (int id, char *salt, int iter, const char *pw, +string_to_key (int id, char *salt, size_t saltlen, int iter, const char *pw, int req_keylen, unsigned char *keybuf) { int rc, i, j; @@ -241,10 +310,16 @@ string_to_key (int id, char *salt, int iter, const char *pw, return -1; } + if (saltlen < 8) + { + log_error ("salt too short\n"); + return -1; + } + /* Store salt and password in BUF_I */ p = buf_i; for(i=0; i < 64; i++) - *p++ = salt [i%8]; + *p++ = salt [i%saltlen]; for(i=j=0; i < 64; i += 2) { *p++ = 0; @@ -314,14 +389,14 @@ string_to_key (int id, char *salt, int iter, const char *pw, static int -set_key_iv (gcry_cipher_hd_t chd, char *salt, int iter, const char *pw, - int keybytes) +set_key_iv (gcry_cipher_hd_t chd, char *salt, size_t saltlen, int iter, + const char *pw, int keybytes) { unsigned char keybuf[24]; int rc; assert (keybytes == 5 || keybytes == 24); - if (string_to_key (1, salt, iter, pw, keybytes, keybuf)) + if (string_to_key (1, salt, saltlen, iter, pw, keybytes, keybuf)) return -1; rc = gcry_cipher_setkey (chd, keybuf, keybytes); if (rc) @@ -330,7 +405,7 @@ set_key_iv (gcry_cipher_hd_t chd, char *salt, int iter, const char *pw, return -1; } - if (string_to_key (2, salt, iter, pw, 8, keybuf)) + if (string_to_key (2, salt, saltlen, iter, pw, 8, keybuf)) return -1; rc = gcry_cipher_setiv (chd, keybuf, 8); if (rc) @@ -343,8 +418,8 @@ set_key_iv (gcry_cipher_hd_t chd, char *salt, int iter, const char *pw, static void -crypt_block (unsigned char *buffer, size_t length, char *salt, int iter, - const char *pw, int cipher_algo, int encrypt) +crypt_block (unsigned char *buffer, size_t length, char *salt, size_t saltlen, + int iter, const char *pw, int cipher_algo, int encrypt) { gcry_cipher_hd_t chd; int rc; @@ -356,7 +431,7 @@ crypt_block (unsigned char *buffer, size_t length, char *salt, int iter, wipememory (buffer, length); return; } - if (set_key_iv (chd, salt, iter, pw, + if (set_key_iv (chd, salt, saltlen, iter, pw, cipher_algo == GCRY_CIPHER_RFC2268_40? 5:24)) { wipememory (buffer, length); @@ -381,18 +456,22 @@ crypt_block (unsigned char *buffer, size_t length, char *salt, int iter, static int parse_bag_encrypted_data (const unsigned char *buffer, size_t length, - int startoffset, const char *pw, + int startoffset, size_t *r_consumed, const char *pw, void (*certcb)(void*, const unsigned char*, size_t), void *certcbarg) { struct tag_info ti; const unsigned char *p = buffer; + const unsigned char *p_start = buffer; size_t n = length; const char *where; - char salt[8]; + char salt[16]; + size_t saltlen; unsigned int iter; unsigned char *plain = NULL; int bad_pass = 0; + unsigned char *cram_buffer = NULL; + size_t consumed = 0; /* Number of bytes consumed from the orginal buffer. */ where = "start"; if (parse_tag (&p, &n, &ti)) @@ -449,11 +528,13 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length, goto bailout; if (parse_tag (&p, &n, &ti)) goto bailout; - if (ti.class || ti.tag != TAG_OCTET_STRING || ti.length != 8 ) + if (ti.class || ti.tag != TAG_OCTET_STRING + || ti.length < 8 || ti.length > 16 ) goto bailout; - memcpy (salt, p, 8); - p += 8; - n -= 8; + saltlen = ti.length; + memcpy (salt, p, saltlen); + p += saltlen; + n -= saltlen; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class || ti.tag != TAG_INTEGER || !ti.length ) @@ -468,7 +549,25 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length, where = "rc2-ciphertext"; if (parse_tag (&p, &n, &ti)) goto bailout; - if (ti.class != CONTEXT || ti.tag != 0 || !ti.length ) + + consumed = p - p_start; + if (ti.class == CONTEXT && ti.tag == 0 && ti.is_constructed && ti.ndef) + { + /* Mozilla exported certs now come with single byte chunks of + octect strings. (Mozilla Firefox 1.0.4). Arghh. */ + where = "cram-rc2-ciphertext"; + cram_buffer = cram_octet_string ( p, &n, &consumed); + if (!cram_buffer) + goto bailout; + p = p_start = cram_buffer; + if (r_consumed) + *r_consumed = consumed; + r_consumed = NULL; /* Ugly hack to not update that value any further. */ + ti.length = n; + } + else if (ti.class == CONTEXT && ti.tag == 0 && ti.length ) + ; + else goto bailout; log_info ("%lu bytes of RC2 encrypted text\n", ti.length); @@ -480,10 +579,11 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length, goto bailout; } memcpy (plain, p, ti.length); - crypt_block (plain, ti.length, salt, iter, pw, GCRY_CIPHER_RFC2268_40, 0); + crypt_block (plain, ti.length, salt, saltlen, + iter, pw, GCRY_CIPHER_RFC2268_40, 0); n = ti.length; startoffset = 0; - buffer = p = plain; + p_start = p = plain; /* { */ /* # warning debug code is enabled */ @@ -615,13 +715,19 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length, } } + if (r_consumed) + *r_consumed = consumed; gcry_free (plain); - + gcry_free (cram_buffer); return 0; + bailout: + if (r_consumed) + *r_consumed = consumed; gcry_free (plain); + gcry_free (cram_buffer); log_error ("encryptedData error at \"%s\", offset %u\n", - where, (p - buffer)+startoffset); + where, (p - p_start)+startoffset); if (bad_pass) { /* Note, that the following string might be used by other programs @@ -634,19 +740,23 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length, static gcry_mpi_t * parse_bag_data (const unsigned char *buffer, size_t length, int startoffset, - const char *pw) + size_t *r_consumed, const char *pw) { int rc; struct tag_info ti; const unsigned char *p = buffer; + const unsigned char *p_start = buffer; size_t n = length; const char *where; - char salt[8]; + char salt[16]; + size_t saltlen; unsigned int iter; int len; unsigned char *plain = NULL; gcry_mpi_t *result = NULL; int result_count, i; + unsigned char *cram_buffer = NULL; + size_t consumed = 0; /* Number of bytes consumed from the orginal buffer. */ where = "start"; if (parse_tag (&p, &n, &ti)) @@ -658,6 +768,22 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset, if (ti.class || ti.tag != TAG_OCTET_STRING) goto bailout; + consumed = p - p_start; + if (ti.is_constructed && ti.ndef) + { + /* Mozilla exported certs now come with single byte chunks of + octect strings. (Mozilla Firefox 1.0.4). Arghh. */ + where = "cram-data.outersegs"; + cram_buffer = cram_octet_string ( p, &n, &consumed); + if (!cram_buffer) + goto bailout; + p = p_start = cram_buffer; + if (r_consumed) + *r_consumed = consumed; + r_consumed = NULL; /* Ugly hack to not update that value any further. */ + } + + where = "data.outerseqs"; if (parse_tag (&p, &n, &ti)) goto bailout; @@ -709,11 +835,13 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset, goto bailout; if (parse_tag (&p, &n, &ti)) goto bailout; - if (ti.class || ti.tag != TAG_OCTET_STRING || ti.length != 8 ) + if (ti.class || ti.tag != TAG_OCTET_STRING + || ti.length < 8 || ti.length > 16) goto bailout; - memcpy (salt, p, 8); - p += 8; - n -= 8; + saltlen = ti.length; + memcpy (salt, p, saltlen); + p += saltlen; + n -= saltlen; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class || ti.tag != TAG_INTEGER || !ti.length ) @@ -740,10 +868,11 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset, goto bailout; } memcpy (plain, p, ti.length); - crypt_block (plain, ti.length, salt, iter, pw, GCRY_CIPHER_3DES, 0); + consumed += p - p_start + ti.length; + crypt_block (plain, ti.length, salt, saltlen, iter, pw, GCRY_CIPHER_3DES, 0); n = ti.length; startoffset = 0; - buffer = p = plain; + p_start = p = plain; /* { */ /* # warning debug code is enabled */ @@ -828,6 +957,9 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset, if (len) goto bailout; + gcry_free (cram_buffer); + if (r_consumed) + *r_consumed = consumed; return result; bailout: @@ -838,8 +970,11 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset, gcry_mpi_release (result[i]); gcry_free (result); } + gcry_free (cram_buffer); log_error ( "data error at \"%s\", offset %u\n", where, (p - buffer) + startoffset); + if (r_consumed) + *r_consumed = consumed; return NULL; } @@ -857,10 +992,13 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, { struct tag_info ti; const unsigned char *p = buffer; + const unsigned char *p_start = buffer; size_t n = length; const char *where; int bagseqlength, len; + int bagseqndef, lenndef; gcry_mpi_t *result = NULL; + unsigned char *cram_buffer = NULL; where = "pfx"; if (parse_tag (&p, &n, &ti)) @@ -897,71 +1035,121 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, if (ti.class != UNIVERSAL || ti.tag != TAG_OCTET_STRING) goto bailout; + if (ti.is_constructed && ti.ndef) + { + /* Mozilla exported certs now come with single byte chunks of + octect strings. (Mozilla Firefox 1.0.4). Arghh. */ + where = "cram-bags"; + cram_buffer = cram_octet_string ( p, &n, NULL); + if (!cram_buffer) + goto bailout; + p = p_start = cram_buffer; + } + where = "bags"; if (parse_tag (&p, &n, &ti)) goto bailout; if (ti.class != UNIVERSAL || ti.tag != TAG_SEQUENCE) goto bailout; + bagseqndef = ti.ndef; bagseqlength = ti.length; - while (bagseqlength) + while (bagseqlength || bagseqndef) { - /*log_debug ( "at offset %u\n", (p - buffer));*/ + log_debug ( "at offset %u\n", (p - p_start)); where = "bag-sequence"; if (parse_tag (&p, &n, &ti)) goto bailout; + if (bagseqndef && ti.class == UNIVERSAL && !ti.tag && !ti.is_constructed) + break; /* Ready */ if (ti.class != UNIVERSAL || ti.tag != TAG_SEQUENCE) goto bailout; - if (bagseqlength < ti.nhdr) - goto bailout; - bagseqlength -= ti.nhdr; - if (bagseqlength < ti.length) - goto bailout; - bagseqlength -= ti.length; + if (!bagseqndef) + { + if (bagseqlength < ti.nhdr) + goto bailout; + bagseqlength -= ti.nhdr; + if (bagseqlength < ti.length) + goto bailout; + bagseqlength -= ti.length; + } + lenndef = ti.ndef; len = ti.length; if (parse_tag (&p, &n, &ti)) goto bailout; - len -= ti.nhdr; + if (lenndef) + len = ti.nhdr; + else + len -= ti.nhdr; + if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_encryptedData) && !memcmp (p, oid_encryptedData, DIM(oid_encryptedData))) { + size_t consumed = 0; + p += DIM(oid_encryptedData); n -= DIM(oid_encryptedData); - len -= DIM(oid_encryptedData); + if (!lenndef) + len -= DIM(oid_encryptedData); where = "bag.encryptedData"; - if (parse_bag_encrypted_data (p, n, (p - buffer), pw, + if (parse_bag_encrypted_data (p, n, (p - p_start), &consumed, pw, certcb, certcbarg)) goto bailout; + if (lenndef) + len += consumed; } else if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_data) - && !memcmp (p, oid_data, DIM(oid_data))) + && !memcmp (p, oid_data, DIM(oid_data))) { if (result) - log_info ("already got an data object, skipping next one\n"); + { + log_info ("already got an data object, skipping next one\n"); + p += ti.length; + n -= ti.length; + } else { + size_t consumed = 0; + p += DIM(oid_data); n -= DIM(oid_data); - len -= DIM(oid_data); - result = parse_bag_data (p, n, (p-buffer), pw); + if (!lenndef) + len -= DIM(oid_data); + result = parse_bag_data (p, n, (p - p_start), &consumed, pw); if (!result) goto bailout; + if (lenndef) + len += consumed; } } else - log_info ( "unknown bag type - skipped\n"); + { + log_info ("unknown bag type - skipped\n"); + p += ti.length; + n -= ti.length; + } if (len < 0 || len > n) goto bailout; p += len; n -= len; + if (lenndef) + { + /* Need to skip the Null Tag. */ + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (!(ti.class == UNIVERSAL && !ti.tag && !ti.is_constructed)) + goto bailout; + } } + gcry_free (cram_buffer); return result; bailout: - log_error ("error at \"%s\", offset %u\n", where, (p - buffer)); + log_error ("error at \"%s\", offset %u\n", where, (p - p_start)); /* fixme: need to release RESULT. */ + gcry_free (cram_buffer); return NULL; } @@ -1586,7 +1774,8 @@ p12_build (gcry_mpi_t *kparms, unsigned char *cert, size_t certlen, /* Encrypt it. */ gcry_randomize (salt, 8, GCRY_STRONG_RANDOM); - crypt_block (buffer, buflen, salt, 2048, pw, GCRY_CIPHER_RFC2268_40, 1); + crypt_block (buffer, buflen, salt, 8, 2048, pw, + GCRY_CIPHER_RFC2268_40, 1); /* Encode the encrypted stuff into a bag. */ seqlist[seqlistidx].buffer = build_cert_bag (buffer, buflen, salt, &n); @@ -1607,7 +1796,7 @@ p12_build (gcry_mpi_t *kparms, unsigned char *cert, size_t certlen, /* Encrypt it. */ gcry_randomize (salt, 8, GCRY_STRONG_RANDOM); - crypt_block (buffer, buflen, salt, 2048, pw, GCRY_CIPHER_3DES, 1); + crypt_block (buffer, buflen, salt, 8, 2048, pw, GCRY_CIPHER_3DES, 1); /* Encode the encrypted stuff into a bag. */ seqlist[seqlistidx].buffer = build_key_bag (buffer, buflen, salt, &n); diff --git a/doc/tools.texi b/doc/tools.texi index 850202bd5..d39d950f4 100644 --- a/doc/tools.texi +++ b/doc/tools.texi @@ -14,6 +14,7 @@ GnuPG comes with a couple of smaller tools: * gpgsm-gencert.sh:: Generate an X.509 certificate request. * gpg-preset-passphrase:: Put a passphrase into the cache. * gpg-connect-agent:: Communicate with a running agent. +* gpgparsemail:: Parse a mail message into an annotated format * symcryptrun:: Call a simple symmetric encryption tool. @end menu @@ -773,6 +774,17 @@ be used to directly connect to any Assuan style socket server. @end table +@c +@c GPGPARSEMAIL +@c +@node gpgparsemail +@section Parse a mail message into an annotated format + +The @command{gpgparsemail} is a utility currentlu only useful for +debugging. Run it with @code{--help} for usage information. + + + @c @c SYMCRYPTRUN @c diff --git a/tools/ChangeLog b/tools/ChangeLog index 9b2afd42b..b0b7499eb 100644 --- a/tools/ChangeLog +++ b/tools/ChangeLog @@ -1,5 +1,7 @@ 2005-12-14 Werner Koch + * Makefile.am (bin_PROGRAMS): Build gpgparsemail. + * gpgparsemail.c (pkcs7_begin): New. (parse_message, message_cb): Add support of direct pkcs signatures. diff --git a/tools/Makefile.am b/tools/Makefile.am index 508e52bc5..519e87150 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -18,7 +18,6 @@ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA EXTRA_DIST = Manifest watchgnupg.c \ - rfc822parse.c rfc822parse.h gpgparsemail.c \ addgnupghome gpgsm-gencert.sh AM_CPPFLAGS = -I$(top_srcdir)/gl -I$(top_srcdir)/intl -I$(top_srcdir)/common @@ -36,7 +35,7 @@ else symcryptrun = endif -bin_PROGRAMS = gpgconf gpg-connect-agent gpgkey2ssh ${symcryptrun} +bin_PROGRAMS = gpgconf gpg-connect-agent gpgkey2ssh ${symcryptrun} gpgparsemail if !HAVE_W32_SYSTEM bin_PROGRAMS += watchgnupg endif @@ -46,6 +45,9 @@ gpgconf_SOURCES = gpgconf.c gpgconf.h gpgconf-comp.c no-libgcrypt.c gpgconf_LDADD = ../jnlib/libjnlib.a \ ../common/libcommon.a ../gl/libgnu.a @LIBINTL@ +gpgparsemail_SOURCES = gpgparsemail.c rfc822parse.c rfc822parse.h +gpgparsemail_LDADD = + symcryptrun_SOURCES = symcryptrun.c symcryptrun_LDADD = $(LIBUTIL_LIBS) ../jnlib/libjnlib.a \ ../common/libcommon.a ../gl/libgnu.a \ diff --git a/tools/gpgparsemail.c b/tools/gpgparsemail.c index dcc38c3b8..da56093c3 100644 --- a/tools/gpgparsemail.c +++ b/tools/gpgparsemail.c @@ -21,8 +21,8 @@ /* 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. */ + for the content of the line. Several options are available to + scrutinize the message. S/MIME and OpenPGP support is included. */ #include @@ -708,6 +708,8 @@ main (int argc, char **argv) " --debug enable additional debug output\n" " --help display this help and exit\n\n" "With no FILE, or when FILE is -, read standard input.\n\n" + "WARNING: This tool is under development.\n" + " The semantics may change without notice\n\n" "Report bugs to ."); exit (0); } diff --git a/tools/rfc822parse.c b/tools/rfc822parse.c index df3b2e7a4..303ddad13 100644 --- a/tools/rfc822parse.c +++ b/tools/rfc822parse.c @@ -155,7 +155,7 @@ capitalize_header_name (unsigned char *name) *name = *name - 'A' + 'a'; } - +#ifndef HAVE_STPCPY static char * stpcpy (char *a,const char *b) { @@ -165,6 +165,7 @@ stpcpy (char *a,const char *b) return (char*)a; } +#endif /* If a callback has been registerd, call it for the event of type @@ -474,7 +475,7 @@ insert_body (rfc822parse_t msg, const unsigned char *line, size_t length) msg->boundary = NULL; /* No current boundary anymore. */ set_current_part_to_parent (msg); - /* Fixme: The next should acctually be sent right before the + /* Fixme: The next should actually be send right before the next boundary, so that we can mark the epilogue. */ if (!rc) rc = do_callback (msg, RFC822PARSE_LEVEL_UP); @@ -523,7 +524,8 @@ rfc822parse_finish (rfc822parse_t msg) * available. * * If VALUEOFF is not NULL it will receive the offset of the first non - * space character in th value of the line. + * space character in the value part of the line (i.e. after the first + * colon). */ char * rfc822parse_get_field (rfc822parse_t msg, const char *name, int which, @@ -758,7 +760,8 @@ parse_field (HDR_LINE hdr) static const char specials[] = "<>@.,;:\\[]\"()"; static const char specials2[] = "<>@.,;:"; static const char tspecials[] = "/?=<>@,;:\\[]\"()"; - static const char tspecials2[] = "/?=<>@.,;:"; + static const char tspecials2[] = "/?=<>@.,;:"; /* FIXME: really + include '.'?*/ static struct { const unsigned char *name;