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