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