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