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