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