1 /* $NetBSD: mime_attach.c,v 1.16 2013/01/04 01:54:55 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.16 2013/01/04 01:54:55 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 #define MAILMSG_CLEAN 0x0 228 #define MAILMSG_ENDWS 0x1 229 #define MAILMSG_CTRLC 0x2 230 #define MAILMSG_8BIT 0x4 231 #define MAILMSG_LONGL 0x8 232 int c, lastc, state; 233 int ctrlchar, endwhite; 234 size_t curlen, maxlen; 235 236 state = MAILMSG_CLEAN; 237 curlen = 0; 238 maxlen = line_limit(); 239 ctrlchar = 0; 240 endwhite = 0; 241 lastc = EOF; 242 while ((c = fgetc(fh)) != EOF) { 243 curlen++; 244 245 if (c == '\0') 246 return MIME_TRANSFER_BASE64; 247 248 if (c > 0x7f) { 249 if (!is_text(ctype)) 250 return MIME_TRANSFER_BASE64; 251 state |= MAILMSG_8BIT; 252 continue; 253 } 254 if (c == '\n') { 255 if (is_WSP(lastc)) 256 state |= MAILMSG_ENDWS; 257 if (curlen > maxlen) 258 state |= MAILMSG_LONGL; 259 curlen = 0; 260 } 261 else if ((c < 0x20 && c != '\t') || c == 0x7f || lastc == '\r') 262 state |= MAILMSG_CTRLC; 263 lastc = c; 264 } 265 if (lastc == EOF) /* no characters read */ 266 return MIME_TRANSFER_7BIT; 267 268 if (lastc != '\n' || state != MAILMSG_CLEAN) 269 return MIME_TRANSFER_QUOTED; 270 271 return MIME_TRANSFER_7BIT; 272 } 273 274 static const char * 275 content_encoding_by_name(const char *filename, const char *ctype) 276 { 277 FILE *fp; 278 const char *enc; 279 fp = Fopen(filename, "re"); 280 if (fp == NULL) { 281 warn("content_encoding_by_name: %s", filename); 282 return MIME_TRANSFER_BASE64; /* safe */ 283 } 284 enc = content_encoding_core(fp, ctype); 285 (void)Fclose(fp); 286 return enc; 287 } 288 289 static const char * 290 content_encoding_by_fileno(int fd, const char *ctype) 291 { 292 FILE *fp; 293 int fd2; 294 const char *encoding; 295 off_t cur_pos; 296 297 cur_pos = lseek(fd, (off_t)0, SEEK_CUR); 298 if ((fd2 = dup(fd)) == -1 || 299 (fp = Fdopen(fd2, "re")) == NULL) { 300 warn("content_encoding_by_fileno"); 301 if (fd2 != -1) 302 (void)close(fd2); 303 return MIME_TRANSFER_BASE64; 304 } 305 encoding = content_encoding_core(fp, ctype); 306 (void)Fclose(fp); 307 (void)lseek(fd, cur_pos, SEEK_SET); 308 return encoding; 309 } 310 311 static const char * 312 content_encoding(struct attachment *ap, const char *ctype) 313 { 314 switch (ap->a_type) { 315 case ATTACH_FNAME: 316 return content_encoding_by_name(ap->a_name, ctype); 317 case ATTACH_MSG: 318 return "7bit"; 319 case ATTACH_FILENO: 320 return content_encoding_by_fileno(ap->a_fileno, ctype); 321 case ATTACH_INVALID: 322 default: 323 /* This is a coding error! */ 324 assert(/* CONSTCOND */ 0); 325 errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type); 326 /* NOTREACHED */ 327 } 328 } 329 330 /************************ 331 * Content type routines 332 */ 333 /* 334 * We use libmagic(3) to get the content type, except in the case of a 335 * 0 or 1 byte file where libmagic gives rather useless results. 336 */ 337 static const char * 338 content_type_by_name(char *filename) 339 { 340 const char *cp; 341 char *cp2; 342 magic_t magic; 343 struct stat sb; 344 345 #ifdef BROKEN_MAGIC 346 /* 347 * libmagic(3) produces annoying results on very short files. 348 * The common case is MIME encoding an empty message body. 349 * XXX - it would be better to fix libmagic(3)! 350 * 351 * Note: a 1-byte message body always consists of a newline, 352 * so size determines all there. However, 1-byte attachments 353 * (filename != NULL) could be anything, so check those. 354 */ 355 if ((filename != NULL && stat(filename, &sb) == 0) || 356 (filename == NULL && fstat(0, &sb) == 0)) { 357 if (sb.st_size < 2 && S_ISREG(sb.st_mode)) { 358 FILE *fp; 359 int ch; 360 361 if (sb.st_size == 0 || filename == NULL || 362 (fp = Fopen(filename, "re")) == NULL) 363 return "text/plain"; 364 365 ch = fgetc(fp); 366 (void)Fclose(fp); 367 368 return isprint(ch) || isspace(ch) ? 369 "text/plain" : "application/octet-stream"; 370 } 371 } 372 #endif 373 magic = magic_open(MAGIC_MIME); 374 if (magic == NULL) { 375 warnx("magic_open: %s", magic_error(magic)); 376 return NULL; 377 } 378 if (magic_load(magic, NULL) != 0) { 379 warnx("magic_load: %s", magic_error(magic)); 380 return NULL; 381 } 382 cp = magic_file(magic, filename); 383 if (cp == NULL) { 384 warnx("magic_load: %s", magic_error(magic)); 385 return NULL; 386 } 387 if (filename && 388 sasprintf(&cp2, "%s; name=\"%s\"", cp, basename(filename)) != -1) 389 cp = cp2; 390 else 391 cp = savestr(cp); 392 magic_close(magic); 393 return cp; 394 } 395 396 static const char * 397 content_type_by_fileno(int fd) 398 { 399 const char *cp; 400 off_t cur_pos; 401 int ofd; 402 403 cur_pos = lseek(fd, (off_t)0, SEEK_CUR); 404 405 ofd = dup(0); /* save stdin */ 406 if (dup2(fd, 0) == -1) /* become stdin */ 407 warn("dup2"); 408 409 cp = content_type_by_name(NULL); 410 411 if (dup2(ofd, 0) == -1) /* restore stdin */ 412 warn("dup2"); 413 (void)close(ofd); /* close the copy */ 414 415 (void)lseek(fd, cur_pos, SEEK_SET); 416 return cp; 417 } 418 419 static const char * 420 content_type(struct attachment *ap) 421 { 422 switch (ap->a_type) { 423 case ATTACH_FNAME: 424 return content_type_by_name(ap->a_name); 425 case ATTACH_MSG: 426 /* 427 * Note: the encapusulated message header must include 428 * at least one of the "Date:", "From:", or "Subject:" 429 * fields. See rfc2046 Sec 5.2.1. 430 * XXX - Should we really test for this? 431 */ 432 return "message/rfc822"; 433 case ATTACH_FILENO: 434 return content_type_by_fileno(ap->a_fileno); 435 case ATTACH_INVALID: 436 default: 437 /* This is a coding error! */ 438 assert(/* CONSTCOND */ 0); 439 errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type); 440 /* NOTREACHED */ 441 } 442 } 443 444 /************************* 445 * Other content routines 446 */ 447 448 static const char * 449 content_disposition(struct attachment *ap) 450 { 451 switch (ap->a_type) { 452 case ATTACH_FNAME: { 453 char *disp; 454 (void)sasprintf(&disp, "attachment; filename=\"%s\"", 455 basename(ap->a_name)); 456 return disp; 457 } 458 case ATTACH_MSG: 459 return NULL; 460 case ATTACH_FILENO: 461 return "inline"; 462 463 case ATTACH_INVALID: 464 default: 465 /* This is a coding error! */ 466 assert(/* CONSTCOND */ 0); 467 errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type); 468 /* NOTREACHED */ 469 } 470 } 471 472 /*ARGSUSED*/ 473 static const char * 474 content_id(struct attachment *ap __unused) 475 { 476 /* XXX - to be written. */ 477 478 return NULL; 479 } 480 481 static const char * 482 content_description(struct attachment *attach, int attach_num) 483 { 484 if (attach_num) { 485 char *description; 486 (void)sasprintf(&description, "attachment %d", attach_num); 487 return description; 488 } 489 else 490 return attach->a_Content.C_description; 491 } 492 493 /******************************************* 494 * Routines to get the MIME content strings. 495 */ 496 PUBLIC struct Content 497 get_mime_content(struct attachment *ap, int i) 498 { 499 struct Content Cp; 500 501 Cp.C_type = content_type(ap); 502 Cp.C_encoding = content_encoding(ap, Cp.C_type); 503 Cp.C_disposition = content_disposition(ap); 504 Cp.C_id = content_id(ap); 505 Cp.C_description = content_description(ap, i); 506 507 return Cp; 508 } 509 510 /****************** 511 * Output routines 512 */ 513 static void 514 fput_mime_content(FILE *fp, struct Content *Cp) 515 { 516 (void)fprintf(fp, MIME_HDR_TYPE ": %s\n", Cp->C_type); 517 (void)fprintf(fp, MIME_HDR_ENCODING ": %s\n", Cp->C_encoding); 518 if (Cp->C_disposition) 519 (void)fprintf(fp, MIME_HDR_DISPOSITION ": %s\n", 520 Cp->C_disposition); 521 if (Cp->C_id) 522 (void)fprintf(fp, MIME_HDR_ID ": %s\n", Cp->C_id); 523 if (Cp->C_description) 524 (void)fprintf(fp, MIME_HDR_DESCRIPTION ": %s\n", 525 Cp->C_description); 526 } 527 528 static void 529 fput_body(FILE *fi, FILE *fo, struct Content *Cp) 530 { 531 mime_codec_t enc; 532 533 enc = mime_fio_encoder(Cp->C_encoding); 534 if (enc == NULL) 535 warnx("unknown transfer encoding type: %s\n", Cp->C_encoding); 536 else 537 enc(fi, fo, 0); 538 } 539 540 static void 541 fput_attachment(FILE *fo, struct attachment *ap) 542 { 543 FILE *fi; 544 struct Content *Cp = &ap->a_Content; 545 546 fput_mime_content(fo, &ap->a_Content); 547 (void)putc('\n', fo); 548 549 switch (ap->a_type) { 550 case ATTACH_FNAME: 551 fi = Fopen(ap->a_name, "re"); 552 if (fi == NULL) 553 err(EXIT_FAILURE, "Fopen: %s", ap->a_name); 554 break; 555 556 case ATTACH_FILENO: 557 /* 558 * XXX - we should really dup(2) here, however we are 559 * finished with the attachment, so the Fclose() below 560 * is OK for now. This will be changed in the future. 561 */ 562 fi = Fdopen(ap->a_fileno, "re"); 563 if (fi == NULL) 564 err(EXIT_FAILURE, "Fdopen: %d", ap->a_fileno); 565 break; 566 567 case ATTACH_MSG: { 568 char mailtempname[PATHSIZE]; 569 int fd; 570 571 fi = NULL; /* appease gcc */ 572 (void)snprintf(mailtempname, sizeof(mailtempname), 573 "%s/mail.RsXXXXXXXXXX", tmpdir); 574 if ((fd = mkstemp(mailtempname)) == -1 || 575 (fi = Fdopen(fd, "we+")) == NULL) { 576 if (fd != -1) 577 (void)close(fd); 578 err(EXIT_FAILURE, "%s", mailtempname); 579 } 580 (void)rm(mailtempname); 581 582 /* 583 * This is only used for forwarding, so use the forwardtab[]. 584 * 585 * XXX - sendmessage really needs a 'flags' argument 586 * so we don't have to play games. 587 */ 588 ap->a_msg->m_size--; /* XXX - remove trailing newline */ 589 (void)fputc('>', fi); /* XXX - hide the headerline */ 590 if (sendmessage(ap->a_msg, fi, forwardtab, NULL, NULL)) 591 (void)fprintf(stderr, ". . . forward failed, sorry.\n"); 592 ap->a_msg->m_size++; 593 594 rewind(fi); 595 break; 596 } 597 case ATTACH_INVALID: 598 default: 599 /* This is a coding error! */ 600 assert(/* CONSTCOND */ 0); 601 errx(EXIT_FAILURE, "invalid attachment type: %d", ap->a_type); 602 } 603 604 fput_body(fi, fo, Cp); 605 (void)Fclose(fi); 606 } 607 608 /*********************************** 609 * Higher level attachment routines. 610 */ 611 612 static int 613 mktemp_file(FILE **nfo, FILE **nfi, const char *hint) 614 { 615 char tempname[PATHSIZE]; 616 int fd, fd2; 617 (void)snprintf(tempname, sizeof(tempname), "%s/%sXXXXXXXXXX", 618 tmpdir, hint); 619 if ((fd = mkstemp(tempname)) == -1 || 620 (*nfo = Fdopen(fd, "we")) == NULL) { 621 if (fd != -1) 622 (void)close(fd); 623 warn("%s", tempname); 624 return -1; 625 } 626 (void)rm(tempname); 627 if ((fd2 = dup(fd)) == -1 || 628 (*nfi = Fdopen(fd2, "re")) == NULL) { 629 warn("%s", tempname); 630 (void)Fclose(*nfo); 631 return -1; 632 } 633 return 0; 634 } 635 636 /* 637 * Repackage the mail as a multipart MIME message. This should always 638 * be called whenever there are attachments, but might be called even 639 * if there are none if we want to wrap the message in a MIME package. 640 */ 641 PUBLIC FILE * 642 mime_encode(FILE *fi, struct header *header) 643 { 644 struct attachment map; /* fake structure for the message body */ 645 struct attachment *attach; 646 struct attachment *ap; 647 FILE *nfi, *nfo; 648 649 attach = header->h_attach; 650 651 /* 652 * Make new phantom temporary file with read and write file 653 * handles: nfi and nfo, resp. 654 */ 655 if (mktemp_file(&nfo, &nfi, "mail.Rs") != 0) 656 return fi; 657 658 (void)memset(&map, 0, sizeof(map)); 659 map.a_type = ATTACH_FILENO; 660 map.a_fileno = fileno(fi); 661 662 map.a_Content = get_mime_content(&map, 0); 663 664 if (attach) { 665 /* Multi-part message: 666 * Make an attachment structure for the body message 667 * and make that the first element in the attach list. 668 */ 669 if (fsize(fi)) { 670 map.a_flink = attach; 671 attach->a_blink = ↦ 672 attach = ↦ 673 } 674 675 /* Construct our MIME boundary string - used by mime_putheader() */ 676 header->h_mime_boundary = make_boundary(); 677 678 (void)fprintf(nfo, "This is a multi-part message in MIME format.\n"); 679 680 for (ap = attach; ap; ap = ap->a_flink) { 681 (void)fprintf(nfo, "\n--%s\n", header->h_mime_boundary); 682 fput_attachment(nfo, ap); 683 } 684 685 /* the final boundary with two attached dashes */ 686 (void)fprintf(nfo, "\n--%s--\n", header->h_mime_boundary); 687 } 688 else { 689 /* Single-part message (no attachments): 690 * Update header->h_Content (used by mime_putheader()). 691 * Output the body contents. 692 */ 693 char *encoding; 694 695 header->h_Content = map.a_Content; 696 697 /* check for an encoding override */ 698 if ((encoding = value(ENAME_MIME_ENCODE_MSG)) && *encoding) 699 header->h_Content.C_encoding = encoding; 700 701 fput_body(fi, nfo, &header->h_Content); 702 } 703 (void)Fclose(fi); 704 (void)Fclose(nfo); 705 rewind(nfi); 706 return nfi; 707 } 708 709 static char* 710 check_filename(char *filename, char *canon_name) 711 { 712 int fd; 713 struct stat sb; 714 char *fname = filename; 715 716 /* We need to expand '~' if we got here from '~@'. The shell 717 * does this otherwise. 718 */ 719 if (fname[0] == '~' && fname[1] == '/') { 720 if (homedir && homedir[0] != '~') 721 (void)easprintf(&fname, "%s/%s", 722 homedir, fname + 2); 723 } 724 if (realpath(fname, canon_name) == NULL) { 725 warn("realpath: %s", filename); 726 canon_name = NULL; 727 goto done; 728 } 729 fd = open(canon_name, O_RDONLY, 0); 730 if (fd == -1) { 731 warnx("open: cannot read %s", filename); 732 canon_name = NULL; 733 goto done; 734 } 735 if (fstat(fd, &sb) == -1) { 736 warn("stat: %s", canon_name); 737 canon_name = NULL; 738 goto do_close; 739 } 740 if (!S_ISREG(sb.st_mode)) { 741 warnx("stat: %s is not a file", filename); 742 canon_name = NULL; 743 /* goto do_close; */ 744 } 745 do_close: 746 (void)close(fd); 747 done: 748 if (fname != filename) 749 free(fname); 750 751 return canon_name; 752 } 753 754 static struct attachment * 755 attach_one_file(struct attachment *ap, char *filename, int attach_num) 756 { 757 char canon_name[MAXPATHLEN]; 758 struct attachment *nap; 759 760 /* 761 * 1) check that filename is really a readable file; return NULL if not. 762 * 2) allocate an attachment structure. 763 * 3) save cananonical name for filename, so cd won't screw things later. 764 * 4) add the structure to the end of the chain. 765 * 5) return the new attachment structure. 766 */ 767 if (check_filename(filename, canon_name) == NULL) 768 return NULL; 769 770 nap = csalloc(1, sizeof(*nap)); 771 nap->a_type = ATTACH_FNAME; 772 nap->a_name = savestr(canon_name); 773 774 if (ap) { 775 for (/*EMPTY*/; ap->a_flink != NULL; ap = ap->a_flink) 776 continue; 777 ap->a_flink = nap; 778 nap->a_blink = ap; 779 } 780 781 if (attach_num) 782 nap->a_Content = get_mime_content(nap, attach_num); 783 784 return nap; 785 } 786 787 static char * 788 get_line(el_mode_t *em, const char *pr, const char *str, int i) 789 { 790 char *prompt; 791 char *line; 792 793 /* 794 * Don't use a '\t' in the format string here as completion 795 * seems to handle it badly. 796 */ 797 (void)easprintf(&prompt, "#%-7d %s: ", i, pr); 798 line = my_gets(em, prompt, __UNCONST(str)); 799 if (line != NULL) { 800 (void)strip_WSP(line); /* strip trailing whitespace */ 801 line = skip_WSP(line); /* skip leading white space */ 802 line = savestr(line); /* XXX - do we need this? */ 803 } 804 else { 805 line = __UNCONST(""); 806 } 807 free(prompt); 808 809 return line; 810 } 811 812 static void 813 sget_line(el_mode_t *em, const char *pr, const char **str, int i) 814 { 815 char *line; 816 line = get_line(em, pr, *str, i); 817 if (line != NULL && strcmp(line, *str) != 0) 818 *str = line; 819 } 820 821 static void 822 sget_encoding(const char **str, const char *filename, const char *ctype, int num) 823 { 824 const char *ename; 825 const char *defename; 826 827 defename = NULL; 828 ename = *str; 829 for (;;) { 830 ename = get_line(&elm.mime_enc, "encoding", ename, num); 831 832 if (*ename == '\0') { 833 if (defename == NULL) 834 defename = content_encoding_by_name(filename, ctype); 835 ename = defename; 836 } 837 else if (mime_fio_encoder(ename) == NULL) { 838 const void *cookie; 839 (void)printf("Sorry: valid encoding modes are: "); 840 cookie = NULL; 841 ename = mime_next_encoding_name(&cookie); 842 for (;;) { 843 (void)printf("%s", ename); 844 ename = mime_next_encoding_name(&cookie); 845 if (ename == NULL) 846 break; 847 (void)fputc(',', stdout); 848 } 849 (void)putchar('\n'); 850 ename = *str; 851 } 852 else { 853 if (strcmp(ename, *str) != 0) 854 *str = savestr(ename); 855 break; 856 } 857 } 858 } 859 860 /* 861 * Edit an attachment list. 862 * Return the new attachment list. 863 */ 864 static struct attachment * 865 edit_attachlist(struct attachment *alist) 866 { 867 struct attachment *ap; 868 char *line; 869 int attach_num; 870 871 (void)printf("Attachments:\n"); 872 873 attach_num = 1; 874 ap = alist; 875 while (ap) { 876 SHOW_ALIST(alist, ap); 877 878 switch(ap->a_type) { 879 case ATTACH_MSG: 880 (void)printf("#%-7d message: <not changeable>\n", 881 attach_num); 882 break; 883 case ATTACH_FNAME: 884 case ATTACH_FILENO: 885 line = get_line(&elm.filec, "filename", ap->a_name, attach_num); 886 if (*line == '\0') { /* omit this attachment */ 887 if (ap->a_blink) { 888 struct attachment *next_ap; 889 next_ap = ap->a_flink; 890 ap = ap->a_blink; 891 ap->a_flink = next_ap; 892 if (next_ap) 893 next_ap->a_blink = ap; 894 else 895 goto done; 896 } 897 else { 898 alist = ap->a_flink; 899 if (alist) 900 alist->a_blink = NULL; 901 } 902 } 903 else { 904 char canon_name[MAXPATHLEN]; 905 if (strcmp(line, ap->a_name) != 0) { /* new filename */ 906 if (check_filename(line, canon_name) == NULL) 907 continue; 908 ap->a_name = savestr(canon_name); 909 ap->a_Content = get_mime_content(ap, 0); 910 } 911 sget_line(&elm.string, "description", 912 &ap->a_Content.C_description, attach_num); 913 sget_encoding(&ap->a_Content.C_encoding, ap->a_name, 914 ap->a_Content.C_type, attach_num); 915 } 916 break; 917 case ATTACH_INVALID: 918 default: 919 /* This is a coding error! */ 920 assert(/* CONSTCOND */ 0); 921 errx(EXIT_FAILURE, "invalid attachment type: %d", 922 ap->a_type); 923 } 924 925 attach_num++; 926 if (alist == NULL || ap->a_flink == NULL) 927 break; 928 929 ap = ap->a_flink; 930 } 931 932 ap = alist; 933 for (;;) { 934 struct attachment *nap; 935 936 SHOW_ALIST(alist, ap); 937 938 line = get_line(&elm.filec, "filename", "", attach_num); 939 if (*line == '\0') 940 break; 941 942 nap = attach_one_file(ap, line, attach_num); 943 if (nap == NULL) 944 continue; 945 946 if (alist == NULL) 947 alist = nap; 948 ap = nap; 949 950 sget_line(&elm.string, "description", 951 &ap->a_Content.C_description, attach_num); 952 sget_encoding(&ap->a_Content.C_encoding, ap->a_name, 953 ap->a_Content.C_type, attach_num); 954 attach_num++; 955 } 956 done: 957 SHOW_ALIST(alist, ap); 958 959 return alist; 960 } 961 962 /* 963 * Hook used by the '~@' escape to attach files. 964 */ 965 PUBLIC struct attachment* 966 mime_attach_files(struct attachment * volatile attach, char *linebuf) 967 { 968 struct attachment *ap; 969 char *argv[MAXARGC]; 970 int argc; 971 int attach_num; 972 973 argc = getrawlist(linebuf, argv, (int)__arraycount(argv)); 974 attach_num = 1; 975 for (ap = attach; ap && ap->a_flink; ap = ap->a_flink) 976 attach_num++; 977 978 if (argc) { 979 int i; 980 for (i = 0; i < argc; i++) { 981 struct attachment *ap2; 982 ap2 = attach_one_file(ap, argv[i], attach_num); 983 if (ap2 != NULL) { 984 ap = ap2; 985 if (attach == NULL) 986 attach = ap; 987 attach_num++; 988 } 989 } 990 } 991 else { 992 attach = edit_attachlist(attach); 993 (void)printf("--- end attachments ---\n"); 994 } 995 996 return attach; 997 } 998 999 /* 1000 * Hook called in main() to attach files registered by the '-a' flag. 1001 */ 1002 PUBLIC struct attachment * 1003 mime_attach_optargs(struct name *optargs) 1004 { 1005 struct attachment *attach; 1006 struct attachment *ap; 1007 struct name *np; 1008 char *expand_optargs; 1009 int attach_num; 1010 1011 expand_optargs = value(ENAME_MIME_ATTACH_LIST); 1012 attach_num = 1; 1013 ap = NULL; 1014 attach = NULL; 1015 for (np = optargs; np; np = np->n_flink) { 1016 char *argv[MAXARGC]; 1017 int argc; 1018 int i; 1019 1020 if (expand_optargs != NULL) 1021 argc = getrawlist(np->n_name, 1022 argv, (int)__arraycount(argv)); 1023 else { 1024 if (np->n_name == '\0') 1025 argc = 0; 1026 else { 1027 argc = 1; 1028 argv[0] = np->n_name; 1029 } 1030 argv[argc] = NULL;/* be consistent with getrawlist() */ 1031 } 1032 for (i = 0; i < argc; i++) { 1033 struct attachment *ap2; 1034 char *filename; 1035 1036 if (argv[i][0] == '/') /* an absolute path */ 1037 (void)easprintf(&filename, "%s", argv[i]); 1038 else 1039 (void)easprintf(&filename, "%s/%s", 1040 origdir, argv[i]); 1041 1042 ap2 = attach_one_file(ap, filename, attach_num); 1043 if (ap2 != NULL) { 1044 ap = ap2; 1045 if (attach == NULL) 1046 attach = ap; 1047 attach_num++; 1048 } 1049 free(filename); 1050 } 1051 } 1052 return attach; 1053 } 1054 1055 /* 1056 * Output MIME header strings as specified in the header structure. 1057 */ 1058 PUBLIC void 1059 mime_putheader(FILE *fp, struct header *header) 1060 { 1061 (void)fprintf(fp, MIME_HDR_VERSION ": " MIME_VERSION "\n"); 1062 if (header->h_attach) { 1063 (void)fprintf(fp, MIME_HDR_TYPE ": multipart/mixed;\n"); 1064 (void)fprintf(fp, "\tboundary=\"%s\"\n", header->h_mime_boundary); 1065 } 1066 else { 1067 fput_mime_content(fp, &header->h_Content); 1068 } 1069 } 1070 1071 #endif /* MIME_SUPPORT */ 1072