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