1 /* $NetBSD: mime_attach.c,v 1.13 2009/04/11 14:22:32 christos Exp $ */ 2 3 /*- 4 * Copyright (c) 2006 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Anon Ymous. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #ifdef MIME_SUPPORT 33 34 #include <sys/cdefs.h> 35 #ifndef __lint__ 36 __RCSID("$NetBSD: mime_attach.c,v 1.13 2009/04/11 14:22:32 christos Exp $"); 37 #endif /* not __lint__ */ 38 39 #include <assert.h> 40 #include <err.h> 41 #include <fcntl.h> 42 #include <libgen.h> 43 #include <magic.h> 44 #include <signal.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <unistd.h> 49 #include <util.h> 50 51 #include "def.h" 52 #include "extern.h" 53 #ifdef USE_EDITLINE 54 #include "complete.h" 55 #endif 56 #ifdef MIME_SUPPORT 57 #include "mime.h" 58 #include "mime_codecs.h" 59 #include "mime_child.h" 60 #endif 61 #include "glob.h" 62 #include "sig.h" 63 64 #if 0 65 /* 66 * XXX - This block is for debugging only and eventually should go away. 67 */ 68 # define SHOW_ALIST(a,b) show_alist(a,b) 69 static void 70 show_alist(struct attachment *alist, struct attachment *ap) 71 { 72 (void)printf("alist=%p ap=%p\n", alist, ap); 73 for (ap = alist; ap; ap = ap->a_flink) { 74 (void)printf("ap=%p ap->a_flink=%p ap->a_blink=%p ap->a_name=%s\n", 75 ap, ap->a_flink, ap->a_blink, ap->a_name ? ap->a_name : "<null>"); 76 } 77 } 78 #else 79 # define SHOW_ALIST(a,b) 80 #endif 81 82 #if 0 83 #ifndef __lint__ /* Don't lint: the public routines may not be used. */ 84 /* 85 * XXX - This block for is debugging only and eventually should go away. 86 */ 87 static void 88 show_name(const char *prefix, struct name *np) 89 { 90 int i; 91 92 i = 0; 93 for (/*EMPTY*/; np; np = np->n_flink) { 94 (void)printf("%s[%d]: %s\n", prefix, i, np->n_name); 95 i++; 96 } 97 } 98 99 static void fput_mime_content(FILE *fp, struct Content *Cp); 100 101 PUBLIC void 102 show_attach(const char *prefix, struct attachment *ap) 103 { 104 int i; 105 i = 1; 106 for (/*EMPTY*/; ap; ap = ap->a_flink) { 107 (void)printf("%s[%d]:\n", prefix, i); 108 fput_mime_content(stdout, &ap->a_Content); 109 i++; 110 } 111 } 112 113 PUBLIC void 114 show_header(struct header *hp) 115 { 116 show_name("TO", hp->h_to); 117 (void)printf("SUBJECT: %s\n", hp->h_subject); 118 show_name("CC", hp->h_cc); 119 show_name("BCC", hp->h_bcc); 120 show_name("SMOPTS", hp->h_smopts); 121 show_attach("ATTACH", hp->h_attach); 122 } 123 #endif /* __lint__ */ 124 #endif 125 126 /*************************** 127 * boundary string routines 128 */ 129 static char * 130 getrandstring(size_t length) 131 { 132 void *vbin; 133 uint32_t *bin; 134 size_t binlen; 135 size_t i; 136 char *b64; 137 138 /* XXX - check this stuff again!!! */ 139 140 binlen = 3 * roundup(length, 4) / 4; /* bytes of binary to encode base64 */ 141 bin = vbin = salloc(roundup(binlen, 4)); 142 for (i = 0; i < roundup(binlen, 4) / 4; i++) 143 bin[i] = arc4random(); 144 145 b64 = salloc(roundup(length, 4)); 146 mime_bintob64(b64, vbin, binlen); 147 b64[length] = '\0'; 148 149 return b64; 150 } 151 152 /* 153 * Generate a boundary for MIME multipart messages. 154 */ 155 static char * 156 make_boundary(void) 157 { 158 #define BOUND_LEN 70 /* maximum length is 70 characters: RFC2046 sec 5.1.1 */ 159 160 char *bound; 161 time_t now; 162 163 (void)time(&now); 164 bound = salloc(BOUND_LEN); 165 (void)snprintf(bound, BOUND_LEN, "=_%08lx.%s", 166 (long)now, getrandstring(BOUND_LEN - 12)); 167 return bound; 168 169 #undef BOUND_LEN 170 } 171 172 /*************************** 173 * Transfer coding routines 174 */ 175 /* 176 * We determine the recommended transfer encoding type for a file as 177 * follows: 178 * 179 * 1) If there is a NULL byte or a stray CR (not in a CRLF 180 * combination) in the file, play it safe and use base64. 181 * 182 * 2) If any high bit is set, use quoted-printable if the content type 183 * is "text" and base64 otherwise. 184 * 185 * 3) Otherwise: 186 * a) use quoted-printable if there are any long lines, control 187 * chars (including CR), end-of-line blank space, or a missing 188 * terminating NL. 189 * b) use 7bit in all remaining case, including an empty file. 190 * 191 * NOTE: This means that CRLF text (MSDOS) files will be encoded 192 * quoted-printable. 193 */ 194 /* 195 * RFC 821 imposes the following line length limit: 196 * The maximum total length of a text line including the 197 * <CRLF> is 1000 characters (but not counting the leading 198 * dot duplicated for transparency). 199 */ 200 #define MIME_UNENCODED_LINE_MAX (1000 - 2) 201 static size_t 202 line_limit(void) 203 { 204 int limit; 205 const char *cp; 206 limit = -1; 207 208 if ((cp = value(ENAME_MIME_UNENC_LINE_MAX)) != NULL) 209 limit = atoi(cp); 210 211 if (limit < 0 || limit > MIME_UNENCODED_LINE_MAX) 212 limit = MIME_UNENCODED_LINE_MAX; 213 214 return (size_t)limit; 215 } 216 217 static inline int 218 is_text(const char *ctype) 219 { 220 return ctype && 221 strncasecmp(ctype, "text/", sizeof("text/") - 1) == 0; 222 } 223 224 static const char * 225 content_encoding_core(void *fh, const char *ctype) 226 { 227 int c, lastc; 228 int ctrlchar, endwhite; 229 size_t curlen, maxlen; 230 231 curlen = 0; 232 maxlen = 0; 233 ctrlchar = 0; 234 endwhite = 0; 235 lastc = EOF; 236 while ((c = fgetc(fh)) != EOF) { 237 curlen++; 238 239 if (c == '\0' || (lastc == '\r' && c != '\n')) 240 return MIME_TRANSFER_BASE64; 241 242 if (c > 0x7f) { 243 if (is_text(ctype)) 244 return MIME_TRANSFER_QUOTED; 245 else 246 return MIME_TRANSFER_BASE64; 247 } 248 if (c == '\n') { 249 if (is_WSP(lastc)) 250 endwhite = 1; 251 if (curlen > maxlen) 252 maxlen = curlen; 253 curlen = 0; 254 } 255 else if ((c < 0x20 && c != '\t') || c == 0x7f) 256 ctrlchar = 1; 257 258 lastc = c; 259 } 260 if (lastc == EOF) /* no characters read */ 261 return MIME_TRANSFER_7BIT; 262 263 if (lastc != '\n' || ctrlchar || endwhite || maxlen > line_limit()) 264 return MIME_TRANSFER_QUOTED; 265 266 return MIME_TRANSFER_7BIT; 267 } 268 269 static const char * 270 content_encoding_by_name(const char *filename, const char *ctype) 271 { 272 FILE *fp; 273 const char *enc; 274 fp = Fopen(filename, "r"); 275 if (fp == NULL) { 276 warn("content_encoding_by_name: %s", filename); 277 return MIME_TRANSFER_BASE64; /* safe */ 278 } 279 enc = content_encoding_core(fp, ctype); 280 (void)Fclose(fp); 281 return enc; 282 } 283 284 static const char * 285 content_encoding_by_fileno(int fd, const char *ctype) 286 { 287 FILE *fp; 288 int fd2; 289 const char *encoding; 290 off_t cur_pos; 291 292 cur_pos = lseek(fd, (off_t)0, SEEK_CUR); 293 if ((fd2 = dup(fd)) == -1 || 294 (fp = Fdopen(fd2, "r")) == NULL) { 295 warn("content_encoding_by_fileno"); 296 if (fd2 != -1) 297 (void)close(fd2); 298 return MIME_TRANSFER_BASE64; 299 } 300 encoding = content_encoding_core(fp, ctype); 301 (void)Fclose(fp); 302 (void)lseek(fd, cur_pos, SEEK_SET); 303 return encoding; 304 } 305 306 static const char * 307 content_encoding(struct attachment *ap, const char *ctype) 308 { 309 switch (ap->a_type) { 310 case ATTACH_FNAME: 311 return content_encoding_by_name(ap->a_name, ctype); 312 case ATTACH_MSG: 313 return "7bit"; 314 case ATTACH_FILENO: 315 return content_encoding_by_fileno(ap->a_fileno, ctype); 316 case ATTACH_INVALID: 317 default: 318 /* This is a coding error! */ 319 assert(/* CONSTCOND */ 0); 320 errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type); 321 /* NOTREACHED */ 322 } 323 } 324 325 /************************ 326 * Content type routines 327 */ 328 /* 329 * We use libmagic(3) to get the content type, except in the case of a 330 * 0 or 1 byte file where libmagic gives rather useless results. 331 */ 332 static const char * 333 content_type_by_name(char *filename) 334 { 335 const char *cp; 336 char *cp2; 337 magic_t magic; 338 struct stat sb; 339 340 #ifdef BROKEN_MAGIC 341 /* 342 * libmagic(3) produces annoying results on very short files. 343 * The common case is MIME encoding an empty message body. 344 * XXX - it would be better to fix libmagic(3)! 345 * 346 * Note: a 1-byte message body always consists of a newline, 347 * so size determines all there. However, 1-byte attachments 348 * (filename != NULL) could be anything, so check those. 349 */ 350 if ((filename != NULL && stat(filename, &sb) == 0) || 351 (filename == NULL && fstat(0, &sb) == 0)) { 352 if (sb.st_size < 2 && S_ISREG(sb.st_mode)) { 353 FILE *fp; 354 int ch; 355 356 if (sb.st_size == 0 || filename == NULL || 357 (fp = Fopen(filename, "r")) == NULL) 358 return "text/plain"; 359 360 ch = fgetc(fp); 361 (void)Fclose(fp); 362 363 return isprint(ch) || isspace(ch) ? 364 "text/plain" : "application/octet-stream"; 365 } 366 } 367 #endif 368 magic = magic_open(MAGIC_MIME); 369 if (magic == NULL) { 370 warnx("magic_open: %s", magic_error(magic)); 371 return NULL; 372 } 373 if (magic_load(magic, NULL) != 0) { 374 warnx("magic_load: %s", magic_error(magic)); 375 return NULL; 376 } 377 cp = magic_file(magic, filename); 378 if (cp == NULL) { 379 warnx("magic_load: %s", magic_error(magic)); 380 return NULL; 381 } 382 if (filename && 383 sasprintf(&cp2, "%s; name=\"%s\"", cp, basename(filename)) != -1) 384 cp = cp2; 385 else 386 cp = savestr(cp); 387 magic_close(magic); 388 return cp; 389 } 390 391 static const char * 392 content_type_by_fileno(int fd) 393 { 394 const char *cp; 395 off_t cur_pos; 396 int ofd; 397 398 cur_pos = lseek(fd, (off_t)0, SEEK_CUR); 399 400 ofd = dup(0); /* save stdin */ 401 if (dup2(fd, 0) == -1) /* become stdin */ 402 warn("dup2"); 403 404 cp = content_type_by_name(NULL); 405 406 if (dup2(ofd, 0) == -1) /* restore stdin */ 407 warn("dup2"); 408 (void)close(ofd); /* close the copy */ 409 410 (void)lseek(fd, cur_pos, SEEK_SET); 411 return cp; 412 } 413 414 static const char * 415 content_type(struct attachment *ap) 416 { 417 switch (ap->a_type) { 418 case ATTACH_FNAME: 419 return content_type_by_name(ap->a_name); 420 case ATTACH_MSG: 421 /* 422 * Note: the encapusulated message header must include 423 * at least one of the "Date:", "From:", or "Subject:" 424 * fields. See rfc2046 Sec 5.2.1. 425 * XXX - Should we really test for this? 426 */ 427 return "message/rfc822"; 428 case ATTACH_FILENO: 429 return content_type_by_fileno(ap->a_fileno); 430 case ATTACH_INVALID: 431 default: 432 /* This is a coding error! */ 433 assert(/* CONSTCOND */ 0); 434 errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type); 435 /* NOTREACHED */ 436 } 437 } 438 439 /************************* 440 * Other content routines 441 */ 442 443 static const char * 444 content_disposition(struct attachment *ap) 445 { 446 switch (ap->a_type) { 447 case ATTACH_FNAME: { 448 char *disp; 449 (void)sasprintf(&disp, "attachment; filename=\"%s\"", 450 basename(ap->a_name)); 451 return disp; 452 } 453 case ATTACH_MSG: 454 return NULL; 455 case ATTACH_FILENO: 456 return "inline"; 457 458 case ATTACH_INVALID: 459 default: 460 /* This is a coding error! */ 461 assert(/* CONSTCOND */ 0); 462 errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type); 463 /* NOTREACHED */ 464 } 465 } 466 467 /*ARGSUSED*/ 468 static const char * 469 content_id(struct attachment *ap __unused) 470 { 471 /* XXX - to be written. */ 472 473 return NULL; 474 } 475 476 static const char * 477 content_description(struct attachment *attach, int attach_num) 478 { 479 if (attach_num) { 480 char *description; 481 (void)sasprintf(&description, "attachment %d", attach_num); 482 return description; 483 } 484 else 485 return attach->a_Content.C_description; 486 } 487 488 /******************************************* 489 * Routines to get the MIME content strings. 490 */ 491 PUBLIC struct Content 492 get_mime_content(struct attachment *ap, int i) 493 { 494 struct Content Cp; 495 496 Cp.C_type = content_type(ap); 497 Cp.C_encoding = content_encoding(ap, Cp.C_type); 498 Cp.C_disposition = content_disposition(ap); 499 Cp.C_id = content_id(ap); 500 Cp.C_description = content_description(ap, i); 501 502 return Cp; 503 } 504 505 /****************** 506 * Output routines 507 */ 508 static void 509 fput_mime_content(FILE *fp, struct Content *Cp) 510 { 511 (void)fprintf(fp, MIME_HDR_TYPE ": %s\n", Cp->C_type); 512 (void)fprintf(fp, MIME_HDR_ENCODING ": %s\n", Cp->C_encoding); 513 if (Cp->C_disposition) 514 (void)fprintf(fp, MIME_HDR_DISPOSITION ": %s\n", 515 Cp->C_disposition); 516 if (Cp->C_id) 517 (void)fprintf(fp, MIME_HDR_ID ": %s\n", Cp->C_id); 518 if (Cp->C_description) 519 (void)fprintf(fp, MIME_HDR_DESCRIPTION ": %s\n", 520 Cp->C_description); 521 } 522 523 static void 524 fput_body(FILE *fi, FILE *fo, struct Content *Cp) 525 { 526 mime_codec_t enc; 527 528 enc = mime_fio_encoder(Cp->C_encoding); 529 if (enc == NULL) 530 warnx("unknown transfer encoding type: %s\n", Cp->C_encoding); 531 else 532 enc(fi, fo, 0); 533 } 534 535 static void 536 fput_attachment(FILE *fo, struct attachment *ap) 537 { 538 FILE *fi; 539 struct Content *Cp = &ap->a_Content; 540 541 fput_mime_content(fo, &ap->a_Content); 542 (void)putc('\n', fo); 543 544 switch (ap->a_type) { 545 case ATTACH_FNAME: 546 fi = Fopen(ap->a_name, "r"); 547 if (fi == NULL) 548 err(EXIT_FAILURE, "Fopen: %s", ap->a_name); 549 break; 550 551 case ATTACH_FILENO: 552 /* 553 * XXX - we should really dup(2) here, however we are 554 * finished with the attachment, so the Fclose() below 555 * is OK for now. This will be changed in the future. 556 */ 557 fi = Fdopen(ap->a_fileno, "r"); 558 if (fi == NULL) 559 err(EXIT_FAILURE, "Fdopen: %d", ap->a_fileno); 560 break; 561 562 case ATTACH_MSG: { 563 char mailtempname[PATHSIZE]; 564 int fd; 565 566 fi = NULL; /* appease gcc */ 567 (void)snprintf(mailtempname, sizeof(mailtempname), 568 "%s/mail.RsXXXXXXXXXX", tmpdir); 569 if ((fd = mkstemp(mailtempname)) == -1 || 570 (fi = Fdopen(fd, "w+")) == NULL) { 571 if (fd != -1) 572 (void)close(fd); 573 err(EXIT_FAILURE, "%s", mailtempname); 574 } 575 (void)rm(mailtempname); 576 577 /* 578 * This is only used for forwarding, so use the forwardtab[]. 579 * 580 * XXX - sendmessage really needs a 'flags' argument 581 * so we don't have to play games. 582 */ 583 ap->a_msg->m_size--; /* XXX - remove trailing newline */ 584 (void)fputc('>', fi); /* XXX - hide the headerline */ 585 if (sendmessage(ap->a_msg, fi, forwardtab, NULL, NULL)) 586 (void)fprintf(stderr, ". . . forward failed, sorry.\n"); 587 ap->a_msg->m_size++; 588 589 rewind(fi); 590 break; 591 } 592 case ATTACH_INVALID: 593 default: 594 /* This is a coding error! */ 595 assert(/* CONSTCOND */ 0); 596 errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type); 597 } 598 599 fput_body(fi, fo, Cp); 600 (void)Fclose(fi); 601 } 602 603 /*********************************** 604 * Higher level attachment routines. 605 */ 606 607 static int 608 mktemp_file(FILE **nfo, FILE **nfi, const char *hint) 609 { 610 char tempname[PATHSIZE]; 611 int fd, fd2; 612 (void)snprintf(tempname, sizeof(tempname), "%s/%sXXXXXXXXXX", 613 tmpdir, hint); 614 if ((fd = mkstemp(tempname)) == -1 || 615 (*nfo = Fdopen(fd, "w")) == NULL) { 616 if (fd != -1) 617 (void)close(fd); 618 warn("%s", tempname); 619 return -1; 620 } 621 (void)rm(tempname); 622 if ((fd2 = dup(fd)) == -1 || 623 (*nfi = Fdopen(fd2, "r")) == NULL) { 624 warn("%s", tempname); 625 (void)Fclose(*nfo); 626 return -1; 627 } 628 return 0; 629 } 630 631 /* 632 * Repackage the mail as a multipart MIME message. This should always 633 * be called whenever there are attachments, but might be called even 634 * if there are none if we want to wrap the message in a MIME package. 635 */ 636 PUBLIC FILE * 637 mime_encode(FILE *fi, struct header *header) 638 { 639 struct attachment map; /* fake structure for the message body */ 640 struct attachment *attach; 641 struct attachment *ap; 642 FILE *nfi, *nfo; 643 644 attach = header->h_attach; 645 646 /* 647 * Make new phantom temporary file with read and write file 648 * handles: nfi and nfo, resp. 649 */ 650 if (mktemp_file(&nfo, &nfi, "mail.Rs") != 0) 651 return fi; 652 653 (void)memset(&map, 0, sizeof(map)); 654 map.a_type = ATTACH_FILENO; 655 map.a_fileno = fileno(fi); 656 657 map.a_Content = get_mime_content(&map, 0); 658 659 if (attach) { 660 /* Multi-part message: 661 * Make an attachment structure for the body message 662 * and make that the first element in the attach list. 663 */ 664 if (fsize(fi)) { 665 map.a_flink = attach; 666 attach->a_blink = ↦ 667 attach = ↦ 668 } 669 670 /* Construct our MIME boundary string - used by mime_putheader() */ 671 header->h_mime_boundary = make_boundary(); 672 673 (void)fprintf(nfo, "This is a multi-part message in MIME format.\n"); 674 675 for (ap = attach; ap; ap = ap->a_flink) { 676 (void)fprintf(nfo, "\n--%s\n", header->h_mime_boundary); 677 fput_attachment(nfo, ap); 678 } 679 680 /* the final boundary with two attached dashes */ 681 (void)fprintf(nfo, "\n--%s--\n", header->h_mime_boundary); 682 } 683 else { 684 /* Single-part message (no attachments): 685 * Update header->h_Content (used by mime_putheader()). 686 * Output the body contents. 687 */ 688 char *encoding; 689 690 header->h_Content = map.a_Content; 691 692 /* check for an encoding override */ 693 if ((encoding = value(ENAME_MIME_ENCODE_MSG)) && *encoding) 694 header->h_Content.C_encoding = encoding; 695 696 fput_body(fi, nfo, &header->h_Content); 697 } 698 (void)Fclose(fi); 699 (void)Fclose(nfo); 700 rewind(nfi); 701 return nfi; 702 } 703 704 static char* 705 check_filename(char *filename, char *canon_name) 706 { 707 int fd; 708 struct stat sb; 709 char *fname = filename; 710 711 /* We need to expand '~' if we got here from '~@'. The shell 712 * does this otherwise. 713 */ 714 if (fname[0] == '~' && fname[1] == '/') { 715 if (homedir && homedir[0] != '~') 716 (void)easprintf(&fname, "%s/%s", 717 homedir, fname + 2); 718 } 719 if (realpath(fname, canon_name) == NULL) { 720 warn("realpath: %s", filename); 721 canon_name = NULL; 722 goto done; 723 } 724 fd = open(canon_name, O_RDONLY, 0); 725 if (fd == -1) { 726 warnx("open: cannot read %s", filename); 727 canon_name = NULL; 728 goto done; 729 } 730 if (fstat(fd, &sb) == -1) { 731 warn("stat: %s", canon_name); 732 canon_name = NULL; 733 goto do_close; 734 } 735 if (!S_ISREG(sb.st_mode)) { 736 warnx("stat: %s is not a file", filename); 737 canon_name = NULL; 738 /* goto do_close; */ 739 } 740 do_close: 741 (void)close(fd); 742 done: 743 if (fname != filename) 744 free(fname); 745 746 return canon_name; 747 } 748 749 static struct attachment * 750 attach_one_file(struct attachment *ap, char *filename, int attach_num) 751 { 752 char canon_name[MAXPATHLEN]; 753 struct attachment *nap; 754 755 /* 756 * 1) check that filename is really a readable file; return NULL if not. 757 * 2) allocate an attachment structure. 758 * 3) save cananonical name for filename, so cd won't screw things later. 759 * 4) add the structure to the end of the chain. 760 * 5) return the new attachment structure. 761 */ 762 if (check_filename(filename, canon_name) == NULL) 763 return NULL; 764 765 nap = csalloc(1, sizeof(*nap)); 766 nap->a_type = ATTACH_FNAME; 767 nap->a_name = savestr(canon_name); 768 769 if (ap) { 770 for (/*EMPTY*/; ap->a_flink != NULL; ap = ap->a_flink) 771 continue; 772 ap->a_flink = nap; 773 nap->a_blink = ap; 774 } 775 776 if (attach_num) 777 nap->a_Content = get_mime_content(nap, attach_num); 778 779 return nap; 780 } 781 782 static char * 783 get_line(el_mode_t *em, const char *pr, const char *str, int i) 784 { 785 char *prompt; 786 char *line; 787 788 /* 789 * Don't use a '\t' in the format string here as completion 790 * seems to handle it badly. 791 */ 792 (void)easprintf(&prompt, "#%-7d %s: ", i, pr); 793 line = my_gets(em, prompt, __UNCONST(str)); 794 if (line != NULL) { 795 (void)strip_WSP(line); /* strip trailing whitespace */ 796 line = skip_WSP(line); /* skip leading white space */ 797 line = savestr(line); /* XXX - do we need this? */ 798 } 799 else { 800 line = __UNCONST(""); 801 } 802 free(prompt); 803 804 return line; 805 } 806 807 static void 808 sget_line(el_mode_t *em, const char *pr, const char **str, int i) 809 { 810 char *line; 811 line = get_line(em, pr, *str, i); 812 if (line != NULL && strcmp(line, *str) != 0) 813 *str = line; 814 } 815 816 static void 817 sget_encoding(const char **str, const char *filename, const char *ctype, int num) 818 { 819 const char *ename; 820 const char *defename; 821 822 defename = NULL; 823 ename = *str; 824 for (;;) { 825 ename = get_line(&elm.mime_enc, "encoding", ename, num); 826 827 if (*ename == '\0') { 828 if (defename == NULL) 829 defename = content_encoding_by_name(filename, ctype); 830 ename = defename; 831 } 832 else if (mime_fio_encoder(ename) == NULL) { 833 const void *cookie; 834 (void)printf("Sorry: valid encoding modes are: "); 835 cookie = NULL; 836 ename = mime_next_encoding_name(&cookie); 837 for (;;) { 838 (void)printf("%s", ename); 839 ename = mime_next_encoding_name(&cookie); 840 if (ename == NULL) 841 break; 842 (void)fputc(',', stdout); 843 } 844 (void)putchar('\n'); 845 ename = *str; 846 } 847 else { 848 if (strcmp(ename, *str) != 0) 849 *str = savestr(ename); 850 break; 851 } 852 } 853 } 854 855 /* 856 * Edit an attachment list. 857 * Return the new attachment list. 858 */ 859 static struct attachment * 860 edit_attachlist(struct attachment *alist) 861 { 862 struct attachment *ap; 863 char *line; 864 int attach_num; 865 866 (void)printf("Attachments:\n"); 867 868 attach_num = 1; 869 ap = alist; 870 while (ap) { 871 SHOW_ALIST(alist, ap); 872 873 switch(ap->a_type) { 874 case ATTACH_MSG: 875 (void)printf("#%-7d message: <not changeable>\n", 876 attach_num); 877 break; 878 case ATTACH_FNAME: 879 case ATTACH_FILENO: 880 line = get_line(&elm.filec, "filename", ap->a_name, attach_num); 881 if (*line == '\0') { /* omit this attachment */ 882 if (ap->a_blink) { 883 struct attachment *next_ap; 884 next_ap = ap->a_flink; 885 ap = ap->a_blink; 886 ap->a_flink = next_ap; 887 if (next_ap) 888 next_ap->a_blink = ap; 889 else 890 goto done; 891 } 892 else { 893 alist = ap->a_flink; 894 if (alist) 895 alist->a_blink = NULL; 896 } 897 } 898 else { 899 char canon_name[MAXPATHLEN]; 900 if (strcmp(line, ap->a_name) != 0) { /* new filename */ 901 if (check_filename(line, canon_name) == NULL) 902 continue; 903 ap->a_name = savestr(canon_name); 904 ap->a_Content = get_mime_content(ap, 0); 905 } 906 sget_line(&elm.string, "description", 907 &ap->a_Content.C_description, attach_num); 908 sget_encoding(&ap->a_Content.C_encoding, ap->a_name, 909 ap->a_Content.C_type, attach_num); 910 } 911 break; 912 case ATTACH_INVALID: 913 default: 914 /* This is a coding error! */ 915 assert(/* CONSTCOND */ 0); 916 errx(EXIT_FAILURE, "invalid attachment type: %d", 917 ap->a_type); 918 } 919 920 attach_num++; 921 if (alist == NULL || ap->a_flink == NULL) 922 break; 923 924 ap = ap->a_flink; 925 } 926 927 ap = alist; 928 for (;;) { 929 struct attachment *nap; 930 931 SHOW_ALIST(alist, ap); 932 933 line = get_line(&elm.filec, "filename", "", attach_num); 934 if (*line == '\0') 935 break; 936 937 nap = attach_one_file(ap, line, attach_num); 938 if (nap == NULL) 939 continue; 940 941 if (alist == NULL) 942 alist = nap; 943 ap = nap; 944 945 sget_line(&elm.string, "description", 946 &ap->a_Content.C_description, attach_num); 947 sget_encoding(&ap->a_Content.C_encoding, ap->a_name, 948 ap->a_Content.C_type, attach_num); 949 attach_num++; 950 } 951 done: 952 SHOW_ALIST(alist, ap); 953 954 return alist; 955 } 956 957 /* 958 * Hook used by the '~@' escape to attach files. 959 */ 960 PUBLIC struct attachment* 961 mime_attach_files(struct attachment * volatile attach, char *linebuf) 962 { 963 struct attachment *ap; 964 char *argv[MAXARGC]; 965 int argc; 966 int attach_num; 967 968 argc = getrawlist(linebuf, argv, (int)__arraycount(argv)); 969 attach_num = 1; 970 for (ap = attach; ap && ap->a_flink; ap = ap->a_flink) 971 attach_num++; 972 973 if (argc) { 974 int i; 975 for (i = 0; i < argc; i++) { 976 struct attachment *ap2; 977 ap2 = attach_one_file(ap, argv[i], attach_num); 978 if (ap2 != NULL) { 979 ap = ap2; 980 if (attach == NULL) 981 attach = ap; 982 attach_num++; 983 } 984 } 985 } 986 else { 987 attach = edit_attachlist(attach); 988 (void)printf("--- end attachments ---\n"); 989 } 990 991 return attach; 992 } 993 994 /* 995 * Hook called in main() to attach files registered by the '-a' flag. 996 */ 997 PUBLIC struct attachment * 998 mime_attach_optargs(struct name *optargs) 999 { 1000 struct attachment *attach; 1001 struct attachment *ap; 1002 struct name *np; 1003 char *expand_optargs; 1004 int attach_num; 1005 1006 expand_optargs = value(ENAME_MIME_ATTACH_LIST); 1007 attach_num = 1; 1008 ap = NULL; 1009 attach = NULL; 1010 for (np = optargs; np; np = np->n_flink) { 1011 char *argv[MAXARGC]; 1012 int argc; 1013 int i; 1014 1015 if (expand_optargs != NULL) 1016 argc = getrawlist(np->n_name, 1017 argv, (int)__arraycount(argv)); 1018 else { 1019 if (np->n_name == '\0') 1020 argc = 0; 1021 else { 1022 argc = 1; 1023 argv[0] = np->n_name; 1024 } 1025 argv[argc] = NULL;/* be consistent with getrawlist() */ 1026 } 1027 for (i = 0; i < argc; i++) { 1028 struct attachment *ap2; 1029 char *filename; 1030 1031 if (argv[i][0] == '/') /* an absolute path */ 1032 (void)easprintf(&filename, "%s", argv[i]); 1033 else 1034 (void)easprintf(&filename, "%s/%s", 1035 origdir, argv[i]); 1036 1037 ap2 = attach_one_file(ap, filename, attach_num); 1038 if (ap2 != NULL) { 1039 ap = ap2; 1040 if (attach == NULL) 1041 attach = ap; 1042 attach_num++; 1043 } 1044 free(filename); 1045 } 1046 } 1047 return attach; 1048 } 1049 1050 /* 1051 * Output MIME header strings as specified in the header structure. 1052 */ 1053 PUBLIC void 1054 mime_putheader(FILE *fp, struct header *header) 1055 { 1056 (void)fprintf(fp, MIME_HDR_VERSION ": " MIME_VERSION "\n"); 1057 if (header->h_attach) { 1058 (void)fprintf(fp, MIME_HDR_TYPE ": multipart/mixed;\n"); 1059 (void)fprintf(fp, "\tboundary=\"%s\"\n", header->h_mime_boundary); 1060 } 1061 else { 1062 fput_mime_content(fp, &header->h_Content); 1063 } 1064 } 1065 1066 #endif /* MIME_SUPPORT */ 1067