1/* sendmail-like interface to /bin/mail for system V, 2 Copyright (C) 1985, 1994, 1999, 2001, 2002, 2003, 2004, 3 2005, 2006, 2007 Free Software Foundation, Inc. 4 5This file is part of GNU Emacs. 6 7GNU Emacs is free software; you can redistribute it and/or modify 8it under the terms of the GNU General Public License as published by 9the Free Software Foundation; either version 2, or (at your option) 10any later version. 11 12GNU Emacs is distributed in the hope that it will be useful, 13but WITHOUT ANY WARRANTY; without even the implied warranty of 14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15GNU General Public License for more details. 16 17You should have received a copy of the GNU General Public License 18along with GNU Emacs; see the file COPYING. If not, write to 19the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 20Boston, MA 02110-1301, USA. */ 21 22#define NO_SHORTNAMES 23#define _XOPEN_SOURCE 500 /* for cuserid */ 24 25#ifdef HAVE_CONFIG_H 26#include <config.h> 27#endif 28 29#if defined (BSD_SYSTEM) && !defined (BSD4_1) && !defined (USE_FAKEMAIL) 30/* This program isnot used in BSD, so just avoid loader complaints. */ 31int 32main () 33{ 34 return 0; 35} 36#else /* not BSD 4.2 (or newer) */ 37#ifdef MSDOS 38int 39main () 40{ 41 return 0; 42} 43#else /* not MSDOS */ 44/* This conditional contains all the rest of the file. */ 45 46/* These are defined in config in some versions. */ 47 48#ifdef static 49#undef static 50#endif 51 52#ifdef WINDOWSNT 53#include "ntlib.h" 54#endif 55 56#include <stdio.h> 57#include <string.h> 58#include <ctype.h> 59#include <time.h> 60#include <pwd.h> 61 62/* This is to declare cuserid. */ 63#ifdef HAVE_UNISTD_H 64#include <unistd.h> 65#endif 66 67/* Type definitions */ 68 69#define boolean int 70#define true 1 71#define false 0 72 73#define TM_YEAR_BASE 1900 74 75/* Nonzero if TM_YEAR is a struct tm's tm_year value that causes 76 asctime to have well-defined behavior. */ 77#ifndef TM_YEAR_IN_ASCTIME_RANGE 78# define TM_YEAR_IN_ASCTIME_RANGE(tm_year) \ 79 (1000 - TM_YEAR_BASE <= (tm_year) && (tm_year) <= 9999 - TM_YEAR_BASE) 80#endif 81 82/* Various lists */ 83 84struct line_record 85{ 86 char *string; 87 struct line_record *continuation; 88}; 89typedef struct line_record *line_list; 90 91struct header_record 92{ 93 line_list text; 94 struct header_record *next; 95 struct header_record *previous; 96}; 97typedef struct header_record *header; 98 99struct stream_record 100{ 101 FILE *handle; 102 int (*action)(); 103 struct stream_record *rest_streams; 104}; 105typedef struct stream_record *stream_list; 106 107/* A `struct linebuffer' is a structure which holds a line of text. 108 * `readline' reads a line from a stream into a linebuffer 109 * and works regardless of the length of the line. 110 */ 111 112struct linebuffer 113{ 114 long size; 115 char *buffer; 116}; 117 118struct linebuffer lb; 119 120#define new_list() \ 121 ((line_list) xmalloc (sizeof (struct line_record))) 122#define new_header() \ 123 ((header) xmalloc (sizeof (struct header_record))) 124#define new_stream() \ 125 ((stream_list) xmalloc (sizeof (struct stream_record))) 126#define alloc_string(nchars) \ 127 ((char *) xmalloc ((nchars) + 1)) 128 129/* Global declarations */ 130 131#define BUFLEN 1024 132#define KEYWORD_SIZE 256 133#define FROM_PREFIX "From" 134#define MY_NAME "fakemail" 135#define NIL ((line_list) NULL) 136#define INITIAL_LINE_SIZE 200 137 138#ifndef MAIL_PROGRAM_NAME 139#define MAIL_PROGRAM_NAME "/bin/mail" 140#endif 141 142static char *my_name; 143static char *the_date; 144static char *the_user; 145static line_list file_preface; 146static stream_list the_streams; 147static boolean no_problems = true; 148 149extern FILE *popen (); 150extern int fclose (), pclose (); 151 152#ifdef CURRENT_USER 153extern struct passwd *getpwuid (); 154extern unsigned short geteuid (); 155static struct passwd *my_entry; 156#define cuserid(s) \ 157(my_entry = getpwuid (((int) geteuid ())), \ 158 my_entry->pw_name) 159#endif 160 161/* Utilities */ 162 163/* Print error message. `s1' is printf control string, `s2' is arg for it. */ 164 165static void 166error (s1, s2) 167 char *s1, *s2; 168{ 169 printf ("%s: ", my_name); 170 printf (s1, s2); 171 printf ("\n"); 172 no_problems = false; 173} 174 175/* Print error message and exit. */ 176 177static void 178fatal (s1) 179 char *s1; 180{ 181 error ("%s", s1); 182 exit (EXIT_FAILURE); 183} 184 185/* Like malloc but get fatal error if memory is exhausted. */ 186 187static long * 188xmalloc (size) 189 int size; 190{ 191 long *result = (long *) malloc (((unsigned) size)); 192 if (result == ((long *) NULL)) 193 fatal ("virtual memory exhausted"); 194 return result; 195} 196 197static long * 198xrealloc (ptr, size) 199 long *ptr; 200 int size; 201{ 202 long *result = (long *) realloc (ptr, ((unsigned) size)); 203 if (result == ((long *) NULL)) 204 fatal ("virtual memory exhausted"); 205 return result; 206} 207 208/* Initialize a linebuffer for use */ 209 210void 211init_linebuffer (linebuffer) 212 struct linebuffer *linebuffer; 213{ 214 linebuffer->size = INITIAL_LINE_SIZE; 215 linebuffer->buffer = ((char *) xmalloc (INITIAL_LINE_SIZE)); 216} 217 218/* Read a line of text from `stream' into `linebuffer'. 219 Return the length of the line. */ 220 221long 222readline (linebuffer, stream) 223 struct linebuffer *linebuffer; 224 FILE *stream; 225{ 226 char *buffer = linebuffer->buffer; 227 char *p = linebuffer->buffer; 228 char *end = p + linebuffer->size; 229 230 while (true) 231 { 232 int c = getc (stream); 233 if (p == end) 234 { 235 linebuffer->size *= 2; 236 buffer = ((char *) xrealloc ((long *)buffer, linebuffer->size)); 237 p = buffer + (p - linebuffer->buffer); 238 end = buffer + linebuffer->size; 239 linebuffer->buffer = buffer; 240 } 241 if (c < 0 || c == '\n') 242 { 243 *p = 0; 244 break; 245 } 246 *p++ = c; 247 } 248 249 return p - buffer; 250} 251 252/* Extract a colon-terminated keyword from the string FIELD. 253 Return that keyword as a string stored in a static buffer. 254 Store the address of the rest of the string into *REST. 255 256 If there is no keyword, return NULL and don't alter *REST. */ 257 258char * 259get_keyword (field, rest) 260 register char *field; 261 char **rest; 262{ 263 static char keyword[KEYWORD_SIZE]; 264 register char *ptr; 265 register int c; 266 267 ptr = &keyword[0]; 268 c = (unsigned char) *field++; 269 if (isspace (c) || c == ':') 270 return ((char *) NULL); 271 *ptr++ = (islower (c) ? toupper (c) : c); 272 while (((c = (unsigned char) *field++) != ':') && ! isspace (c)) 273 *ptr++ = (islower (c) ? toupper (c) : c); 274 *ptr++ = '\0'; 275 while (isspace (c)) 276 c = (unsigned char) *field++; 277 if (c != ':') 278 return ((char *) NULL); 279 *rest = field; 280 return &keyword[0]; 281} 282 283/* Nonzero if the string FIELD starts with a colon-terminated keyword. */ 284 285boolean 286has_keyword (field) 287 char *field; 288{ 289 char *ignored; 290 return (get_keyword (field, &ignored) != ((char *) NULL)); 291} 292 293/* Store the string FIELD, followed by any lines in THE_LIST, 294 into the buffer WHERE. 295 Concatenate lines, putting just a space between them. 296 Delete everything contained in parentheses. 297 When a recipient name contains <...>, we discard 298 everything except what is inside the <...>. 299 300 We don't pay attention to overflowing WHERE; 301 the caller has to make it big enough. */ 302 303char * 304add_field (the_list, field, where) 305 line_list the_list; 306 register char *field, *where; 307{ 308 register char c; 309 while (true) 310 { 311 char *this_recipient_where; 312 int in_quotes = 0; 313 314 *where++ = ' '; 315 this_recipient_where = where; 316 317 while ((c = *field++) != '\0') 318 { 319 if (c == '\\') 320 *where++ = c; 321 else if (c == '"') 322 { 323 in_quotes = ! in_quotes; 324 *where++ = c; 325 } 326 else if (in_quotes) 327 *where++ = c; 328 else if (c == '(') 329 { 330 while (*field && *field != ')') ++field; 331 if (! (*field++)) break; /* no close */ 332 continue; 333 } 334 else if (c == ',') 335 { 336 *where++ = ' '; 337 /* When we get to the end of one recipient, 338 don't discard it if the next one has <...>. */ 339 this_recipient_where = where; 340 } 341 else if (c == '<') 342 /* Discard everything we got before the `<'. */ 343 where = this_recipient_where; 344 else if (c == '>') 345 /* Discard the rest of this name that follows the `>'. */ 346 { 347 while (*field && *field != ',') ++field; 348 if (! (*field++)) break; /* no comma */ 349 continue; 350 } 351 else 352 *where++ = c; 353 } 354 if (the_list == NIL) break; 355 field = the_list->string; 356 the_list = the_list->continuation; 357 } 358 return where; 359} 360 361line_list 362make_file_preface () 363{ 364 char *the_string, *temp; 365 long idiotic_interface; 366 struct tm *tm; 367 long prefix_length; 368 long user_length; 369 long date_length; 370 line_list result; 371 372 prefix_length = strlen (FROM_PREFIX); 373 time (&idiotic_interface); 374 /* Convert to a string, checking for out-of-range time stamps. 375 Don't use 'ctime', as that might dump core if the hardware clock 376 is set to a bizarre value. */ 377 tm = localtime (&idiotic_interface); 378 if (! (tm && TM_YEAR_IN_ASCTIME_RANGE (tm->tm_year) 379 && (the_date = asctime (tm)))) 380 fatal ("current time is out of range"); 381 /* the_date has an unwanted newline at the end */ 382 date_length = strlen (the_date) - 1; 383 the_date[date_length] = '\0'; 384 temp = cuserid ((char *) NULL); 385 user_length = strlen (temp); 386 the_user = alloc_string (user_length + 1); 387 strcpy (the_user, temp); 388 the_string = alloc_string (3 + prefix_length 389 + user_length 390 + date_length); 391 temp = the_string; 392 strcpy (temp, FROM_PREFIX); 393 temp = &temp[prefix_length]; 394 *temp++ = ' '; 395 strcpy (temp, the_user); 396 temp = &temp[user_length]; 397 *temp++ = ' '; 398 strcpy (temp, the_date); 399 result = new_list (); 400 result->string = the_string; 401 result->continuation = ((line_list) NULL); 402 return result; 403} 404 405void 406write_line_list (the_list, the_stream) 407 register line_list the_list; 408 FILE *the_stream; 409{ 410 for ( ; 411 the_list != ((line_list) NULL) ; 412 the_list = the_list->continuation) 413 { 414 fputs (the_list->string, the_stream); 415 putc ('\n', the_stream); 416 } 417 return; 418} 419 420int 421close_the_streams () 422{ 423 register stream_list rem; 424 for (rem = the_streams; 425 rem != ((stream_list) NULL); 426 rem = rem->rest_streams) 427 no_problems = (no_problems && 428 ((*rem->action) (rem->handle) == 0)); 429 the_streams = ((stream_list) NULL); 430 return (no_problems ? EXIT_SUCCESS : EXIT_FAILURE); 431} 432 433void 434add_a_stream (the_stream, closing_action) 435 FILE *the_stream; 436 int (*closing_action)(); 437{ 438 stream_list old = the_streams; 439 the_streams = new_stream (); 440 the_streams->handle = the_stream; 441 the_streams->action = closing_action; 442 the_streams->rest_streams = old; 443 return; 444} 445 446int 447my_fclose (the_file) 448 FILE *the_file; 449{ 450 putc ('\n', the_file); 451 fflush (the_file); 452 return fclose (the_file); 453} 454 455boolean 456open_a_file (name) 457 char *name; 458{ 459 FILE *the_stream = fopen (name, "a"); 460 if (the_stream != ((FILE *) NULL)) 461 { 462 add_a_stream (the_stream, my_fclose); 463 if (the_user == ((char *) NULL)) 464 file_preface = make_file_preface (); 465 write_line_list (file_preface, the_stream); 466 return true; 467 } 468 return false; 469} 470 471void 472put_string (s) 473 char *s; 474{ 475 register stream_list rem; 476 for (rem = the_streams; 477 rem != ((stream_list) NULL); 478 rem = rem->rest_streams) 479 fputs (s, rem->handle); 480 return; 481} 482 483void 484put_line (string) 485 char *string; 486{ 487 register stream_list rem; 488 for (rem = the_streams; 489 rem != ((stream_list) NULL); 490 rem = rem->rest_streams) 491 { 492 char *s = string; 493 int column = 0; 494 495 /* Divide STRING into lines. */ 496 while (*s != 0) 497 { 498 char *breakpos; 499 500 /* Find the last char that fits. */ 501 for (breakpos = s; *breakpos && column < 78; ++breakpos) 502 { 503 if (*breakpos == '\t') 504 column += 8; 505 else 506 column++; 507 } 508 /* If we didn't reach end of line, break the line. */ 509 if (*breakpos) 510 { 511 /* Back up to just after the last comma that fits. */ 512 while (breakpos != s && breakpos[-1] != ',') --breakpos; 513 514 if (breakpos == s) 515 { 516 /* If no comma fits, move past the first address anyway. */ 517 while (*breakpos != 0 && *breakpos != ',') ++breakpos; 518 if (*breakpos != 0) 519 /* Include the comma after it. */ 520 ++breakpos; 521 } 522 } 523 /* Output that much, then break the line. */ 524 fwrite (s, 1, breakpos - s, rem->handle); 525 column = 8; 526 527 /* Skip whitespace and prepare to print more addresses. */ 528 s = breakpos; 529 while (*s == ' ' || *s == '\t') ++s; 530 if (*s != 0) 531 fputs ("\n\t", rem->handle); 532 } 533 putc ('\n', rem->handle); 534 } 535 return; 536} 537 538#define mail_error error 539 540/* Handle an FCC field. FIELD is the text of the first line (after 541 the header name), and THE_LIST holds the continuation lines if any. 542 Call open_a_file for each file. */ 543 544void 545setup_files (the_list, field) 546 register line_list the_list; 547 register char *field; 548{ 549 register char *start; 550 register char c; 551 while (true) 552 { 553 while (((c = *field) != '\0') 554 && (c == ' ' 555 || c == '\t' 556 || c == ',')) 557 field += 1; 558 if (c != '\0') 559 { 560 start = field; 561 while (((c = *field) != '\0') 562 && c != ' ' 563 && c != '\t' 564 && c != ',') 565 field += 1; 566 *field = '\0'; 567 if (!open_a_file (start)) 568 mail_error ("Could not open file %s", start); 569 *field = c; 570 if (c != '\0') continue; 571 } 572 if (the_list == ((line_list) NULL)) 573 return; 574 field = the_list->string; 575 the_list = the_list->continuation; 576 } 577} 578 579/* Compute the total size of all recipient names stored in THE_HEADER. 580 The result says how big to make the buffer to pass to parse_header. */ 581 582int 583args_size (the_header) 584 header the_header; 585{ 586 register header old = the_header; 587 register line_list rem; 588 register int size = 0; 589 do 590 { 591 char *field; 592 register char *keyword = get_keyword (the_header->text->string, &field); 593 if ((strcmp (keyword, "TO") == 0) 594 || (strcmp (keyword, "CC") == 0) 595 || (strcmp (keyword, "BCC") == 0)) 596 { 597 size += 1 + strlen (field); 598 for (rem = the_header->text->continuation; 599 rem != NIL; 600 rem = rem->continuation) 601 size += 1 + strlen (rem->string); 602 } 603 the_header = the_header->next; 604 } while (the_header != old); 605 return size; 606} 607 608/* Scan the header described by the lists THE_HEADER, 609 and put all recipient names into the buffer WHERE. 610 Precede each recipient name with a space. 611 612 Also, if the header has any FCC fields, call setup_files for each one. */ 613 614void 615parse_header (the_header, where) 616 header the_header; 617 register char *where; 618{ 619 register header old = the_header; 620 do 621 { 622 char *field; 623 register char *keyword = get_keyword (the_header->text->string, &field); 624 if (strcmp (keyword, "TO") == 0) 625 where = add_field (the_header->text->continuation, field, where); 626 else if (strcmp (keyword, "CC") == 0) 627 where = add_field (the_header->text->continuation, field, where); 628 else if (strcmp (keyword, "BCC") == 0) 629 { 630 where = add_field (the_header->text->continuation, field, where); 631 the_header->previous->next = the_header->next; 632 the_header->next->previous = the_header->previous; 633 } 634 else if (strcmp (keyword, "FCC") == 0) 635 setup_files (the_header->text->continuation, field); 636 the_header = the_header->next; 637 } while (the_header != old); 638 *where = '\0'; 639 return; 640} 641 642/* Read lines from the input until we get a blank line. 643 Create a list of `header' objects, one for each header field, 644 each of which points to a list of `line_list' objects, 645 one for each line in that field. 646 Continuation lines are grouped in the headers they continue. */ 647 648header 649read_header () 650{ 651 register header the_header = ((header) NULL); 652 register line_list *next_line = ((line_list *) NULL); 653 654 init_linebuffer (&lb); 655 656 do 657 { 658 long length; 659 register char *line; 660 661 readline (&lb, stdin); 662 line = lb.buffer; 663 length = strlen (line); 664 if (length == 0) break; 665 666 if (has_keyword (line)) 667 { 668 register header old = the_header; 669 the_header = new_header (); 670 if (old == ((header) NULL)) 671 { 672 the_header->next = the_header; 673 the_header->previous = the_header; 674 } 675 else 676 { 677 the_header->previous = old; 678 the_header->next = old->next; 679 old->next = the_header; 680 } 681 next_line = &(the_header->text); 682 } 683 684 if (next_line == ((line_list *) NULL)) 685 { 686 /* Not a valid header */ 687 exit (EXIT_FAILURE); 688 } 689 *next_line = new_list (); 690 (*next_line)->string = alloc_string (length); 691 strcpy (((*next_line)->string), line); 692 next_line = &((*next_line)->continuation); 693 *next_line = NIL; 694 695 } while (true); 696 697 if (! the_header) 698 fatal ("input message has no header"); 699 return the_header->next; 700} 701 702void 703write_header (the_header) 704 header the_header; 705{ 706 register header old = the_header; 707 do 708 { 709 register line_list the_list; 710 for (the_list = the_header->text; 711 the_list != NIL; 712 the_list = the_list->continuation) 713 put_line (the_list->string); 714 the_header = the_header->next; 715 } while (the_header != old); 716 put_line (""); 717 return; 718} 719 720int 721main (argc, argv) 722 int argc; 723 char **argv; 724{ 725 char *command_line; 726 header the_header; 727 long name_length; 728 char *mail_program_name; 729 char buf[BUFLEN + 1]; 730 register int size; 731 FILE *the_pipe; 732 733 extern char *getenv (); 734 735 mail_program_name = getenv ("FAKEMAILER"); 736 if (!(mail_program_name && *mail_program_name)) 737 mail_program_name = MAIL_PROGRAM_NAME; 738 name_length = strlen (mail_program_name); 739 740 my_name = MY_NAME; 741 the_streams = ((stream_list) NULL); 742 the_date = ((char *) NULL); 743 the_user = ((char *) NULL); 744 745 the_header = read_header (); 746 command_line = alloc_string (name_length + args_size (the_header)); 747 strcpy (command_line, mail_program_name); 748 parse_header (the_header, &command_line[name_length]); 749 750 the_pipe = popen (command_line, "w"); 751 if (the_pipe == ((FILE *) NULL)) 752 fatal ("cannot open pipe to real mailer"); 753 754 add_a_stream (the_pipe, pclose); 755 756 write_header (the_header); 757 758 /* Dump the message itself */ 759 760 while (!feof (stdin)) 761 { 762 size = fread (buf, 1, BUFLEN, stdin); 763 buf[size] = '\0'; 764 put_string (buf); 765 } 766 767 exit (close_the_streams ()); 768} 769 770#endif /* not MSDOS */ 771#endif /* not BSD 4.2 (or newer) */ 772 773/* arch-tag: acb0afa6-315a-4c5b-b9e3-def5725c8783 774 (do not change this comment) */ 775 776/* fakemail.c ends here */ 777