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