1 /* $NetBSD: mime_attach.c,v 1.2 2006/10/31 20:07: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 * 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.2 2006/10/31 20:07:32 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 72 #if 0 73 #ifndef __lint__ 74 /* 75 * XXX - This block for debugging only and eventually should go away. 76 */ 77 static void 78 show_name(const char *prefix, struct name *np) 79 { 80 int i; 81 82 i = 0; 83 for (/* EMPTY */; np; np = np->n_flink) { 84 (void)printf("%s[%d]: %s\n", prefix, i, np->n_name); 85 i++; 86 } 87 } 88 89 static void fput_mime_content(FILE *fp, struct Content *Cp); 90 91 PUBLIC void 92 show_attach(const char *prefix, struct attachment *ap) 93 { 94 int i; 95 i = 1; 96 for (/* EMPTY */; ap; ap = ap->a_flink) { 97 (void)printf("%s[%d]:\n", prefix, i); 98 fput_mime_content(stdout, &ap->a_Content); 99 i++; 100 } 101 } 102 103 PUBLIC void 104 show_header(struct header *hp) 105 { 106 show_name("TO", hp->h_to); 107 (void)printf("SUBJECT: %s\n", hp->h_subject); 108 show_name("CC", hp->h_cc); 109 show_name("BCC", hp->h_bcc); 110 show_name("SMOPTS", hp->h_smopts); 111 show_attach("ATTACH", hp->h_attach); 112 } 113 #endif /* __lint__ */ 114 #endif 115 116 117 /*************************** 118 * boundary string routines 119 */ 120 static char * 121 getrandstring(size_t length) 122 { 123 void *vbin; 124 uint32_t *bin; 125 size_t binlen; 126 size_t i; 127 char *b64; 128 129 /* XXX - check this stuff again!!! */ 130 131 binlen = 3 * roundup(length, 4) / 4; /* bytes of binary to encode base64 */ 132 bin = vbin = salloc(roundup(binlen, 4)); 133 for (i = 0; i < roundup(binlen, 4) / 4; i++) 134 bin[i] = arc4random(); 135 136 b64 = salloc(roundup(length, 4)); 137 mime_bintob64(b64, vbin, binlen); 138 b64[length] = '\0'; 139 140 return b64; 141 } 142 143 /* 144 * Generate a boundary for MIME multipart messages. 145 */ 146 static char * 147 make_boundary(void) 148 { 149 #define BOUND_LEN 70 /* maximum length is 70 characters: RFC2046 sec 5.1.1 */ 150 151 char *bound; 152 time_t now; 153 154 (void)time(&now); 155 bound = salloc(BOUND_LEN); 156 (void)snprintf(bound, BOUND_LEN, "=_%08lx.%s", 157 (long)now, getrandstring(BOUND_LEN - 12)); 158 return bound; 159 160 #undef BOUND_LEN 161 } 162 163 164 /*************************** 165 * Transfer coding routines 166 */ 167 /* 168 * We determine the recommended transfer encoding type for a file as 169 * follows: 170 * 171 * 1) If there is a NULL byte or a stray CR (not in a CRLF 172 * combination) in the file, play it safe and use base64. 173 * 174 * 2) If any high bit is set, use quoted-printable if the content type 175 * is "text" and base64 otherwise. 176 * 177 * 3) Otherwise: 178 * a) use quoted-printable if there are any long lines, control 179 * chars (including CR), end-of-line blank space, or a missing 180 * terminating NL. 181 * b) use 7bit in all remaining case, including an empty file. 182 * 183 * NOTE: This means that CRLF text (MSDOS) files will be encoded 184 * quoted-printable. 185 */ 186 /* 187 * RFC 821 imposes the following line length limit: 188 * The maximum total length of a text line including the 189 * <CRLF> is 1000 characters (but not counting the leading 190 * dot duplicated for transparency). 191 */ 192 #define MIME_UNENCODED_LINE_MAX (1000 - 2) 193 static size_t 194 line_limit(void) 195 { 196 int limit; 197 const char *cp; 198 limit = -1; 199 200 if ((cp = value(ENAME_MIME_UNENC_LINE_MAX)) != NULL) 201 limit = atoi(cp); 202 203 if (limit < 0 || limit > MIME_UNENCODED_LINE_MAX) 204 limit = MIME_UNENCODED_LINE_MAX; 205 206 return (size_t)limit; 207 } 208 209 static inline 210 int is_text(const char *ctype) 211 { 212 return ctype && 213 strncasecmp(ctype, "text/", sizeof("text/") - 1) == 0; 214 } 215 216 static const char * 217 content_encoding_core(void *fh, const char *ctype) 218 { 219 int c, lastc; 220 int ctrlchar, endwhite; 221 size_t curlen, maxlen; 222 223 curlen = 0; 224 maxlen = 0; 225 ctrlchar = 0; 226 endwhite = 0; 227 lastc = EOF; 228 while ((c = fgetc(fh)) != EOF) { 229 curlen++; 230 231 if (c == '\0' || (lastc == '\r' && c != '\n')) 232 return MIME_TRANSFER_BASE64; 233 234 if (c > 0x7f) { 235 if (is_text(ctype)) 236 return MIME_TRANSFER_QUOTED; 237 else 238 return MIME_TRANSFER_BASE64; 239 } 240 if (c == '\n') { 241 if (isblank((unsigned char)lastc)) 242 endwhite = 1; 243 if (curlen > maxlen) 244 maxlen = curlen; 245 curlen = 0; 246 } 247 else if ((c < 0x20 && c != '\t') || c == 0x7f) 248 ctrlchar = 1; 249 250 lastc = c; 251 } 252 if (lastc == EOF) /* no characters read */ 253 return MIME_TRANSFER_7BIT; 254 255 if (lastc != '\n' || ctrlchar || endwhite || maxlen > line_limit()) 256 return MIME_TRANSFER_QUOTED; 257 258 return MIME_TRANSFER_7BIT; 259 } 260 261 static const char * 262 content_encoding_by_name(const char *filename, const char *ctype) 263 { 264 FILE *fp; 265 const char *enc; 266 fp = fopen(filename, "r"); 267 if (fp == NULL) { 268 warn("content_encoding_by_name: %s", filename); 269 return MIME_TRANSFER_BASE64; /* safe */ 270 } 271 enc = content_encoding_core(fp, ctype); 272 (void)fclose(fp); 273 return enc; 274 } 275 276 static const char * 277 content_encoding_by_fileno(int fd, const char *ctype) 278 { 279 FILE *fp; 280 const char *encoding; 281 off_t cur_pos; 282 283 cur_pos = lseek(fd, (off_t)0, SEEK_CUR); 284 fp = fdopen(fd, "r"); 285 if (fp == NULL) { 286 warn("content_encoding_by_fileno"); 287 return MIME_TRANSFER_BASE64; 288 } 289 encoding = content_encoding_core(fp, ctype); 290 (void)lseek(fd, cur_pos, SEEK_SET); 291 return encoding; 292 } 293 294 static const char * 295 content_encoding(struct attachment *attach, const char *ctype) 296 { 297 switch (attach->a_type) { 298 case ATTACH_FNAME: 299 return content_encoding_by_name(attach->a_name, ctype); 300 case ATTACH_MSG: 301 errx(EXIT_FAILURE, "msgno not supported yet\n"); 302 /* NOTREACHED */ 303 case ATTACH_FILENO: 304 return content_encoding_by_fileno(attach->a_fileno, ctype); 305 default: 306 errx(EXIT_FAILURE, "invalid attach type: %d\n", attach->a_type); 307 } 308 /* NOTREACHED */ 309 } 310 311 312 313 /************************ 314 * Content type routines 315 */ 316 /* 317 * We use libmagic(3) to get the content type, except in the case of a 318 * 0 or 1 byte file, which we declare "text/plain". 319 */ 320 static const char * 321 content_type_by_name(const char *filename) 322 { 323 const char *cp; 324 magic_t magic; 325 struct stat sb; 326 327 /* 328 * libmagic produces annoying results on very short files. 329 * Note: a 1-byte file always consists of a newline, so size 330 * determines all here. 331 */ 332 if ((filename != NULL && stat(filename, &sb) == 0) || 333 (filename == NULL && fstat(0, &sb) == 0)) 334 if (sb.st_size < 2 && S_ISREG(sb.st_mode)) 335 return "text/plain"; 336 337 magic = magic_open(MAGIC_MIME); 338 if (magic == NULL) { 339 warn("magic_open: %s", magic_error(magic)); 340 return NULL; 341 } 342 if (magic_load(magic, NULL) != 0) { 343 warn("magic_load: %s", magic_error(magic)); 344 return NULL; 345 } 346 cp = magic_file(magic, filename); 347 if (cp == NULL) { 348 warn("magic_load: %s", magic_error(magic)); 349 return NULL; 350 } 351 cp = savestr(cp); 352 magic_close(magic); 353 return cp; 354 } 355 356 static const char * 357 content_type_by_fileno(int fd) 358 { 359 const char *cp; 360 off_t cur_pos; 361 int ofd; 362 363 cur_pos = lseek(fd, (off_t)0, SEEK_CUR); 364 365 ofd = dup(0); /* save stdin */ 366 if (dup2(fd, 0) == -1) /* become stdin */ 367 warn("dup2"); 368 369 cp = content_type_by_name(NULL); 370 371 if (dup2(ofd, 0) == -1) /* restore stdin */ 372 warn("dup2"); 373 (void)close(ofd); /* close the copy */ 374 375 (void)lseek(fd, cur_pos, SEEK_SET); 376 return cp; 377 } 378 379 380 static const char * 381 content_type(struct attachment *attach) 382 { 383 switch (attach->a_type) { 384 case ATTACH_FNAME: 385 return content_type_by_name(attach->a_name); 386 case ATTACH_MSG: 387 return "message/rfc822"; 388 case ATTACH_FILENO: 389 return content_type_by_fileno(attach->a_fileno); 390 default: 391 /* This is a coding error! */ 392 assert(/* CONSTCOND */ 0); 393 return NULL; 394 } 395 } 396 397 398 /************************* 399 * Other content routines 400 */ 401 402 static const char * 403 content_disposition(struct attachment *ap) 404 { 405 switch (ap->a_type) { 406 case ATTACH_FNAME: { 407 char *buf; 408 char *disp; 409 (void)easprintf(&buf, "attachment; filename=\"%s\"", basename(ap->a_name)); 410 disp = savestr(buf); 411 free(buf); 412 return disp; 413 } 414 case ATTACH_MSG: 415 case ATTACH_FILENO: 416 return "inline"; 417 418 default: 419 return NULL; 420 } 421 } 422 423 /*ARGSUSED*/ 424 static const char * 425 content_id(struct attachment *attach __unused) 426 { 427 /* XXX - to be written. */ 428 429 return NULL; 430 } 431 432 static const char * 433 content_description(struct attachment *attach, int attach_num) 434 { 435 if (attach_num) { 436 char *description; 437 char *cp; 438 (void)easprintf(&cp, "attachment %d", attach_num); 439 description = savestr(cp); 440 free(cp); 441 return description; 442 } 443 else 444 return attach->a_Content.C_description; 445 } 446 447 /******************************************* 448 * Routines to get the MIME content strings. 449 */ 450 static struct Content 451 get_mime_content(struct attachment *ap, int i) 452 { 453 struct Content Cp; 454 455 Cp.C_type = content_type(ap); 456 Cp.C_encoding = content_encoding(ap, Cp.C_type); 457 Cp.C_disposition = content_disposition(ap); 458 Cp.C_id = content_id(ap); 459 Cp.C_description = content_description(ap, i); 460 461 return Cp; 462 } 463 464 #if 0 /* XXX - dead code! */ 465 /* 466 * A hook to complete the content part of the attachment struct. This 467 * is used when a file is attached by the '-a' to complete the process 468 * after the flags have been processed. 469 */ 470 PUBLIC void 471 mime_attach_content(struct attachment *ap) 472 { 473 int i; 474 i = 1; 475 476 for (/* EMPTY */; ap; ap = ap->a_flink) 477 ap->a_Content = get_mime_content(ap, i++); 478 } 479 #endif 480 481 482 /****************** 483 * Output routines 484 */ 485 static void 486 fput_mime_content(FILE *fp, struct Content *Cp) 487 { 488 (void)fprintf(fp, MIME_HDR_TYPE ": %s\n", Cp->C_type); 489 (void)fprintf(fp, MIME_HDR_ENCODING ": %s\n", Cp->C_encoding); 490 if (Cp->C_disposition) 491 (void)fprintf(fp, MIME_HDR_DISPOSITION ": %s\n", 492 Cp->C_disposition); 493 if (Cp->C_id) 494 (void)fprintf(fp, MIME_HDR_ID ": %s\n", Cp->C_id); 495 if (Cp->C_description) 496 (void)fprintf(fp, MIME_HDR_DESCRIPTION ": %s\n", 497 Cp->C_description); 498 } 499 500 static void 501 fput_body(FILE *fi, FILE *fo, struct Content *Cp) 502 { 503 mime_codec_t enc; 504 505 enc = mime_fio_encoder(Cp->C_encoding); 506 if (enc == NULL) 507 warnx("unknown transfer encoding type: %s\n", Cp->C_encoding); 508 else 509 enc(fi, fo, 0); 510 } 511 512 513 static void 514 fput_attachment(FILE *fo, struct attachment *ap) 515 { 516 FILE *fi; 517 struct Content *Cp = &ap->a_Content; 518 519 fput_mime_content(fo, &ap->a_Content); 520 (void)putc('\n', fo); 521 522 switch (ap->a_type) { 523 case ATTACH_FNAME: 524 fi = fopen(ap->a_name, "r"); 525 if (fi == NULL) 526 err(EXIT_FAILURE, "fopen: %s", ap->a_name); 527 break; 528 529 case ATTACH_FILENO: 530 fi = fdopen(ap->a_fileno, "r"); 531 if (fi == NULL) 532 err(EXIT_FAILURE, "fdopen: %d", ap->a_fileno); 533 break; 534 535 case ATTACH_MSG: 536 default: 537 errx(EXIT_FAILURE, "unsupported attachment type"); 538 } 539 540 fput_body(fi, fo, Cp); 541 542 if (ap->a_type == ATTACH_FNAME) 543 (void)fclose(fi); 544 } 545 546 /*********************************** 547 * Higher level attachment routines. 548 */ 549 550 static int 551 mktemp_file(FILE **nfo, FILE **nfi, const char *hint) 552 { 553 char tempname[PATHSIZE]; 554 int fd, fd2; 555 (void)snprintf(tempname, sizeof(tempname), "%s/%sXXXXXXXXXX", 556 tmpdir, hint); 557 if ((fd = mkstemp(tempname)) == -1 || 558 (*nfo = Fdopen(fd, "w")) == NULL) { 559 if (fd != -1) 560 (void)close(fd); 561 warn("%s", tempname); 562 return -1; 563 } 564 (void)rm(tempname); 565 if ((fd2 = dup(fd)) == -1 || 566 (*nfi = Fdopen(fd2, "r")) == NULL) { 567 warn("%s", tempname); 568 (void)Fclose(*nfo); 569 return -1; 570 } 571 return 0; 572 } 573 574 575 /* 576 * Repackage the mail as a multipart MIME message. This should always 577 * be called whenever there are attachments, but might be called even 578 * if there are none if we want to wrap the message in a MIME package. 579 */ 580 PUBLIC FILE * 581 mime_encode(FILE *fi, struct header *header) 582 { 583 struct attachment map; /* fake structure for the message body */ 584 struct attachment *attach; 585 struct attachment *ap; 586 FILE *nfi, *nfo; 587 588 attach = header->h_attach; 589 590 /* 591 * Make new phantom temporary file with read and write file 592 * handles: nfi and nfo, resp. 593 */ 594 if (mktemp_file(&nfo, &nfi, "mail.Rs") != 0) 595 return fi; 596 597 (void)memset(&map, 0, sizeof(map)); 598 map.a_type = ATTACH_FILENO; 599 map.a_fileno = fileno(fi); 600 601 map.a_Content = get_mime_content(&map, 0); 602 603 if (attach) { 604 /* Multi-part message: 605 * Make an attachment structure for the body message 606 * and make that the first element in the attach list. 607 */ 608 if (fsize(fi)) { 609 map.a_flink = attach; 610 attach->a_blink = ↦ 611 attach = ↦ 612 } 613 614 /* Construct our MIME boundary string - used by mime_putheader() */ 615 header->h_mime_boundary = make_boundary(); 616 617 (void)fprintf(nfo, "This is a multi-part message in MIME format.\n"); 618 619 for (ap = attach; ap; ap = ap->a_flink) { 620 (void)fprintf(nfo, "\n--%s\n", header->h_mime_boundary); 621 fput_attachment(nfo, ap); 622 } 623 624 /* the final boundary with two attached dashes */ 625 (void)fprintf(nfo, "\n--%s--\n", header->h_mime_boundary); 626 } 627 else { 628 /* Single-part message (no attachments): 629 * Update header->h_Content (used by mime_putheader()). 630 * Output the body contents. 631 */ 632 char *encoding; 633 634 header->h_Content = map.a_Content; 635 636 /* check for an encoding override */ 637 if ((encoding = value(ENAME_MIME_ENCODE_MSG)) && *encoding) 638 header->h_Content.C_encoding = encoding; 639 640 fput_body(fi, nfo, &header->h_Content); 641 } 642 (void)Fclose(fi); 643 (void)Fclose(nfo); 644 rewind(nfi); 645 return(nfi); 646 } 647 648 static char* 649 check_filename(char *filename, char *canon_name) 650 { 651 int fd; 652 struct stat sb; 653 char *fname = filename; 654 /* 655 * 1) check that filename is really a file. 656 * 2) check that filename is readable. 657 * 3) allocate an attachment structure. 658 * 4) save cananonical name for filename, so cd won't screw things later. 659 * 5) add the structure to the end of the chain. 660 */ 661 /* We need to expand '~' if we got here from '~@'. The shell 662 * does this otherwise. 663 */ 664 if (fname[0] == '~' && fname[1] == '/') { 665 if (homedir && homedir[0] != '~') 666 (void)easprintf(&fname, "%s/%s", 667 homedir, fname + 2); 668 } 669 if (realpath(fname, canon_name) == NULL) { 670 warn("realpath: %s", filename); 671 canon_name = NULL; 672 goto done; 673 } 674 fd = open(canon_name, O_RDONLY, 0); 675 if (fd == -1) { 676 warnx("open: cannot read %s", filename); 677 canon_name = NULL; 678 goto done; 679 } 680 if (fstat(fd, &sb) == -1) { 681 warn("stat: %s", canon_name); 682 canon_name = NULL; 683 goto do_close; 684 } 685 if (!S_ISREG(sb.st_mode)) { 686 warnx("stat: %s is not a file", filename); 687 canon_name = NULL; 688 /* goto do_close; */ 689 } 690 do_close: 691 (void)close(fd); 692 done: 693 if (fname != filename) 694 free(fname); 695 696 return canon_name; 697 } 698 699 static struct attachment * 700 attach_one_file(struct attachment *attach, char *filename, int attach_num) 701 { 702 char canon_name[MAXPATHLEN]; 703 struct attachment *ap, *nap; 704 705 if (check_filename(filename, canon_name) == NULL) 706 return NULL; 707 708 nap = csalloc(1, sizeof(*nap)); 709 nap->a_type = ATTACH_FNAME; 710 nap->a_name = savestr(canon_name); 711 712 if (attach == NULL) 713 attach = nap; 714 else { 715 for (ap = attach; ap->a_flink != NULL; ap = ap->a_flink) 716 continue; 717 ap->a_flink = nap; 718 nap->a_blink = ap; 719 } 720 721 if (attach_num) 722 nap->a_Content = get_mime_content(nap, attach_num); 723 724 return attach; 725 } 726 727 728 static jmp_buf intjmp; 729 /*ARGSUSED*/ 730 static void 731 sigint(int signum __unused) 732 { 733 siglongjmp(intjmp, 1); 734 } 735 736 static char * 737 get_line(el_mode_t *em, const char *pr, const char *str, int i) 738 { 739 sig_t saveint; 740 char *cp; 741 char *line; 742 char *prompt; 743 744 saveint = signal(SIGINT, sigint); 745 if (sigsetjmp(intjmp, 1)) { 746 (void)signal(SIGINT, saveint); 747 (void)putc('\n', stdout); 748 return __UNCONST(""); 749 } 750 751 /* Don't use a '\t' in the format string here as completion 752 * seems to handle it badly. */ 753 (void)easprintf(&prompt, "#%-8d%s: ", i, pr); 754 line = my_gets(em, prompt, __UNCONST(str)); 755 /* LINTED */ 756 line = line ? savestr(line) : __UNCONST(""); 757 free(prompt); 758 759 (void)signal(SIGINT, saveint); 760 761 /* strip trailing white space */ 762 for (cp = line + strlen(line) - 1; 763 cp >= line && isblank((unsigned char)*cp); cp--) 764 *cp = '\0'; 765 766 /* skip leading white space */ 767 cp = skip_white(line); 768 769 return cp; 770 } 771 772 static void 773 sget_line(el_mode_t *em, const char *pr, const char **str, int i) 774 { 775 char *line; 776 line = get_line(em, pr, *str, i); 777 if (strcmp(line, *str) != 0) 778 *str = savestr(line); 779 } 780 781 static void 782 sget_encoding(const char **str, const char *filename, const char *ctype, int num) 783 { 784 const char *ename; 785 const char *defename; 786 787 defename = NULL; 788 ename = *str; 789 for (;;) { 790 ename = get_line(&elm.mime_enc, "encoding", ename, num); 791 792 if (*ename == '\0') { 793 if (defename == NULL) 794 defename = content_encoding_by_name(filename, ctype); 795 ename = defename; 796 } 797 else if (mime_fio_encoder(ename) == NULL) { 798 const void *cookie; 799 (void)printf("Sorry: valid encoding modes are: "); 800 cookie = NULL; 801 ename = mime_next_encoding_name(&cookie); 802 for (;;) { 803 (void)printf("%s", ename); 804 ename = mime_next_encoding_name(&cookie); 805 if (ename == NULL) 806 break; 807 (void)fputc(',', stdout); 808 } 809 ename = *str; 810 } 811 else { 812 if (strcmp(ename, *str) != 0) 813 *str = savestr(ename); 814 break; 815 } 816 } 817 } 818 819 static struct attachment * 820 edit_attachments(struct attachment *attach) 821 { 822 char canon_name[MAXPATHLEN]; 823 struct attachment *ap; 824 char *line; 825 int attach_num; 826 827 (void)printf("Attachments:\n"); 828 829 attach_num = 1; 830 ap = attach; 831 while (ap) { 832 line = get_line(&elm.filec, "filename", ap->a_name, attach_num); 833 if (*line == '\0') { /* omit this attachment */ 834 if (ap->a_blink) 835 ap->a_blink->a_flink = ap->a_flink; 836 else 837 attach = ap->a_flink; 838 } 839 else { 840 if (strcmp(line, ap->a_name) != 0) { /* new filename */ 841 if (check_filename(line, canon_name) == NULL) 842 continue; 843 ap->a_name = savestr(canon_name); 844 ap->a_Content = get_mime_content(ap, 0); 845 } 846 sget_line(&elm.string, "description", 847 &ap->a_Content.C_description, attach_num); 848 sget_encoding(&ap->a_Content.C_encoding, ap->a_name, 849 ap->a_Content.C_type, attach_num); 850 } 851 attach_num++; 852 if (ap->a_flink == NULL) 853 break; 854 855 ap = ap->a_flink; 856 } 857 858 do { 859 struct attachment *nap; 860 861 line = get_line(&elm.filec, "filename", "", attach_num); 862 if (*line == '\0') 863 break; 864 865 nap = attach_one_file(ap, line, attach_num); 866 if (nap == NULL) 867 continue; 868 869 if (ap) 870 ap = ap->a_flink; 871 else 872 ap = attach = nap; 873 874 sget_line(&elm.string, "description", 875 &ap->a_Content.C_description, attach_num); 876 sget_encoding(&ap->a_Content.C_encoding, ap->a_name, 877 ap->a_Content.C_type, attach_num); 878 attach_num++; 879 880 } while (ap); 881 882 return attach; 883 } 884 885 /* 886 * Hook used by the '~@' escape to attach files. 887 */ 888 PUBLIC struct attachment* 889 mime_attach_files(struct attachment *attach, char *linebuf) 890 { 891 struct attachment *ap; 892 char *argv[MAXARGC]; 893 int argc; 894 int attach_num; 895 896 argc = getrawlist(linebuf, argv, sizeofarray(argv)); 897 attach_num = 1; 898 for (ap = attach; ap && ap->a_flink; ap = ap->a_flink) 899 attach_num++; 900 901 if (argc) { 902 int i; 903 for (i = 0; i < argc; i++) { 904 struct attachment *ap2; 905 ap2 = attach_one_file(ap, argv[i], attach_num); 906 if (ap2 != NULL) { 907 ap = ap2; 908 if (attach == NULL) 909 attach = ap; 910 attach_num++; 911 } 912 } 913 } 914 else { 915 attach = edit_attachments(attach); 916 (void)printf("--- end attachments ---\n"); 917 } 918 919 return attach; 920 } 921 922 /* 923 * Hook called in main() to attach files registered by the '-a' flag. 924 */ 925 PUBLIC struct attachment * 926 mime_attach_optargs(struct name *optargs) 927 { 928 struct attachment *attach; 929 struct attachment *ap; 930 struct name *np; 931 char *expand_optargs; 932 int attach_num; 933 934 expand_optargs = value(ENAME_MIME_ATTACH_LIST); 935 attach_num = 1; 936 ap = NULL; 937 attach = NULL; 938 for (np = optargs; np; np = np->n_flink) { 939 char *argv[MAXARGC]; 940 int argc; 941 int i; 942 943 if (expand_optargs != NULL) 944 argc = getrawlist(np->n_name, argv, sizeofarray(argv)); 945 else { 946 if (np->n_name == '\0') 947 argc = 0; 948 else { 949 argc = 1; 950 argv[0] = np->n_name; 951 } 952 argv[argc] = NULL;/* be consistent with getrawlist() */ 953 } 954 for (i = 0; i < argc; i++) { 955 struct attachment *ap2; 956 char *filename; 957 958 if (argv[i][0] == '/') /* an absolute path */ 959 (void)easprintf(&filename, "%s", argv[i]); 960 else 961 (void)easprintf(&filename, "%s/%s", 962 origdir, argv[i]); 963 964 ap2 = attach_one_file(ap, filename, attach_num); 965 free(filename); 966 if (ap2 != NULL) { 967 ap = ap2; 968 if (attach == NULL) 969 attach = ap; 970 attach_num++; 971 } 972 } 973 } 974 return attach; 975 } 976 977 /* 978 * Output MIME header strings as specified in the header structure. 979 */ 980 PUBLIC void 981 mime_putheader(FILE *fp, struct header *header) 982 { 983 (void)fprintf(fp, MIME_HDR_VERSION ": " MIME_VERSION "\n"); 984 if (header->h_attach) { 985 (void)fprintf(fp, MIME_HDR_TYPE ": multipart/mixed;\n"); 986 (void)fprintf(fp, "\tboundary=\"%s\"\n", header->h_mime_boundary); 987 } 988 else { 989 fput_mime_content(fp, &header->h_Content); 990 } 991 } 992 993 #endif /* MIME_SUPPORT */ 994