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