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