1 /* $OpenBSD: asn_mime.c,v 1.22 2014/07/13 16:03:09 beck Exp $ */ 2 /* Written by Dr Stephen N Henson (steve@openssl.org) for the OpenSSL 3 * project. 4 */ 5 /* ==================================================================== 6 * Copyright (c) 1999-2008 The OpenSSL Project. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in 17 * the documentation and/or other materials provided with the 18 * distribution. 19 * 20 * 3. All advertising materials mentioning features or use of this 21 * software must display the following acknowledgment: 22 * "This product includes software developed by the OpenSSL Project 23 * for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)" 24 * 25 * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to 26 * endorse or promote products derived from this software without 27 * prior written permission. For written permission, please contact 28 * licensing@OpenSSL.org. 29 * 30 * 5. Products derived from this software may not be called "OpenSSL" 31 * nor may "OpenSSL" appear in their names without prior written 32 * permission of the OpenSSL Project. 33 * 34 * 6. Redistributions of any form whatsoever must retain the following 35 * acknowledgment: 36 * "This product includes software developed by the OpenSSL Project 37 * for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)" 38 * 39 * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY 40 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 41 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 42 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR 43 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 44 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 45 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 46 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 47 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 48 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 49 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 50 * OF THE POSSIBILITY OF SUCH DAMAGE. 51 * ==================================================================== 52 * 53 */ 54 55 #include <ctype.h> 56 #include <stdio.h> 57 #include <string.h> 58 59 #include <openssl/asn1.h> 60 #include <openssl/asn1t.h> 61 #include <openssl/err.h> 62 #include <openssl/rand.h> 63 #include <openssl/x509.h> 64 65 #include "asn1_locl.h" 66 67 /* Generalised MIME like utilities for streaming ASN1. Although many 68 * have a PKCS7/CMS like flavour others are more general purpose. 69 */ 70 71 /* MIME format structures 72 * Note that all are translated to lower case apart from 73 * parameter values. Quotes are stripped off 74 */ 75 76 typedef struct { 77 char *param_name; /* Param name e.g. "micalg" */ 78 char *param_value; /* Param value e.g. "sha1" */ 79 } MIME_PARAM; 80 81 DECLARE_STACK_OF(MIME_PARAM) 82 IMPLEMENT_STACK_OF(MIME_PARAM) 83 84 typedef struct { 85 char *name; /* Name of line e.g. "content-type" */ 86 char *value; /* Value of line e.g. "text/plain" */ 87 STACK_OF(MIME_PARAM) *params; /* Zero or more parameters */ 88 } MIME_HEADER; 89 90 DECLARE_STACK_OF(MIME_HEADER) 91 IMPLEMENT_STACK_OF(MIME_HEADER) 92 93 static int asn1_output_data(BIO *out, BIO *data, ASN1_VALUE *val, int flags, 94 const ASN1_ITEM *it); 95 static char * strip_ends(char *name); 96 static char * strip_start(char *name); 97 static char * strip_end(char *name); 98 static MIME_HEADER *mime_hdr_new(char *name, char *value); 99 static int mime_hdr_addparam(MIME_HEADER *mhdr, char *name, char *value); 100 static STACK_OF(MIME_HEADER) *mime_parse_hdr(BIO *bio); 101 static int mime_hdr_cmp(const MIME_HEADER * const *a, 102 const MIME_HEADER * const *b); 103 static int mime_param_cmp(const MIME_PARAM * const *a, 104 const MIME_PARAM * const *b); 105 static void mime_param_free(MIME_PARAM *param); 106 static int mime_bound_check(char *line, int linelen, char *bound, int blen); 107 static int multi_split(BIO *bio, char *bound, STACK_OF(BIO) **ret); 108 static int strip_eol(char *linebuf, int *plen); 109 static MIME_HEADER *mime_hdr_find(STACK_OF(MIME_HEADER) *hdrs, char *name); 110 static MIME_PARAM *mime_param_find(MIME_HEADER *hdr, char *name); 111 static void mime_hdr_free(MIME_HEADER *hdr); 112 113 #define MAX_SMLEN 1024 114 #define mime_debug(x) /* x */ 115 116 /* Output an ASN1 structure in BER format streaming if necessary */ 117 118 int 119 i2d_ASN1_bio_stream(BIO *out, ASN1_VALUE *val, BIO *in, int flags, 120 const ASN1_ITEM *it) 121 { 122 /* If streaming create stream BIO and copy all content through it */ 123 if (flags & SMIME_STREAM) { 124 BIO *bio, *tbio; 125 bio = BIO_new_NDEF(out, val, it); 126 if (!bio) { 127 ASN1err(ASN1_F_I2D_ASN1_BIO_STREAM, 128 ERR_R_MALLOC_FAILURE); 129 return 0; 130 } 131 SMIME_crlf_copy(in, bio, flags); 132 (void)BIO_flush(bio); 133 /* Free up successive BIOs until we hit the old output BIO */ 134 do { 135 tbio = BIO_pop(bio); 136 BIO_free(bio); 137 bio = tbio; 138 } while (bio != out); 139 } 140 /* else just write out ASN1 structure which will have all content 141 * stored internally 142 */ 143 else 144 ASN1_item_i2d_bio(it, out, val); 145 return 1; 146 } 147 148 /* Base 64 read and write of ASN1 structure */ 149 150 static int 151 B64_write_ASN1(BIO *out, ASN1_VALUE *val, BIO *in, int flags, 152 const ASN1_ITEM *it) 153 { 154 BIO *b64; 155 int r; 156 157 b64 = BIO_new(BIO_f_base64()); 158 if (!b64) { 159 ASN1err(ASN1_F_B64_WRITE_ASN1, ERR_R_MALLOC_FAILURE); 160 return 0; 161 } 162 /* prepend the b64 BIO so all data is base64 encoded. 163 */ 164 out = BIO_push(b64, out); 165 r = i2d_ASN1_bio_stream(out, val, in, flags, it); 166 (void)BIO_flush(out); 167 BIO_pop(out); 168 BIO_free(b64); 169 return r; 170 } 171 172 /* Streaming ASN1 PEM write */ 173 174 int 175 PEM_write_bio_ASN1_stream(BIO *out, ASN1_VALUE *val, BIO *in, int flags, 176 const char *hdr, const ASN1_ITEM *it) 177 { 178 int r; 179 180 BIO_printf(out, "-----BEGIN %s-----\n", hdr); 181 r = B64_write_ASN1(out, val, in, flags, it); 182 BIO_printf(out, "-----END %s-----\n", hdr); 183 return r; 184 } 185 186 static ASN1_VALUE * 187 b64_read_asn1(BIO *bio, const ASN1_ITEM *it) 188 { 189 BIO *b64; 190 ASN1_VALUE *val; 191 if (!(b64 = BIO_new(BIO_f_base64()))) { 192 ASN1err(ASN1_F_B64_READ_ASN1, ERR_R_MALLOC_FAILURE); 193 return 0; 194 } 195 bio = BIO_push(b64, bio); 196 val = ASN1_item_d2i_bio(it, bio, NULL); 197 if (!val) 198 ASN1err(ASN1_F_B64_READ_ASN1, ASN1_R_DECODE_ERROR); 199 (void)BIO_flush(bio); 200 bio = BIO_pop(bio); 201 BIO_free(b64); 202 return val; 203 } 204 205 /* Generate the MIME "micalg" parameter from RFC3851, RFC4490 */ 206 207 static int 208 asn1_write_micalg(BIO *out, STACK_OF(X509_ALGOR) *mdalgs) 209 { 210 const EVP_MD *md; 211 int i, have_unknown = 0, write_comma, ret = 0, md_nid; 212 213 have_unknown = 0; 214 write_comma = 0; 215 for (i = 0; i < sk_X509_ALGOR_num(mdalgs); i++) { 216 if (write_comma) 217 BIO_write(out, ",", 1); 218 write_comma = 1; 219 md_nid = OBJ_obj2nid(sk_X509_ALGOR_value(mdalgs, i)->algorithm); 220 md = EVP_get_digestbynid(md_nid); 221 if (md && md->md_ctrl) { 222 int rv; 223 char *micstr; 224 rv = md->md_ctrl(NULL, EVP_MD_CTRL_MICALG, 0, &micstr); 225 if (rv > 0) { 226 BIO_puts(out, micstr); 227 free(micstr); 228 continue; 229 } 230 if (rv != -2) 231 goto err; 232 } 233 switch (md_nid) { 234 case NID_sha1: 235 BIO_puts(out, "sha1"); 236 break; 237 238 case NID_md5: 239 BIO_puts(out, "md5"); 240 break; 241 242 case NID_sha256: 243 BIO_puts(out, "sha-256"); 244 break; 245 246 case NID_sha384: 247 BIO_puts(out, "sha-384"); 248 break; 249 250 case NID_sha512: 251 BIO_puts(out, "sha-512"); 252 break; 253 254 case NID_id_GostR3411_94: 255 BIO_puts(out, "gostr3411-94"); 256 goto err; 257 break; 258 259 default: 260 if (have_unknown) 261 write_comma = 0; 262 else { 263 BIO_puts(out, "unknown"); 264 have_unknown = 1; 265 } 266 break; 267 268 } 269 } 270 271 ret = 1; 272 273 err: 274 return ret; 275 } 276 277 /* SMIME sender */ 278 279 int 280 SMIME_write_ASN1(BIO *bio, ASN1_VALUE *val, BIO *data, int flags, 281 int ctype_nid, int econt_nid, STACK_OF(X509_ALGOR) *mdalgs, 282 const ASN1_ITEM *it) 283 { 284 char bound[33], c; 285 int i; 286 const char *mime_prefix, *mime_eol, *cname = "smime.p7m"; 287 const char *msg_type = NULL; 288 289 if (flags & SMIME_OLDMIME) 290 mime_prefix = "application/x-pkcs7-"; 291 else 292 mime_prefix = "application/pkcs7-"; 293 294 if (flags & SMIME_CRLFEOL) 295 mime_eol = "\r\n"; 296 else 297 mime_eol = "\n"; 298 if ((flags & SMIME_DETACHED) && data) { 299 /* We want multipart/signed */ 300 /* Generate a random boundary */ 301 RAND_pseudo_bytes((unsigned char *)bound, 32); 302 for (i = 0; i < 32; i++) { 303 c = bound[i] & 0xf; 304 if (c < 10) 305 c += '0'; 306 else 307 c += 'A' - 10; 308 bound[i] = c; 309 } 310 bound[32] = 0; 311 BIO_printf(bio, "MIME-Version: 1.0%s", mime_eol); 312 BIO_printf(bio, "Content-Type: multipart/signed;"); 313 BIO_printf(bio, " protocol=\"%ssignature\";", mime_prefix); 314 BIO_puts(bio, " micalg=\""); 315 asn1_write_micalg(bio, mdalgs); 316 BIO_printf(bio, "\"; boundary=\"----%s\"%s%s", 317 bound, mime_eol, mime_eol); 318 BIO_printf(bio, "This is an S/MIME signed message%s%s", 319 mime_eol, mime_eol); 320 /* Now write out the first part */ 321 BIO_printf(bio, "------%s%s", bound, mime_eol); 322 if (!asn1_output_data(bio, data, val, flags, it)) 323 return 0; 324 BIO_printf(bio, "%s------%s%s", mime_eol, bound, mime_eol); 325 326 /* Headers for signature */ 327 328 BIO_printf(bio, "Content-Type: %ssignature;", mime_prefix); 329 BIO_printf(bio, " name=\"smime.p7s\"%s", mime_eol); 330 BIO_printf(bio, "Content-Transfer-Encoding: base64%s", 331 mime_eol); 332 BIO_printf(bio, "Content-Disposition: attachment;"); 333 BIO_printf(bio, " filename=\"smime.p7s\"%s%s", 334 mime_eol, mime_eol); 335 B64_write_ASN1(bio, val, NULL, 0, it); 336 BIO_printf(bio, "%s------%s--%s%s", mime_eol, bound, 337 mime_eol, mime_eol); 338 return 1; 339 } 340 341 /* Determine smime-type header */ 342 343 if (ctype_nid == NID_pkcs7_enveloped) 344 msg_type = "enveloped-data"; 345 else if (ctype_nid == NID_pkcs7_signed) { 346 if (econt_nid == NID_id_smime_ct_receipt) 347 msg_type = "signed-receipt"; 348 else if (sk_X509_ALGOR_num(mdalgs) >= 0) 349 msg_type = "signed-data"; 350 else 351 msg_type = "certs-only"; 352 } else if (ctype_nid == NID_id_smime_ct_compressedData) { 353 msg_type = "compressed-data"; 354 cname = "smime.p7z"; 355 } 356 /* MIME headers */ 357 BIO_printf(bio, "MIME-Version: 1.0%s", mime_eol); 358 BIO_printf(bio, "Content-Disposition: attachment;"); 359 BIO_printf(bio, " filename=\"%s\"%s", cname, mime_eol); 360 BIO_printf(bio, "Content-Type: %smime;", mime_prefix); 361 if (msg_type) 362 BIO_printf(bio, " smime-type=%s;", msg_type); 363 BIO_printf(bio, " name=\"%s\"%s", cname, mime_eol); 364 BIO_printf(bio, "Content-Transfer-Encoding: base64%s%s", 365 mime_eol, mime_eol); 366 if (!B64_write_ASN1(bio, val, data, flags, it)) 367 return 0; 368 BIO_printf(bio, "%s", mime_eol); 369 return 1; 370 } 371 372 /* Handle output of ASN1 data */ 373 374 375 static int 376 asn1_output_data(BIO *out, BIO *data, ASN1_VALUE *val, int flags, 377 const ASN1_ITEM *it) 378 { 379 BIO *tmpbio; 380 const ASN1_AUX *aux = it->funcs; 381 ASN1_STREAM_ARG sarg; 382 int rv = 1; 383 384 /* If data is not deteched or resigning then the output BIO is 385 * already set up to finalise when it is written through. 386 */ 387 if (!(flags & SMIME_DETACHED) || (flags & PKCS7_REUSE_DIGEST)) { 388 SMIME_crlf_copy(data, out, flags); 389 return 1; 390 } 391 392 if (!aux || !aux->asn1_cb) { 393 ASN1err(ASN1_F_ASN1_OUTPUT_DATA, 394 ASN1_R_STREAMING_NOT_SUPPORTED); 395 return 0; 396 } 397 398 sarg.out = out; 399 sarg.ndef_bio = NULL; 400 sarg.boundary = NULL; 401 402 /* Let ASN1 code prepend any needed BIOs */ 403 404 if (aux->asn1_cb(ASN1_OP_DETACHED_PRE, &val, it, &sarg) <= 0) 405 return 0; 406 407 /* Copy data across, passing through filter BIOs for processing */ 408 SMIME_crlf_copy(data, sarg.ndef_bio, flags); 409 410 /* Finalize structure */ 411 if (aux->asn1_cb(ASN1_OP_DETACHED_POST, &val, it, &sarg) <= 0) 412 rv = 0; 413 414 /* Now remove any digests prepended to the BIO */ 415 416 while (sarg.ndef_bio != out) { 417 tmpbio = BIO_pop(sarg.ndef_bio); 418 BIO_free(sarg.ndef_bio); 419 sarg.ndef_bio = tmpbio; 420 } 421 422 return rv; 423 } 424 425 /* SMIME reader: handle multipart/signed and opaque signing. 426 * in multipart case the content is placed in a memory BIO 427 * pointed to by "bcont". In opaque this is set to NULL 428 */ 429 430 ASN1_VALUE * 431 SMIME_read_ASN1(BIO *bio, BIO **bcont, const ASN1_ITEM *it) 432 { 433 BIO *asnin; 434 STACK_OF(MIME_HEADER) *headers = NULL; 435 STACK_OF(BIO) *parts = NULL; 436 MIME_HEADER *hdr; 437 MIME_PARAM *prm; 438 ASN1_VALUE *val; 439 int ret; 440 441 if (bcont) 442 *bcont = NULL; 443 444 if (!(headers = mime_parse_hdr(bio))) { 445 ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_MIME_PARSE_ERROR); 446 return NULL; 447 } 448 449 if (!(hdr = mime_hdr_find(headers, "content-type")) || !hdr->value) { 450 sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 451 ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_NO_CONTENT_TYPE); 452 return NULL; 453 } 454 455 /* Handle multipart/signed */ 456 457 if (!strcmp(hdr->value, "multipart/signed")) { 458 /* Split into two parts */ 459 prm = mime_param_find(hdr, "boundary"); 460 if (!prm || !prm->param_value) { 461 sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 462 ASN1err(ASN1_F_SMIME_READ_ASN1, 463 ASN1_R_NO_MULTIPART_BOUNDARY); 464 return NULL; 465 } 466 ret = multi_split(bio, prm->param_value, &parts); 467 sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 468 if (!ret || (sk_BIO_num(parts) != 2) ) { 469 ASN1err(ASN1_F_SMIME_READ_ASN1, 470 ASN1_R_NO_MULTIPART_BODY_FAILURE); 471 sk_BIO_pop_free(parts, BIO_vfree); 472 return NULL; 473 } 474 475 /* Parse the signature piece */ 476 asnin = sk_BIO_value(parts, 1); 477 478 if (!(headers = mime_parse_hdr(asnin))) { 479 ASN1err(ASN1_F_SMIME_READ_ASN1, 480 ASN1_R_MIME_SIG_PARSE_ERROR); 481 sk_BIO_pop_free(parts, BIO_vfree); 482 return NULL; 483 } 484 485 /* Get content type */ 486 487 if (!(hdr = mime_hdr_find(headers, "content-type")) || 488 !hdr->value) { 489 sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 490 ASN1err(ASN1_F_SMIME_READ_ASN1, 491 ASN1_R_NO_SIG_CONTENT_TYPE); 492 return NULL; 493 } 494 495 if (strcmp(hdr->value, "application/x-pkcs7-signature") && 496 strcmp(hdr->value, "application/pkcs7-signature")) { 497 ASN1err(ASN1_F_SMIME_READ_ASN1, 498 ASN1_R_SIG_INVALID_MIME_TYPE); 499 ERR_asprintf_error_data("type: %s", hdr->value); 500 sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 501 sk_BIO_pop_free(parts, BIO_vfree); 502 return NULL; 503 } 504 sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 505 /* Read in ASN1 */ 506 if (!(val = b64_read_asn1(asnin, it))) { 507 ASN1err(ASN1_F_SMIME_READ_ASN1, 508 ASN1_R_ASN1_SIG_PARSE_ERROR); 509 sk_BIO_pop_free(parts, BIO_vfree); 510 return NULL; 511 } 512 513 if (bcont) { 514 *bcont = sk_BIO_value(parts, 0); 515 BIO_free(asnin); 516 sk_BIO_free(parts); 517 } else sk_BIO_pop_free(parts, BIO_vfree); 518 return val; 519 } 520 521 /* OK, if not multipart/signed try opaque signature */ 522 523 if (strcmp (hdr->value, "application/x-pkcs7-mime") && 524 strcmp (hdr->value, "application/pkcs7-mime")) { 525 ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_INVALID_MIME_TYPE); 526 ERR_asprintf_error_data("type: %s", hdr->value); 527 sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 528 return NULL; 529 } 530 531 sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 532 533 if (!(val = b64_read_asn1(bio, it))) { 534 ASN1err(ASN1_F_SMIME_READ_ASN1, ASN1_R_ASN1_PARSE_ERROR); 535 return NULL; 536 } 537 return val; 538 } 539 540 /* Copy text from one BIO to another making the output CRLF at EOL */ 541 int 542 SMIME_crlf_copy(BIO *in, BIO *out, int flags) 543 { 544 BIO *bf; 545 char eol; 546 int len; 547 char linebuf[MAX_SMLEN]; 548 549 /* Buffer output so we don't write one line at a time. This is 550 * useful when streaming as we don't end up with one OCTET STRING 551 * per line. 552 */ 553 bf = BIO_new(BIO_f_buffer()); 554 if (!bf) 555 return 0; 556 out = BIO_push(bf, out); 557 if (flags & SMIME_BINARY) { 558 while ((len = BIO_read(in, linebuf, MAX_SMLEN)) > 0) 559 BIO_write(out, linebuf, len); 560 } else { 561 if (flags & SMIME_TEXT) 562 BIO_printf(out, "Content-Type: text/plain\r\n\r\n"); 563 while ((len = BIO_gets(in, linebuf, MAX_SMLEN)) > 0) { 564 eol = strip_eol(linebuf, &len); 565 if (len) 566 BIO_write(out, linebuf, len); 567 if (eol) 568 BIO_write(out, "\r\n", 2); 569 } 570 } 571 (void)BIO_flush(out); 572 BIO_pop(out); 573 BIO_free(bf); 574 return 1; 575 } 576 577 /* Strip off headers if they are text/plain */ 578 int 579 SMIME_text(BIO *in, BIO *out) 580 { 581 char iobuf[4096]; 582 int len; 583 STACK_OF(MIME_HEADER) *headers; 584 MIME_HEADER *hdr; 585 586 if (!(headers = mime_parse_hdr(in))) { 587 ASN1err(ASN1_F_SMIME_TEXT, ASN1_R_MIME_PARSE_ERROR); 588 return 0; 589 } 590 if (!(hdr = mime_hdr_find(headers, "content-type")) || !hdr->value) { 591 ASN1err(ASN1_F_SMIME_TEXT, ASN1_R_MIME_NO_CONTENT_TYPE); 592 sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 593 return 0; 594 } 595 if (strcmp (hdr->value, "text/plain")) { 596 ASN1err(ASN1_F_SMIME_TEXT, ASN1_R_INVALID_MIME_TYPE); 597 ERR_asprintf_error_data("type: %s", hdr->value); 598 sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 599 return 0; 600 } 601 sk_MIME_HEADER_pop_free(headers, mime_hdr_free); 602 while ((len = BIO_read(in, iobuf, sizeof(iobuf))) > 0) 603 BIO_write(out, iobuf, len); 604 if (len < 0) 605 return 0; 606 return 1; 607 } 608 609 /* Split a multipart/XXX message body into component parts: result is 610 * canonical parts in a STACK of bios 611 */ 612 613 static int 614 multi_split(BIO *bio, char *bound, STACK_OF(BIO) **ret) 615 { 616 char linebuf[MAX_SMLEN]; 617 int len, blen; 618 int eol = 0, next_eol = 0; 619 BIO *bpart = NULL; 620 STACK_OF(BIO) *parts; 621 char state, part, first; 622 623 blen = strlen(bound); 624 part = 0; 625 state = 0; 626 first = 1; 627 parts = sk_BIO_new_null(); 628 *ret = parts; 629 while ((len = BIO_gets(bio, linebuf, MAX_SMLEN)) > 0) { 630 state = mime_bound_check(linebuf, len, bound, blen); 631 if (state == 1) { 632 first = 1; 633 part++; 634 } else if (state == 2) { 635 sk_BIO_push(parts, bpart); 636 return 1; 637 } else if (part) { 638 /* Strip CR+LF from linebuf */ 639 next_eol = strip_eol(linebuf, &len); 640 if (first) { 641 first = 0; 642 if (bpart) 643 sk_BIO_push(parts, bpart); 644 bpart = BIO_new(BIO_s_mem()); 645 BIO_set_mem_eof_return(bpart, 0); 646 } else if (eol) 647 BIO_write(bpart, "\r\n", 2); 648 eol = next_eol; 649 if (len) 650 BIO_write(bpart, linebuf, len); 651 } 652 } 653 return 0; 654 } 655 656 /* This is the big one: parse MIME header lines up to message body */ 657 658 #define MIME_INVALID 0 659 #define MIME_START 1 660 #define MIME_TYPE 2 661 #define MIME_NAME 3 662 #define MIME_VALUE 4 663 #define MIME_QUOTE 5 664 #define MIME_COMMENT 6 665 666 667 static 668 STACK_OF(MIME_HEADER) *mime_parse_hdr(BIO *bio) 669 { 670 char *p, *q, c; 671 char *ntmp; 672 char linebuf[MAX_SMLEN]; 673 MIME_HEADER *mhdr = NULL; 674 STACK_OF(MIME_HEADER) *headers; 675 int len, state, save_state = 0; 676 677 headers = sk_MIME_HEADER_new(mime_hdr_cmp); 678 if (!headers) 679 return NULL; 680 while ((len = BIO_gets(bio, linebuf, MAX_SMLEN)) > 0) { 681 /* If whitespace at line start then continuation line */ 682 if (mhdr && isspace((unsigned char)linebuf[0])) 683 state = MIME_NAME; 684 else 685 state = MIME_START; 686 ntmp = NULL; 687 688 /* Go through all characters */ 689 for (p = linebuf, q = linebuf; 690 (c = *p) && (c != '\r') && (c != '\n'); p++) { 691 692 /* State machine to handle MIME headers 693 * if this looks horrible that's because it *is* 694 */ 695 696 switch (state) { 697 case MIME_START: 698 if (c == ':') { 699 state = MIME_TYPE; 700 *p = 0; 701 ntmp = strip_ends(q); 702 q = p + 1; 703 } 704 break; 705 706 case MIME_TYPE: 707 if (c == ';') { 708 mime_debug("Found End Value\n"); 709 *p = 0; 710 mhdr = mime_hdr_new(ntmp, 711 strip_ends(q)); 712 sk_MIME_HEADER_push(headers, mhdr); 713 ntmp = NULL; 714 q = p + 1; 715 state = MIME_NAME; 716 } else if (c == '(') { 717 save_state = state; 718 state = MIME_COMMENT; 719 } 720 break; 721 722 case MIME_COMMENT: 723 if (c == ')') { 724 state = save_state; 725 } 726 break; 727 728 case MIME_NAME: 729 if (c == '=') { 730 state = MIME_VALUE; 731 *p = 0; 732 ntmp = strip_ends(q); 733 q = p + 1; 734 } 735 break; 736 737 case MIME_VALUE: 738 if (c == ';') { 739 state = MIME_NAME; 740 *p = 0; 741 mime_hdr_addparam(mhdr, ntmp, 742 strip_ends(q)); 743 ntmp = NULL; 744 q = p + 1; 745 } else if (c == '"') { 746 mime_debug("Found Quote\n"); 747 state = MIME_QUOTE; 748 } else if (c == '(') { 749 save_state = state; 750 state = MIME_COMMENT; 751 } 752 break; 753 754 case MIME_QUOTE: 755 if (c == '"') { 756 mime_debug("Found Match Quote\n"); 757 state = MIME_VALUE; 758 } 759 break; 760 } 761 } 762 763 if (state == MIME_TYPE) { 764 mhdr = mime_hdr_new(ntmp, strip_ends(q)); 765 sk_MIME_HEADER_push(headers, mhdr); 766 } else if (state == MIME_VALUE) 767 mime_hdr_addparam(mhdr, ntmp, strip_ends(q)); 768 769 if (p == linebuf) 770 break; /* Blank line means end of headers */ 771 } 772 773 return headers; 774 } 775 776 static char * 777 strip_ends(char *name) 778 { 779 return strip_end(strip_start(name)); 780 } 781 782 /* Strip a parameter of whitespace from start of param */ 783 static char * 784 strip_start(char *name) 785 { 786 char *p, c; 787 788 /* Look for first non white space or quote */ 789 for (p = name; (c = *p); p++) { 790 if (c == '"') { 791 /* Next char is start of string if non null */ 792 if (p[1]) 793 return p + 1; 794 /* Else null string */ 795 return NULL; 796 } 797 if (!isspace((unsigned char)c)) 798 return p; 799 } 800 return NULL; 801 } 802 803 /* As above but strip from end of string : maybe should handle brackets? */ 804 static char * 805 strip_end(char *name) 806 { 807 char *p, c; 808 809 if (!name) 810 return NULL; 811 812 /* Look for first non white space or quote */ 813 for (p = name + strlen(name) - 1; p >= name; p--) { 814 c = *p; 815 if (c == '"') { 816 if (p - 1 == name) 817 return NULL; 818 *p = 0; 819 return name; 820 } 821 if (isspace((unsigned char)c)) 822 *p = 0; 823 else 824 return name; 825 } 826 return NULL; 827 } 828 829 static MIME_HEADER * 830 mime_hdr_new(char *name, char *value) 831 { 832 MIME_HEADER *mhdr; 833 char *tmpname = NULL, *tmpval = NULL, *p; 834 835 if (name) { 836 if (!(tmpname = strdup(name))) 837 goto err; 838 for (p = tmpname; *p; p++) 839 *p = tolower((unsigned char)*p); 840 } 841 if (value) { 842 if (!(tmpval = strdup(value))) 843 goto err; 844 for (p = tmpval; *p; p++) 845 *p = tolower((unsigned char)*p); 846 } 847 mhdr = malloc(sizeof(MIME_HEADER)); 848 if (!mhdr) 849 goto err; 850 mhdr->name = tmpname; 851 mhdr->value = tmpval; 852 if (!(mhdr->params = sk_MIME_PARAM_new(mime_param_cmp))) { 853 free(mhdr); 854 goto err; 855 } 856 return mhdr; 857 err: 858 free(tmpname); 859 free(tmpval); 860 return NULL; 861 } 862 863 static int 864 mime_hdr_addparam(MIME_HEADER *mhdr, char *name, char *value) 865 { 866 char *tmpname = NULL, *tmpval = NULL, *p; 867 MIME_PARAM *mparam; 868 869 if (name) { 870 tmpname = strdup(name); 871 if (!tmpname) 872 goto err; 873 for (p = tmpname; *p; p++) 874 *p = tolower((unsigned char)*p); 875 } 876 if (value) { 877 tmpval = strdup(value); 878 if (!tmpval) 879 goto err; 880 } 881 /* Parameter values are case sensitive so leave as is */ 882 mparam = malloc(sizeof(MIME_PARAM)); 883 if (!mparam) 884 goto err; 885 mparam->param_name = tmpname; 886 mparam->param_value = tmpval; 887 sk_MIME_PARAM_push(mhdr->params, mparam); 888 return 1; 889 err: 890 free(tmpname); 891 free(tmpval); 892 return 0; 893 } 894 895 static int 896 mime_hdr_cmp(const MIME_HEADER * const *a, const MIME_HEADER * const *b) 897 { 898 if (!(*a)->name || !(*b)->name) 899 return !!(*a)->name - !!(*b)->name; 900 return (strcmp((*a)->name, (*b)->name)); 901 } 902 903 static int 904 mime_param_cmp(const MIME_PARAM * const *a, const MIME_PARAM * const *b) 905 { 906 if (!(*a)->param_name || !(*b)->param_name) 907 return !!(*a)->param_name - !!(*b)->param_name; 908 return (strcmp((*a)->param_name, (*b)->param_name)); 909 } 910 911 /* Find a header with a given name (if possible) */ 912 913 static MIME_HEADER * 914 mime_hdr_find(STACK_OF(MIME_HEADER) *hdrs, char *name) 915 { 916 MIME_HEADER htmp; 917 int idx; 918 htmp.name = name; 919 idx = sk_MIME_HEADER_find(hdrs, &htmp); 920 if (idx < 0) 921 return NULL; 922 return sk_MIME_HEADER_value(hdrs, idx); 923 } 924 925 static MIME_PARAM * 926 mime_param_find(MIME_HEADER *hdr, char *name) 927 { 928 MIME_PARAM param; 929 int idx; 930 param.param_name = name; 931 idx = sk_MIME_PARAM_find(hdr->params, ¶m); 932 if (idx < 0) 933 return NULL; 934 return sk_MIME_PARAM_value(hdr->params, idx); 935 } 936 937 static void 938 mime_hdr_free(MIME_HEADER *hdr) 939 { 940 free(hdr->name); 941 free(hdr->value); 942 if (hdr->params) 943 sk_MIME_PARAM_pop_free(hdr->params, mime_param_free); 944 free(hdr); 945 } 946 947 static void 948 mime_param_free(MIME_PARAM *param) 949 { 950 free(param->param_name); 951 free(param->param_value); 952 free(param); 953 } 954 955 /* Check for a multipart boundary. Returns: 956 * 0 : no boundary 957 * 1 : part boundary 958 * 2 : final boundary 959 */ 960 static int 961 mime_bound_check(char *line, int linelen, char *bound, int blen) 962 { 963 if (linelen == -1) 964 linelen = strlen(line); 965 if (blen == -1) 966 blen = strlen(bound); 967 /* Quickly eliminate if line length too short */ 968 if (blen + 2 > linelen) 969 return 0; 970 /* Check for part boundary */ 971 if (!strncmp(line, "--", 2) && !strncmp(line + 2, bound, blen)) { 972 if (!strncmp(line + blen + 2, "--", 2)) 973 return 2; 974 else 975 return 1; 976 } 977 return 0; 978 } 979 980 static int 981 strip_eol(char *linebuf, int *plen) 982 { 983 int len = *plen; 984 char *p, c; 985 int is_eol = 0; 986 987 for (p = linebuf + len - 1; len > 0; len--, p--) { 988 c = *p; 989 if (c == '\n') 990 is_eol = 1; 991 else if (c != '\r') 992 break; 993 } 994 *plen = len; 995 return is_eol; 996 } 997