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