1183234Ssimon/* asn_mime.c */ 2280304Sjkim/* 3280304Sjkim * Written by Dr Stephen N Henson (steve@openssl.org) for the OpenSSL 4183234Ssimon * project. 5183234Ssimon */ 6183234Ssimon/* ==================================================================== 7183234Ssimon * Copyright (c) 1999-2008 The OpenSSL Project. All rights reserved. 8183234Ssimon * 9183234Ssimon * Redistribution and use in source and binary forms, with or without 10183234Ssimon * modification, are permitted provided that the following conditions 11183234Ssimon * are met: 12183234Ssimon * 13183234Ssimon * 1. Redistributions of source code must retain the above copyright 14280304Sjkim * notice, this list of conditions and the following disclaimer. 15183234Ssimon * 16183234Ssimon * 2. Redistributions in binary form must reproduce the above copyright 17183234Ssimon * notice, this list of conditions and the following disclaimer in 18183234Ssimon * the documentation and/or other materials provided with the 19183234Ssimon * distribution. 20183234Ssimon * 21183234Ssimon * 3. All advertising materials mentioning features or use of this 22183234Ssimon * software must display the following acknowledgment: 23183234Ssimon * "This product includes software developed by the OpenSSL Project 24183234Ssimon * for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)" 25183234Ssimon * 26183234Ssimon * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to 27183234Ssimon * endorse or promote products derived from this software without 28183234Ssimon * prior written permission. For written permission, please contact 29183234Ssimon * licensing@OpenSSL.org. 30183234Ssimon * 31183234Ssimon * 5. Products derived from this software may not be called "OpenSSL" 32183234Ssimon * nor may "OpenSSL" appear in their names without prior written 33183234Ssimon * permission of the OpenSSL Project. 34183234Ssimon * 35183234Ssimon * 6. Redistributions of any form whatsoever must retain the following 36183234Ssimon * acknowledgment: 37183234Ssimon * "This product includes software developed by the OpenSSL Project 38183234Ssimon * for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)" 39183234Ssimon * 40183234Ssimon * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY 41183234Ssimon * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 42183234Ssimon * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 43183234Ssimon * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR 44183234Ssimon * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 45183234Ssimon * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 46183234Ssimon * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 47183234Ssimon * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 48183234Ssimon * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 49183234Ssimon * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 50183234Ssimon * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 51183234Ssimon * OF THE POSSIBILITY OF SUCH DAMAGE. 52183234Ssimon * ==================================================================== 53183234Ssimon * 54183234Ssimon */ 55183234Ssimon 56183234Ssimon#include <stdio.h> 57183234Ssimon#include <ctype.h> 58183234Ssimon#include "cryptlib.h" 59183234Ssimon#include <openssl/rand.h> 60183234Ssimon#include <openssl/x509.h> 61183234Ssimon#include <openssl/asn1.h> 62183234Ssimon#include <openssl/asn1t.h> 63238405Sjkim#include "asn1_locl.h" 64183234Ssimon 65280304Sjkim/* 66280304Sjkim * Generalised MIME like utilities for streaming ASN1. Although many have a 67280304Sjkim * PKCS7/CMS like flavour others are more general purpose. 68183234Ssimon */ 69183234Ssimon 70280304Sjkim/* 71280304Sjkim * MIME format structures Note that all are translated to lower case apart 72280304Sjkim * from parameter values. Quotes are stripped off 73183234Ssimon */ 74183234Ssimon 75183234Ssimontypedef struct { 76280304Sjkim char *param_name; /* Param name e.g. "micalg" */ 77280304Sjkim char *param_value; /* Param value e.g. "sha1" */ 78183234Ssimon} MIME_PARAM; 79183234Ssimon 80183234SsimonDECLARE_STACK_OF(MIME_PARAM) 81183234SsimonIMPLEMENT_STACK_OF(MIME_PARAM) 82183234Ssimon 83183234Ssimontypedef struct { 84280304Sjkim char *name; /* Name of line e.g. "content-type" */ 85280304Sjkim char *value; /* Value of line e.g. "text/plain" */ 86280304Sjkim STACK_OF(MIME_PARAM) *params; /* Zero or more parameters */ 87183234Ssimon} MIME_HEADER; 88183234Ssimon 89183234SsimonDECLARE_STACK_OF(MIME_HEADER) 90183234SsimonIMPLEMENT_STACK_OF(MIME_HEADER) 91183234Ssimon 92238405Sjkimstatic int asn1_output_data(BIO *out, BIO *data, ASN1_VALUE *val, int flags, 93280304Sjkim const ASN1_ITEM *it); 94280304Sjkimstatic char *strip_ends(char *name); 95280304Sjkimstatic char *strip_start(char *name); 96280304Sjkimstatic char *strip_end(char *name); 97183234Ssimonstatic MIME_HEADER *mime_hdr_new(char *name, char *value); 98183234Ssimonstatic int mime_hdr_addparam(MIME_HEADER *mhdr, char *name, char *value); 99183234Ssimonstatic STACK_OF(MIME_HEADER) *mime_parse_hdr(BIO *bio); 100280304Sjkimstatic int mime_hdr_cmp(const MIME_HEADER *const *a, 101280304Sjkim const MIME_HEADER *const *b); 102280304Sjkimstatic int mime_param_cmp(const MIME_PARAM *const *a, 103280304Sjkim const MIME_PARAM *const *b); 104183234Ssimonstatic void mime_param_free(MIME_PARAM *param); 105183234Ssimonstatic int mime_bound_check(char *line, int linelen, char *bound, int blen); 106183234Ssimonstatic int multi_split(BIO *bio, char *bound, STACK_OF(BIO) **ret); 107183234Ssimonstatic int strip_eol(char *linebuf, int *plen); 108183234Ssimonstatic MIME_HEADER *mime_hdr_find(STACK_OF(MIME_HEADER) *hdrs, char *name); 109183234Ssimonstatic MIME_PARAM *mime_param_find(MIME_HEADER *hdr, char *name); 110183234Ssimonstatic void mime_hdr_free(MIME_HEADER *hdr); 111183234Ssimon 112183234Ssimon#define MAX_SMLEN 1024 113280304Sjkim#define mime_debug(x) /* x */ 114183234Ssimon 115238405Sjkim/* Output an ASN1 structure in BER format streaming if necessary */ 116238405Sjkim 117238405Sjkimint i2d_ASN1_bio_stream(BIO *out, ASN1_VALUE *val, BIO *in, int flags, 118280304Sjkim const ASN1_ITEM *it) 119280304Sjkim{ 120280304Sjkim /* If streaming create stream BIO and copy all content through it */ 121280304Sjkim if (flags & SMIME_STREAM) { 122280304Sjkim BIO *bio, *tbio; 123280304Sjkim bio = BIO_new_NDEF(out, val, it); 124280304Sjkim if (!bio) { 125280304Sjkim ASN1err(ASN1_F_I2D_ASN1_BIO_STREAM, ERR_R_MALLOC_FAILURE); 126280304Sjkim return 0; 127280304Sjkim } 128280304Sjkim SMIME_crlf_copy(in, bio, flags); 129280304Sjkim (void)BIO_flush(bio); 130280304Sjkim /* Free up successive BIOs until we hit the old output BIO */ 131280304Sjkim do { 132280304Sjkim tbio = BIO_pop(bio); 133280304Sjkim BIO_free(bio); 134280304Sjkim bio = tbio; 135280304Sjkim } while (bio != out); 136280304Sjkim } 137280304Sjkim /* 138280304Sjkim * else just write out ASN1 structure which will have all content stored 139280304Sjkim * internally 140280304Sjkim */ 141280304Sjkim else 142280304Sjkim ASN1_item_i2d_bio(it, out, val); 143280304Sjkim return 1; 144280304Sjkim} 145238405Sjkim 146183234Ssimon/* Base 64 read and write of ASN1 structure */ 147183234Ssimon 148183234Ssimonstatic int B64_write_ASN1(BIO *out, ASN1_VALUE *val, BIO *in, int flags, 149280304Sjkim const ASN1_ITEM *it) 150280304Sjkim{ 151280304Sjkim BIO *b64; 152280304Sjkim int r; 153280304Sjkim b64 = BIO_new(BIO_f_base64()); 154280304Sjkim if (!b64) { 155280304Sjkim ASN1err(ASN1_F_B64_WRITE_ASN1, ERR_R_MALLOC_FAILURE); 156280304Sjkim return 0; 157280304Sjkim } 158280304Sjkim /* 159280304Sjkim * prepend the b64 BIO so all data is base64 encoded. 160280304Sjkim */ 161280304Sjkim out = BIO_push(b64, out); 162280304Sjkim r = i2d_ASN1_bio_stream(out, val, in, flags, it); 163280304Sjkim (void)BIO_flush(out); 164280304Sjkim BIO_pop(out); 165280304Sjkim BIO_free(b64); 166280304Sjkim return r; 167280304Sjkim} 168183234Ssimon 169238405Sjkim/* Streaming ASN1 PEM write */ 170238405Sjkim 171238405Sjkimint PEM_write_bio_ASN1_stream(BIO *out, ASN1_VALUE *val, BIO *in, int flags, 172280304Sjkim const char *hdr, const ASN1_ITEM *it) 173280304Sjkim{ 174280304Sjkim int r; 175280304Sjkim BIO_printf(out, "-----BEGIN %s-----\n", hdr); 176280304Sjkim r = B64_write_ASN1(out, val, in, flags, it); 177280304Sjkim BIO_printf(out, "-----END %s-----\n", hdr); 178280304Sjkim return r; 179280304Sjkim} 180238405Sjkim 181183234Ssimonstatic ASN1_VALUE *b64_read_asn1(BIO *bio, const ASN1_ITEM *it) 182183234Ssimon{ 183280304Sjkim BIO *b64; 184280304Sjkim ASN1_VALUE *val; 185280304Sjkim if (!(b64 = BIO_new(BIO_f_base64()))) { 186280304Sjkim ASN1err(ASN1_F_B64_READ_ASN1, ERR_R_MALLOC_FAILURE); 187280304Sjkim return 0; 188280304Sjkim } 189280304Sjkim bio = BIO_push(b64, bio); 190280304Sjkim val = ASN1_item_d2i_bio(it, bio, NULL); 191280304Sjkim if (!val) 192280304Sjkim ASN1err(ASN1_F_B64_READ_ASN1, ASN1_R_DECODE_ERROR); 193280304Sjkim (void)BIO_flush(bio); 194280304Sjkim bio = BIO_pop(bio); 195280304Sjkim BIO_free(b64); 196280304Sjkim return val; 197183234Ssimon} 198183234Ssimon 199183234Ssimon/* Generate the MIME "micalg" parameter from RFC3851, RFC4490 */ 200183234Ssimon 201183234Ssimonstatic int asn1_write_micalg(BIO *out, STACK_OF(X509_ALGOR) *mdalgs) 202280304Sjkim{ 203280304Sjkim const EVP_MD *md; 204280304Sjkim int i, have_unknown = 0, write_comma, ret = 0, md_nid; 205280304Sjkim have_unknown = 0; 206280304Sjkim write_comma = 0; 207280304Sjkim for (i = 0; i < sk_X509_ALGOR_num(mdalgs); i++) { 208280304Sjkim if (write_comma) 209280304Sjkim BIO_write(out, ",", 1); 210280304Sjkim write_comma = 1; 211280304Sjkim md_nid = OBJ_obj2nid(sk_X509_ALGOR_value(mdalgs, i)->algorithm); 212280304Sjkim md = EVP_get_digestbynid(md_nid); 213280304Sjkim if (md && md->md_ctrl) { 214280304Sjkim int rv; 215280304Sjkim char *micstr; 216280304Sjkim rv = md->md_ctrl(NULL, EVP_MD_CTRL_MICALG, 0, &micstr); 217280304Sjkim if (rv > 0) { 218280304Sjkim BIO_puts(out, micstr); 219280304Sjkim OPENSSL_free(micstr); 220280304Sjkim continue; 221280304Sjkim } 222280304Sjkim if (rv != -2) 223280304Sjkim goto err; 224280304Sjkim } 225280304Sjkim switch (md_nid) { 226280304Sjkim case NID_sha1: 227280304Sjkim BIO_puts(out, "sha1"); 228280304Sjkim break; 229183234Ssimon 230280304Sjkim case NID_md5: 231280304Sjkim BIO_puts(out, "md5"); 232280304Sjkim break; 233183234Ssimon 234280304Sjkim case NID_sha256: 235280304Sjkim BIO_puts(out, "sha-256"); 236280304Sjkim break; 237183234Ssimon 238280304Sjkim case NID_sha384: 239280304Sjkim BIO_puts(out, "sha-384"); 240280304Sjkim break; 241183234Ssimon 242280304Sjkim case NID_sha512: 243280304Sjkim BIO_puts(out, "sha-512"); 244280304Sjkim break; 245183234Ssimon 246280304Sjkim case NID_id_GostR3411_94: 247280304Sjkim BIO_puts(out, "gostr3411-94"); 248280304Sjkim goto err; 249280304Sjkim break; 250238405Sjkim 251280304Sjkim default: 252280304Sjkim if (have_unknown) 253280304Sjkim write_comma = 0; 254280304Sjkim else { 255280304Sjkim BIO_puts(out, "unknown"); 256280304Sjkim have_unknown = 1; 257280304Sjkim } 258280304Sjkim break; 259183234Ssimon 260280304Sjkim } 261280304Sjkim } 262183234Ssimon 263280304Sjkim ret = 1; 264280304Sjkim err: 265183234Ssimon 266280304Sjkim return ret; 267238405Sjkim 268280304Sjkim} 269183234Ssimon 270183234Ssimon/* SMIME sender */ 271183234Ssimon 272238405Sjkimint SMIME_write_ASN1(BIO *bio, ASN1_VALUE *val, BIO *data, int flags, 273280304Sjkim int ctype_nid, int econt_nid, 274280304Sjkim STACK_OF(X509_ALGOR) *mdalgs, const ASN1_ITEM *it) 275183234Ssimon{ 276280304Sjkim char bound[33], c; 277280304Sjkim int i; 278280304Sjkim const char *mime_prefix, *mime_eol, *cname = "smime.p7m"; 279280304Sjkim const char *msg_type = NULL; 280280304Sjkim if (flags & SMIME_OLDMIME) 281280304Sjkim mime_prefix = "application/x-pkcs7-"; 282280304Sjkim else 283280304Sjkim mime_prefix = "application/pkcs7-"; 284183234Ssimon 285280304Sjkim if (flags & SMIME_CRLFEOL) 286280304Sjkim mime_eol = "\r\n"; 287280304Sjkim else 288280304Sjkim mime_eol = "\n"; 289280304Sjkim if ((flags & SMIME_DETACHED) && data) { 290280304Sjkim /* We want multipart/signed */ 291280304Sjkim /* Generate a random boundary */ 292306196Sjkim if (RAND_bytes((unsigned char *)bound, 32) <= 0) 293284285Sjkim return 0; 294280304Sjkim for (i = 0; i < 32; i++) { 295280304Sjkim c = bound[i] & 0xf; 296280304Sjkim if (c < 10) 297280304Sjkim c += '0'; 298280304Sjkim else 299280304Sjkim c += 'A' - 10; 300280304Sjkim bound[i] = c; 301280304Sjkim } 302280304Sjkim bound[32] = 0; 303280304Sjkim BIO_printf(bio, "MIME-Version: 1.0%s", mime_eol); 304280304Sjkim BIO_printf(bio, "Content-Type: multipart/signed;"); 305280304Sjkim BIO_printf(bio, " protocol=\"%ssignature\";", mime_prefix); 306280304Sjkim BIO_puts(bio, " micalg=\""); 307280304Sjkim asn1_write_micalg(bio, mdalgs); 308280304Sjkim BIO_printf(bio, "\"; boundary=\"----%s\"%s%s", 309280304Sjkim bound, mime_eol, mime_eol); 310280304Sjkim BIO_printf(bio, "This is an S/MIME signed message%s%s", 311280304Sjkim mime_eol, mime_eol); 312280304Sjkim /* Now write out the first part */ 313280304Sjkim BIO_printf(bio, "------%s%s", bound, mime_eol); 314280304Sjkim if (!asn1_output_data(bio, data, val, flags, it)) 315280304Sjkim return 0; 316280304Sjkim BIO_printf(bio, "%s------%s%s", mime_eol, bound, mime_eol); 317183234Ssimon 318280304Sjkim /* Headers for signature */ 319183234Ssimon 320280304Sjkim BIO_printf(bio, "Content-Type: %ssignature;", mime_prefix); 321280304Sjkim BIO_printf(bio, " name=\"smime.p7s\"%s", mime_eol); 322280304Sjkim BIO_printf(bio, "Content-Transfer-Encoding: base64%s", mime_eol); 323280304Sjkim BIO_printf(bio, "Content-Disposition: attachment;"); 324280304Sjkim BIO_printf(bio, " filename=\"smime.p7s\"%s%s", mime_eol, mime_eol); 325280304Sjkim B64_write_ASN1(bio, val, NULL, 0, it); 326280304Sjkim BIO_printf(bio, "%s------%s--%s%s", mime_eol, bound, 327280304Sjkim mime_eol, mime_eol); 328280304Sjkim return 1; 329280304Sjkim } 330183234Ssimon 331280304Sjkim /* Determine smime-type header */ 332183234Ssimon 333280304Sjkim if (ctype_nid == NID_pkcs7_enveloped) 334280304Sjkim msg_type = "enveloped-data"; 335280304Sjkim else if (ctype_nid == NID_pkcs7_signed) { 336280304Sjkim if (econt_nid == NID_id_smime_ct_receipt) 337280304Sjkim msg_type = "signed-receipt"; 338280304Sjkim else if (sk_X509_ALGOR_num(mdalgs) >= 0) 339280304Sjkim msg_type = "signed-data"; 340280304Sjkim else 341280304Sjkim msg_type = "certs-only"; 342280304Sjkim } else if (ctype_nid == NID_id_smime_ct_compressedData) { 343280304Sjkim msg_type = "compressed-data"; 344280304Sjkim cname = "smime.p7z"; 345280304Sjkim } 346280304Sjkim /* MIME headers */ 347280304Sjkim BIO_printf(bio, "MIME-Version: 1.0%s", mime_eol); 348280304Sjkim BIO_printf(bio, "Content-Disposition: attachment;"); 349280304Sjkim BIO_printf(bio, " filename=\"%s\"%s", cname, mime_eol); 350280304Sjkim BIO_printf(bio, "Content-Type: %smime;", mime_prefix); 351280304Sjkim if (msg_type) 352280304Sjkim BIO_printf(bio, " smime-type=%s;", msg_type); 353280304Sjkim BIO_printf(bio, " name=\"%s\"%s", cname, mime_eol); 354280304Sjkim BIO_printf(bio, "Content-Transfer-Encoding: base64%s%s", 355280304Sjkim mime_eol, mime_eol); 356280304Sjkim if (!B64_write_ASN1(bio, val, data, flags, it)) 357280304Sjkim return 0; 358280304Sjkim BIO_printf(bio, "%s", mime_eol); 359280304Sjkim return 1; 360183234Ssimon} 361183234Ssimon 362183234Ssimon/* Handle output of ASN1 data */ 363183234Ssimon 364183234Ssimonstatic int asn1_output_data(BIO *out, BIO *data, ASN1_VALUE *val, int flags, 365280304Sjkim const ASN1_ITEM *it) 366280304Sjkim{ 367280304Sjkim BIO *tmpbio; 368280304Sjkim const ASN1_AUX *aux = it->funcs; 369280304Sjkim ASN1_STREAM_ARG sarg; 370280304Sjkim int rv = 1; 371183234Ssimon 372280304Sjkim /* 373280304Sjkim * If data is not deteched or resigning then the output BIO is already 374280304Sjkim * set up to finalise when it is written through. 375280304Sjkim */ 376280304Sjkim if (!(flags & SMIME_DETACHED) || (flags & PKCS7_REUSE_DIGEST)) { 377280304Sjkim SMIME_crlf_copy(data, out, flags); 378280304Sjkim return 1; 379280304Sjkim } 380183234Ssimon 381280304Sjkim if (!aux || !aux->asn1_cb) { 382280304Sjkim ASN1err(ASN1_F_ASN1_OUTPUT_DATA, ASN1_R_STREAMING_NOT_SUPPORTED); 383280304Sjkim return 0; 384280304Sjkim } 385183234Ssimon 386280304Sjkim sarg.out = out; 387280304Sjkim sarg.ndef_bio = NULL; 388280304Sjkim sarg.boundary = NULL; 389183234Ssimon 390280304Sjkim /* Let ASN1 code prepend any needed BIOs */ 391183234Ssimon 392280304Sjkim if (aux->asn1_cb(ASN1_OP_DETACHED_PRE, &val, it, &sarg) <= 0) 393280304Sjkim return 0; 394183234Ssimon 395280304Sjkim /* Copy data across, passing through filter BIOs for processing */ 396280304Sjkim SMIME_crlf_copy(data, sarg.ndef_bio, flags); 397183234Ssimon 398280304Sjkim /* Finalize structure */ 399280304Sjkim if (aux->asn1_cb(ASN1_OP_DETACHED_POST, &val, it, &sarg) <= 0) 400280304Sjkim rv = 0; 401183234Ssimon 402280304Sjkim /* Now remove any digests prepended to the BIO */ 403183234Ssimon 404280304Sjkim while (sarg.ndef_bio != out) { 405280304Sjkim tmpbio = BIO_pop(sarg.ndef_bio); 406280304Sjkim BIO_free(sarg.ndef_bio); 407280304Sjkim sarg.ndef_bio = tmpbio; 408280304Sjkim } 409183234Ssimon 410280304Sjkim return rv; 411183234Ssimon 412280304Sjkim} 413183234Ssimon 414280304Sjkim/* 415280304Sjkim * SMIME reader: handle multipart/signed and opaque signing. in multipart 416280304Sjkim * case the content is placed in a memory BIO pointed to by "bcont". In 417280304Sjkim * opaque this is set to NULL 418183234Ssimon */ 419183234Ssimon 420183234SsimonASN1_VALUE *SMIME_read_ASN1(BIO *bio, BIO **bcont, const ASN1_ITEM *it) 421183234Ssimon{ 422280304Sjkim BIO *asnin; 423280304Sjkim STACK_OF(MIME_HEADER) *headers = NULL; 424280304Sjkim STACK_OF(BIO) *parts = NULL; 425280304Sjkim MIME_HEADER *hdr; 426280304Sjkim MIME_PARAM *prm; 427280304Sjkim ASN1_VALUE *val; 428280304Sjkim int ret; 429183234Ssimon 430280304Sjkim if (bcont) 431280304Sjkim *bcont = NULL; 432183234Ssimon 433280304Sjkim if (!(headers = mime_parse_hdr(bio))) { 434280304Sjkim ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_MIME_PARSE_ERROR); 435280304Sjkim return NULL; 436280304Sjkim } 437183234Ssimon 438280304Sjkim if (!(hdr = mime_hdr_find(headers, "content-type")) || !hdr->value) { 439280304Sjkim sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 440280304Sjkim ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_NO_CONTENT_TYPE); 441280304Sjkim return NULL; 442280304Sjkim } 443183234Ssimon 444280304Sjkim /* Handle multipart/signed */ 445183234Ssimon 446280304Sjkim if (!strcmp(hdr->value, "multipart/signed")) { 447280304Sjkim /* Split into two parts */ 448280304Sjkim prm = mime_param_find(hdr, "boundary"); 449280304Sjkim if (!prm || !prm->param_value) { 450280304Sjkim sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 451280304Sjkim ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_NO_MULTIPART_BOUNDARY); 452280304Sjkim return NULL; 453280304Sjkim } 454280304Sjkim ret = multi_split(bio, prm->param_value, &parts); 455280304Sjkim sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 456280304Sjkim if (!ret || (sk_BIO_num(parts) != 2)) { 457280304Sjkim ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_NO_MULTIPART_BODY_FAILURE); 458280304Sjkim sk_BIO_pop_free(parts, BIO_vfree); 459280304Sjkim return NULL; 460280304Sjkim } 461183234Ssimon 462280304Sjkim /* Parse the signature piece */ 463280304Sjkim asnin = sk_BIO_value(parts, 1); 464183234Ssimon 465280304Sjkim if (!(headers = mime_parse_hdr(asnin))) { 466280304Sjkim ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_MIME_SIG_PARSE_ERROR); 467280304Sjkim sk_BIO_pop_free(parts, BIO_vfree); 468280304Sjkim return NULL; 469280304Sjkim } 470183234Ssimon 471280304Sjkim /* Get content type */ 472183234Ssimon 473280304Sjkim if (!(hdr = mime_hdr_find(headers, "content-type")) || !hdr->value) { 474280304Sjkim sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 475280304Sjkim ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_NO_SIG_CONTENT_TYPE); 476280304Sjkim return NULL; 477280304Sjkim } 478183234Ssimon 479280304Sjkim if (strcmp(hdr->value, "application/x-pkcs7-signature") && 480280304Sjkim strcmp(hdr->value, "application/pkcs7-signature")) { 481280304Sjkim ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_SIG_INVALID_MIME_TYPE); 482280304Sjkim ERR_add_error_data(2, "type: ", hdr->value); 483280304Sjkim sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 484280304Sjkim sk_BIO_pop_free(parts, BIO_vfree); 485280304Sjkim return NULL; 486280304Sjkim } 487280304Sjkim sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 488280304Sjkim /* Read in ASN1 */ 489280304Sjkim if (!(val = b64_read_asn1(asnin, it))) { 490280304Sjkim ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_ASN1_SIG_PARSE_ERROR); 491280304Sjkim sk_BIO_pop_free(parts, BIO_vfree); 492280304Sjkim return NULL; 493280304Sjkim } 494183234Ssimon 495280304Sjkim if (bcont) { 496280304Sjkim *bcont = sk_BIO_value(parts, 0); 497280304Sjkim BIO_free(asnin); 498280304Sjkim sk_BIO_free(parts); 499280304Sjkim } else 500280304Sjkim sk_BIO_pop_free(parts, BIO_vfree); 501280304Sjkim return val; 502280304Sjkim } 503183234Ssimon 504280304Sjkim /* OK, if not multipart/signed try opaque signature */ 505183234Ssimon 506280304Sjkim if (strcmp(hdr->value, "application/x-pkcs7-mime") && 507280304Sjkim strcmp(hdr->value, "application/pkcs7-mime")) { 508280304Sjkim ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_INVALID_MIME_TYPE); 509280304Sjkim ERR_add_error_data(2, "type: ", hdr->value); 510280304Sjkim sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 511280304Sjkim return NULL; 512280304Sjkim } 513183234Ssimon 514280304Sjkim sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 515280304Sjkim 516280304Sjkim if (!(val = b64_read_asn1(bio, it))) { 517280304Sjkim ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_ASN1_PARSE_ERROR); 518280304Sjkim return NULL; 519280304Sjkim } 520280304Sjkim return val; 521280304Sjkim 522183234Ssimon} 523183234Ssimon 524183234Ssimon/* Copy text from one BIO to another making the output CRLF at EOL */ 525183234Ssimonint SMIME_crlf_copy(BIO *in, BIO *out, int flags) 526183234Ssimon{ 527280304Sjkim BIO *bf; 528280304Sjkim char eol; 529280304Sjkim int len; 530280304Sjkim char linebuf[MAX_SMLEN]; 531280304Sjkim /* 532280304Sjkim * Buffer output so we don't write one line at a time. This is useful 533280304Sjkim * when streaming as we don't end up with one OCTET STRING per line. 534280304Sjkim */ 535280304Sjkim bf = BIO_new(BIO_f_buffer()); 536280304Sjkim if (!bf) 537280304Sjkim return 0; 538280304Sjkim out = BIO_push(bf, out); 539280304Sjkim if (flags & SMIME_BINARY) { 540280304Sjkim while ((len = BIO_read(in, linebuf, MAX_SMLEN)) > 0) 541280304Sjkim BIO_write(out, linebuf, len); 542280304Sjkim } else { 543280304Sjkim if (flags & SMIME_TEXT) 544280304Sjkim BIO_printf(out, "Content-Type: text/plain\r\n\r\n"); 545280304Sjkim while ((len = BIO_gets(in, linebuf, MAX_SMLEN)) > 0) { 546280304Sjkim eol = strip_eol(linebuf, &len); 547280304Sjkim if (len) 548280304Sjkim BIO_write(out, linebuf, len); 549280304Sjkim if (eol) 550280304Sjkim BIO_write(out, "\r\n", 2); 551280304Sjkim } 552280304Sjkim } 553280304Sjkim (void)BIO_flush(out); 554280304Sjkim BIO_pop(out); 555280304Sjkim BIO_free(bf); 556280304Sjkim return 1; 557183234Ssimon} 558183234Ssimon 559183234Ssimon/* Strip off headers if they are text/plain */ 560183234Ssimonint SMIME_text(BIO *in, BIO *out) 561183234Ssimon{ 562280304Sjkim char iobuf[4096]; 563280304Sjkim int len; 564280304Sjkim STACK_OF(MIME_HEADER) *headers; 565280304Sjkim MIME_HEADER *hdr; 566183234Ssimon 567280304Sjkim if (!(headers = mime_parse_hdr(in))) { 568280304Sjkim ASN1err(ASN1_F_SMIME_TEXT, ASN1_R_MIME_PARSE_ERROR); 569280304Sjkim return 0; 570280304Sjkim } 571280304Sjkim if (!(hdr = mime_hdr_find(headers, "content-type")) || !hdr->value) { 572280304Sjkim ASN1err(ASN1_F_SMIME_TEXT, ASN1_R_MIME_NO_CONTENT_TYPE); 573280304Sjkim sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 574280304Sjkim return 0; 575280304Sjkim } 576280304Sjkim if (strcmp(hdr->value, "text/plain")) { 577280304Sjkim ASN1err(ASN1_F_SMIME_TEXT, ASN1_R_INVALID_MIME_TYPE); 578280304Sjkim ERR_add_error_data(2, "type: ", hdr->value); 579280304Sjkim sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 580280304Sjkim return 0; 581280304Sjkim } 582280304Sjkim sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 583280304Sjkim while ((len = BIO_read(in, iobuf, sizeof(iobuf))) > 0) 584280304Sjkim BIO_write(out, iobuf, len); 585280304Sjkim if (len < 0) 586280304Sjkim return 0; 587280304Sjkim return 1; 588183234Ssimon} 589183234Ssimon 590280304Sjkim/* 591280304Sjkim * Split a multipart/XXX message body into component parts: result is 592183234Ssimon * canonical parts in a STACK of bios 593183234Ssimon */ 594183234Ssimon 595183234Ssimonstatic int multi_split(BIO *bio, char *bound, STACK_OF(BIO) **ret) 596183234Ssimon{ 597280304Sjkim char linebuf[MAX_SMLEN]; 598280304Sjkim int len, blen; 599280304Sjkim int eol = 0, next_eol = 0; 600280304Sjkim BIO *bpart = NULL; 601280304Sjkim STACK_OF(BIO) *parts; 602280304Sjkim char state, part, first; 603183234Ssimon 604280304Sjkim blen = strlen(bound); 605280304Sjkim part = 0; 606280304Sjkim state = 0; 607280304Sjkim first = 1; 608280304Sjkim parts = sk_BIO_new_null(); 609280304Sjkim *ret = parts; 610280304Sjkim while ((len = BIO_gets(bio, linebuf, MAX_SMLEN)) > 0) { 611280304Sjkim state = mime_bound_check(linebuf, len, bound, blen); 612280304Sjkim if (state == 1) { 613280304Sjkim first = 1; 614280304Sjkim part++; 615280304Sjkim } else if (state == 2) { 616280304Sjkim sk_BIO_push(parts, bpart); 617280304Sjkim return 1; 618280304Sjkim } else if (part) { 619280304Sjkim /* Strip CR+LF from linebuf */ 620280304Sjkim next_eol = strip_eol(linebuf, &len); 621280304Sjkim if (first) { 622280304Sjkim first = 0; 623280304Sjkim if (bpart) 624280304Sjkim sk_BIO_push(parts, bpart); 625280304Sjkim bpart = BIO_new(BIO_s_mem()); 626280304Sjkim BIO_set_mem_eof_return(bpart, 0); 627280304Sjkim } else if (eol) 628280304Sjkim BIO_write(bpart, "\r\n", 2); 629280304Sjkim eol = next_eol; 630280304Sjkim if (len) 631280304Sjkim BIO_write(bpart, linebuf, len); 632280304Sjkim } 633280304Sjkim } 634280304Sjkim return 0; 635183234Ssimon} 636183234Ssimon 637183234Ssimon/* This is the big one: parse MIME header lines up to message body */ 638183234Ssimon 639280304Sjkim#define MIME_INVALID 0 640280304Sjkim#define MIME_START 1 641280304Sjkim#define MIME_TYPE 2 642280304Sjkim#define MIME_NAME 3 643280304Sjkim#define MIME_VALUE 4 644280304Sjkim#define MIME_QUOTE 5 645280304Sjkim#define MIME_COMMENT 6 646183234Ssimon 647183234Ssimonstatic STACK_OF(MIME_HEADER) *mime_parse_hdr(BIO *bio) 648183234Ssimon{ 649280304Sjkim char *p, *q, c; 650280304Sjkim char *ntmp; 651280304Sjkim char linebuf[MAX_SMLEN]; 652280304Sjkim MIME_HEADER *mhdr = NULL; 653280304Sjkim STACK_OF(MIME_HEADER) *headers; 654280304Sjkim int len, state, save_state = 0; 655183234Ssimon 656280304Sjkim headers = sk_MIME_HEADER_new(mime_hdr_cmp); 657280304Sjkim if (!headers) 658280304Sjkim return NULL; 659280304Sjkim while ((len = BIO_gets(bio, linebuf, MAX_SMLEN)) > 0) { 660280304Sjkim /* If whitespace at line start then continuation line */ 661280304Sjkim if (mhdr && isspace((unsigned char)linebuf[0])) 662280304Sjkim state = MIME_NAME; 663280304Sjkim else 664280304Sjkim state = MIME_START; 665280304Sjkim ntmp = NULL; 666280304Sjkim /* Go through all characters */ 667280304Sjkim for (p = linebuf, q = linebuf; (c = *p) && (c != '\r') && (c != '\n'); 668280304Sjkim p++) { 669183234Ssimon 670280304Sjkim /* 671280304Sjkim * State machine to handle MIME headers if this looks horrible 672280304Sjkim * that's because it *is* 673280304Sjkim */ 674183234Ssimon 675280304Sjkim switch (state) { 676280304Sjkim case MIME_START: 677280304Sjkim if (c == ':') { 678280304Sjkim state = MIME_TYPE; 679280304Sjkim *p = 0; 680280304Sjkim ntmp = strip_ends(q); 681280304Sjkim q = p + 1; 682280304Sjkim } 683280304Sjkim break; 684183234Ssimon 685280304Sjkim case MIME_TYPE: 686280304Sjkim if (c == ';') { 687280304Sjkim mime_debug("Found End Value\n"); 688280304Sjkim *p = 0; 689280304Sjkim mhdr = mime_hdr_new(ntmp, strip_ends(q)); 690280304Sjkim sk_MIME_HEADER_push(headers, mhdr); 691280304Sjkim ntmp = NULL; 692280304Sjkim q = p + 1; 693280304Sjkim state = MIME_NAME; 694280304Sjkim } else if (c == '(') { 695280304Sjkim save_state = state; 696280304Sjkim state = MIME_COMMENT; 697280304Sjkim } 698280304Sjkim break; 699183234Ssimon 700280304Sjkim case MIME_COMMENT: 701280304Sjkim if (c == ')') { 702280304Sjkim state = save_state; 703280304Sjkim } 704280304Sjkim break; 705183234Ssimon 706280304Sjkim case MIME_NAME: 707280304Sjkim if (c == '=') { 708280304Sjkim state = MIME_VALUE; 709280304Sjkim *p = 0; 710280304Sjkim ntmp = strip_ends(q); 711280304Sjkim q = p + 1; 712280304Sjkim } 713280304Sjkim break; 714183234Ssimon 715280304Sjkim case MIME_VALUE: 716280304Sjkim if (c == ';') { 717280304Sjkim state = MIME_NAME; 718280304Sjkim *p = 0; 719280304Sjkim mime_hdr_addparam(mhdr, ntmp, strip_ends(q)); 720280304Sjkim ntmp = NULL; 721280304Sjkim q = p + 1; 722280304Sjkim } else if (c == '"') { 723280304Sjkim mime_debug("Found Quote\n"); 724280304Sjkim state = MIME_QUOTE; 725280304Sjkim } else if (c == '(') { 726280304Sjkim save_state = state; 727280304Sjkim state = MIME_COMMENT; 728280304Sjkim } 729280304Sjkim break; 730183234Ssimon 731280304Sjkim case MIME_QUOTE: 732280304Sjkim if (c == '"') { 733280304Sjkim mime_debug("Found Match Quote\n"); 734280304Sjkim state = MIME_VALUE; 735280304Sjkim } 736280304Sjkim break; 737280304Sjkim } 738280304Sjkim } 739183234Ssimon 740280304Sjkim if (state == MIME_TYPE) { 741280304Sjkim mhdr = mime_hdr_new(ntmp, strip_ends(q)); 742280304Sjkim sk_MIME_HEADER_push(headers, mhdr); 743280304Sjkim } else if (state == MIME_VALUE) 744280304Sjkim mime_hdr_addparam(mhdr, ntmp, strip_ends(q)); 745280304Sjkim if (p == linebuf) 746280304Sjkim break; /* Blank line means end of headers */ 747280304Sjkim } 748183234Ssimon 749280304Sjkim return headers; 750183234Ssimon 751183234Ssimon} 752183234Ssimon 753183234Ssimonstatic char *strip_ends(char *name) 754183234Ssimon{ 755280304Sjkim return strip_end(strip_start(name)); 756183234Ssimon} 757183234Ssimon 758183234Ssimon/* Strip a parameter of whitespace from start of param */ 759183234Ssimonstatic char *strip_start(char *name) 760183234Ssimon{ 761280304Sjkim char *p, c; 762280304Sjkim /* Look for first non white space or quote */ 763280304Sjkim for (p = name; (c = *p); p++) { 764280304Sjkim if (c == '"') { 765280304Sjkim /* Next char is start of string if non null */ 766280304Sjkim if (p[1]) 767280304Sjkim return p + 1; 768280304Sjkim /* Else null string */ 769280304Sjkim return NULL; 770280304Sjkim } 771280304Sjkim if (!isspace((unsigned char)c)) 772280304Sjkim return p; 773280304Sjkim } 774280304Sjkim return NULL; 775183234Ssimon} 776183234Ssimon 777183234Ssimon/* As above but strip from end of string : maybe should handle brackets? */ 778183234Ssimonstatic char *strip_end(char *name) 779183234Ssimon{ 780280304Sjkim char *p, c; 781280304Sjkim if (!name) 782280304Sjkim return NULL; 783280304Sjkim /* Look for first non white space or quote */ 784280304Sjkim for (p = name + strlen(name) - 1; p >= name; p--) { 785280304Sjkim c = *p; 786280304Sjkim if (c == '"') { 787280304Sjkim if (p - 1 == name) 788280304Sjkim return NULL; 789280304Sjkim *p = 0; 790280304Sjkim return name; 791280304Sjkim } 792280304Sjkim if (isspace((unsigned char)c)) 793280304Sjkim *p = 0; 794280304Sjkim else 795280304Sjkim return name; 796280304Sjkim } 797280304Sjkim return NULL; 798183234Ssimon} 799183234Ssimon 800183234Ssimonstatic MIME_HEADER *mime_hdr_new(char *name, char *value) 801183234Ssimon{ 802280304Sjkim MIME_HEADER *mhdr; 803280304Sjkim char *tmpname, *tmpval, *p; 804280304Sjkim int c; 805280304Sjkim if (name) { 806280304Sjkim if (!(tmpname = BUF_strdup(name))) 807280304Sjkim return NULL; 808280304Sjkim for (p = tmpname; *p; p++) { 809280304Sjkim c = (unsigned char)*p; 810280304Sjkim if (isupper(c)) { 811280304Sjkim c = tolower(c); 812280304Sjkim *p = c; 813280304Sjkim } 814280304Sjkim } 815280304Sjkim } else 816280304Sjkim tmpname = NULL; 817280304Sjkim if (value) { 818280304Sjkim if (!(tmpval = BUF_strdup(value))) 819280304Sjkim return NULL; 820280304Sjkim for (p = tmpval; *p; p++) { 821280304Sjkim c = (unsigned char)*p; 822280304Sjkim if (isupper(c)) { 823280304Sjkim c = tolower(c); 824280304Sjkim *p = c; 825280304Sjkim } 826280304Sjkim } 827280304Sjkim } else 828280304Sjkim tmpval = NULL; 829280304Sjkim mhdr = (MIME_HEADER *)OPENSSL_malloc(sizeof(MIME_HEADER)); 830280304Sjkim if (!mhdr) 831280304Sjkim return NULL; 832280304Sjkim mhdr->name = tmpname; 833280304Sjkim mhdr->value = tmpval; 834280304Sjkim if (!(mhdr->params = sk_MIME_PARAM_new(mime_param_cmp))) 835280304Sjkim return NULL; 836280304Sjkim return mhdr; 837183234Ssimon} 838280304Sjkim 839183234Ssimonstatic int mime_hdr_addparam(MIME_HEADER *mhdr, char *name, char *value) 840183234Ssimon{ 841280304Sjkim char *tmpname, *tmpval, *p; 842280304Sjkim int c; 843280304Sjkim MIME_PARAM *mparam; 844280304Sjkim if (name) { 845280304Sjkim tmpname = BUF_strdup(name); 846280304Sjkim if (!tmpname) 847280304Sjkim return 0; 848280304Sjkim for (p = tmpname; *p; p++) { 849280304Sjkim c = (unsigned char)*p; 850280304Sjkim if (isupper(c)) { 851280304Sjkim c = tolower(c); 852280304Sjkim *p = c; 853280304Sjkim } 854280304Sjkim } 855280304Sjkim } else 856280304Sjkim tmpname = NULL; 857280304Sjkim if (value) { 858280304Sjkim tmpval = BUF_strdup(value); 859280304Sjkim if (!tmpval) 860280304Sjkim return 0; 861280304Sjkim } else 862280304Sjkim tmpval = NULL; 863280304Sjkim /* Parameter values are case sensitive so leave as is */ 864280304Sjkim mparam = (MIME_PARAM *)OPENSSL_malloc(sizeof(MIME_PARAM)); 865280304Sjkim if (!mparam) 866280304Sjkim return 0; 867280304Sjkim mparam->param_name = tmpname; 868280304Sjkim mparam->param_value = tmpval; 869280304Sjkim sk_MIME_PARAM_push(mhdr->params, mparam); 870280304Sjkim return 1; 871183234Ssimon} 872183234Ssimon 873280304Sjkimstatic int mime_hdr_cmp(const MIME_HEADER *const *a, 874280304Sjkim const MIME_HEADER *const *b) 875183234Ssimon{ 876280304Sjkim if (!(*a)->name || !(*b)->name) 877280304Sjkim return ! !(*a)->name - ! !(*b)->name; 878237657Sjkim 879280304Sjkim return (strcmp((*a)->name, (*b)->name)); 880183234Ssimon} 881183234Ssimon 882280304Sjkimstatic int mime_param_cmp(const MIME_PARAM *const *a, 883280304Sjkim const MIME_PARAM *const *b) 884183234Ssimon{ 885280304Sjkim if (!(*a)->param_name || !(*b)->param_name) 886280304Sjkim return ! !(*a)->param_name - ! !(*b)->param_name; 887280304Sjkim return (strcmp((*a)->param_name, (*b)->param_name)); 888183234Ssimon} 889183234Ssimon 890183234Ssimon/* Find a header with a given name (if possible) */ 891183234Ssimon 892183234Ssimonstatic MIME_HEADER *mime_hdr_find(STACK_OF(MIME_HEADER) *hdrs, char *name) 893183234Ssimon{ 894280304Sjkim MIME_HEADER htmp; 895280304Sjkim int idx; 896280304Sjkim htmp.name = name; 897280304Sjkim idx = sk_MIME_HEADER_find(hdrs, &htmp); 898280304Sjkim if (idx < 0) 899280304Sjkim return NULL; 900280304Sjkim return sk_MIME_HEADER_value(hdrs, idx); 901183234Ssimon} 902183234Ssimon 903183234Ssimonstatic MIME_PARAM *mime_param_find(MIME_HEADER *hdr, char *name) 904183234Ssimon{ 905280304Sjkim MIME_PARAM param; 906280304Sjkim int idx; 907280304Sjkim param.param_name = name; 908280304Sjkim idx = sk_MIME_PARAM_find(hdr->params, ¶m); 909280304Sjkim if (idx < 0) 910280304Sjkim return NULL; 911280304Sjkim return sk_MIME_PARAM_value(hdr->params, idx); 912183234Ssimon} 913183234Ssimon 914183234Ssimonstatic void mime_hdr_free(MIME_HEADER *hdr) 915183234Ssimon{ 916280304Sjkim if (hdr->name) 917280304Sjkim OPENSSL_free(hdr->name); 918280304Sjkim if (hdr->value) 919280304Sjkim OPENSSL_free(hdr->value); 920280304Sjkim if (hdr->params) 921280304Sjkim sk_MIME_PARAM_pop_free(hdr->params, mime_param_free); 922280304Sjkim OPENSSL_free(hdr); 923183234Ssimon} 924183234Ssimon 925183234Ssimonstatic void mime_param_free(MIME_PARAM *param) 926183234Ssimon{ 927280304Sjkim if (param->param_name) 928280304Sjkim OPENSSL_free(param->param_name); 929280304Sjkim if (param->param_value) 930280304Sjkim OPENSSL_free(param->param_value); 931280304Sjkim OPENSSL_free(param); 932183234Ssimon} 933183234Ssimon 934280304Sjkim/*- 935280304Sjkim * Check for a multipart boundary. Returns: 936183234Ssimon * 0 : no boundary 937183234Ssimon * 1 : part boundary 938183234Ssimon * 2 : final boundary 939183234Ssimon */ 940183234Ssimonstatic int mime_bound_check(char *line, int linelen, char *bound, int blen) 941183234Ssimon{ 942280304Sjkim if (linelen == -1) 943280304Sjkim linelen = strlen(line); 944280304Sjkim if (blen == -1) 945280304Sjkim blen = strlen(bound); 946280304Sjkim /* Quickly eliminate if line length too short */ 947280304Sjkim if (blen + 2 > linelen) 948280304Sjkim return 0; 949280304Sjkim /* Check for part boundary */ 950280304Sjkim if (!strncmp(line, "--", 2) && !strncmp(line + 2, bound, blen)) { 951280304Sjkim if (!strncmp(line + blen + 2, "--", 2)) 952280304Sjkim return 2; 953280304Sjkim else 954280304Sjkim return 1; 955280304Sjkim } 956280304Sjkim return 0; 957183234Ssimon} 958183234Ssimon 959183234Ssimonstatic int strip_eol(char *linebuf, int *plen) 960280304Sjkim{ 961280304Sjkim int len = *plen; 962280304Sjkim char *p, c; 963280304Sjkim int is_eol = 0; 964280304Sjkim p = linebuf + len - 1; 965280304Sjkim for (p = linebuf + len - 1; len > 0; len--, p--) { 966280304Sjkim c = *p; 967280304Sjkim if (c == '\n') 968280304Sjkim is_eol = 1; 969280304Sjkim else if (c != '\r') 970280304Sjkim break; 971280304Sjkim } 972280304Sjkim *plen = len; 973280304Sjkim return is_eol; 974280304Sjkim} 975