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