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