(delete-selection-mode): Add custom-loads property.
[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 24
e397a017 25#if defined (BSD_SYSTEM) && !defined (BSD4_1) && !defined (USE_FAKEMAIL)
6b3ee98a 26/* This program isnot used in BSD, so just avoid loader complaints. */
d0dff6e5 27int
6b3ee98a
RS
28main ()
29{
0d1841a6 30 return 0;
6b3ee98a
RS
31}
32#else /* not BSD 4.2 (or newer) */
26528bbc 33#ifdef MSDOS
d0dff6e5 34int
26528bbc
RS
35main ()
36{
0d1841a6 37 return 0;
26528bbc
RS
38}
39#else /* not MSDOS */
6b3ee98a
RS
40/* This conditional contains all the rest of the file. */
41
42/* These are defined in config in some versions. */
43
44#ifdef static
45#undef static
46#endif
47
48#ifdef read
49#undef read
50#undef write
51#undef open
52#undef close
53#endif
54
7c544420
RS
55#ifdef WINDOWSNT
56#include "ntlib.h"
57#endif
58
6b3ee98a
RS
59#include <stdio.h>
60#include <string.h>
61#include <ctype.h>
62#include <time.h>
63#include <pwd.h>
64\f
65/* Type definitions */
66
67#define boolean int
68#define true 1
69#define false 0
70
71/* Various lists */
72
73struct line_record
74{
75 char *string;
76 struct line_record *continuation;
77};
78typedef struct line_record *line_list;
79
80struct header_record
81{
82 line_list text;
83 struct header_record *next;
84 struct header_record *previous;
85};
86typedef struct header_record *header;
87
88struct stream_record
89{
90 FILE *handle;
91 int (*action)();
92 struct stream_record *rest_streams;
93};
94typedef struct stream_record *stream_list;
95
96/* A `struct linebuffer' is a structure which holds a line of text.
97 * `readline' reads a line from a stream into a linebuffer
98 * and works regardless of the length of the line.
99 */
100
101struct linebuffer
102{
103 long size;
104 char *buffer;
105};
106
107struct linebuffer lb;
108
109#define new_list() \
110 ((line_list) xmalloc (sizeof (struct line_record)))
111#define new_header() \
112 ((header) xmalloc (sizeof (struct header_record)))
113#define new_stream() \
114 ((stream_list) xmalloc (sizeof (struct stream_record)))
115#define alloc_string(nchars) \
116 ((char *) xmalloc ((nchars) + 1))
117\f
118/* Global declarations */
119
120#define BUFLEN 1024
121#define KEYWORD_SIZE 256
122#define FROM_PREFIX "From"
123#define MY_NAME "fakemail"
124#define NIL ((line_list) NULL)
125#define INITIAL_LINE_SIZE 200
126
127#ifndef MAIL_PROGRAM_NAME
128#define MAIL_PROGRAM_NAME "/bin/mail"
129#endif
130
131static char *my_name;
132static char *the_date;
133static char *the_user;
134static line_list file_preface;
135static stream_list the_streams;
136static boolean no_problems = true;
137
138extern FILE *popen ();
139extern int fclose (), pclose ();
6b3ee98a
RS
140
141#ifdef CURRENT_USER
142extern struct passwd *getpwuid ();
143extern unsigned short geteuid ();
144static struct passwd *my_entry;
145#define cuserid(s) \
146(my_entry = getpwuid (((int) geteuid ())), \
147 my_entry->pw_name)
148#endif
149\f
150/* Utilities */
151
152/* Print error message. `s1' is printf control string, `s2' is arg for it. */
153
154static void
155error (s1, s2)
156 char *s1, *s2;
157{
158 printf ("%s: ", my_name);
159 printf (s1, s2);
160 printf ("\n");
161 no_problems = false;
162}
163
164/* Print error message and exit. */
165
166static void
167fatal (s1, s2)
168 char *s1, *s2;
169{
170 error (s1, s2);
171 exit (1);
172}
173
174/* Like malloc but get fatal error if memory is exhausted. */
175
65119039 176static long *
6b3ee98a
RS
177xmalloc (size)
178 int size;
179{
65119039
RS
180 long *result = (long *) malloc (((unsigned) size));
181 if (result == ((long *) NULL))
6b3ee98a
RS
182 fatal ("virtual memory exhausted", 0);
183 return result;
184}
185
65119039 186static long *
6b3ee98a 187xrealloc (ptr, size)
65119039 188 long *ptr;
6b3ee98a
RS
189 int size;
190{
65119039 191 long *result = (long *) realloc (ptr, ((unsigned) size));
5391a863 192 if (result == ((long *) NULL))
6b3ee98a
RS
193 fatal ("virtual memory exhausted");
194 return result;
195}
196\f
197/* Initialize a linebuffer for use */
198
199void
200init_linebuffer (linebuffer)
201 struct linebuffer *linebuffer;
202{
203 linebuffer->size = INITIAL_LINE_SIZE;
204 linebuffer->buffer = ((char *) xmalloc (INITIAL_LINE_SIZE));
205}
206
207/* Read a line of text from `stream' into `linebuffer'.
208 * Return the length of the line.
209 */
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;
226 buffer = ((char *) xrealloc (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;
255 register char c;
256
257 ptr = &keyword[0];
258 c = *field++;
3d23b985 259 if (isspace (c) || c == ':')
6b3ee98a 260 return ((char *) NULL);
3d23b985
RS
261 *ptr++ = (islower (c) ? toupper (c) : c);
262 while (((c = *field++) != ':') && ! isspace (c))
263 *ptr++ = (islower (c) ? toupper (c) : c);
6b3ee98a 264 *ptr++ = '\0';
3d23b985
RS
265 while (isspace (c))
266 c = *field++;
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);
371 the_string = alloc_string (3 + prefix_length +
372 user_length +
373 date_length);
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
6b3ee98a
RS
597parse_header (the_header, where)
598 header the_header;
599 register char *where;
600{
601 register header old = the_header;
602 do
603 {
604 char *field;
605 register char *keyword = get_keyword (the_header->text->string, &field);
606 if (strcmp (keyword, "TO") == 0)
607 where = add_field (the_header->text->continuation, field, where);
608 else if (strcmp (keyword, "CC") == 0)
609 where = add_field (the_header->text->continuation, field, where);
610 else if (strcmp (keyword, "BCC") == 0)
611 {
612 where = add_field (the_header->text->continuation, field, where);
613 the_header->previous->next = the_header->next;
614 the_header->next->previous = the_header->previous;
615 }
616 else if (strcmp (keyword, "FCC") == 0)
617 setup_files (the_header->text->continuation, field);
618 the_header = the_header->next;
619 } while (the_header != old);
620 *where = '\0';
621 return;
622}
623\f
3d23b985
RS
624/* Read lines from the input until we get a blank line.
625 Create a list of `header' objects, one for each header field,
626 each of which points to a list of `line_list' objects,
627 one for each line in that field.
628 Continuation lines are grouped in the headers they continue. */
629
6b3ee98a
RS
630header
631read_header ()
632{
633 register header the_header = ((header) NULL);
634 register line_list *next_line = ((line_list *) NULL);
635
636 init_linebuffer (&lb);
637
638 do
639 {
640 long length;
641 register char *line;
642
643 readline (&lb, stdin);
644 line = lb.buffer;
645 length = strlen (line);
646 if (length == 0) break;
647
648 if (has_keyword (line))
649 {
650 register header old = the_header;
651 the_header = new_header ();
652 if (old == ((header) NULL))
653 {
654 the_header->next = the_header;
655 the_header->previous = the_header;
656 }
657 else
658 {
659 the_header->previous = old;
660 the_header->next = old->next;
661 old->next = the_header;
662 }
663 next_line = &(the_header->text);
664 }
665
666 if (next_line == ((line_list *) NULL))
667 {
668 /* Not a valid header */
669 exit (1);
670 }
671 *next_line = new_list ();
672 (*next_line)->string = alloc_string (length);
673 strcpy (((*next_line)->string), line);
674 next_line = &((*next_line)->continuation);
675 *next_line = NIL;
676
677 } while (true);
678
679 return the_header->next;
680}
681\f
682void
683write_header (the_header)
684 header the_header;
685{
686 register header old = the_header;
687 do
688 {
689 register line_list the_list;
690 for (the_list = the_header->text;
691 the_list != NIL;
692 the_list = the_list->continuation)
693 put_line (the_list->string);
694 the_header = the_header->next;
695 } while (the_header != old);
696 put_line ("");
697 return;
698}
699\f
d0dff6e5 700int
6b3ee98a
RS
701main (argc, argv)
702 int argc;
703 char **argv;
704{
705 char *command_line;
706 header the_header;
707 long name_length;
708 char *mail_program_name;
709 char buf[BUFLEN + 1];
710 register int size;
711 FILE *the_pipe;
712
713 extern char *getenv ();
714
715 mail_program_name = getenv ("FAKEMAILER");
716 if (!(mail_program_name && *mail_program_name))
717 mail_program_name = MAIL_PROGRAM_NAME;
718 name_length = strlen (mail_program_name);
719
720 my_name = MY_NAME;
721 the_streams = ((stream_list) NULL);
722 the_date = ((char *) NULL);
723 the_user = ((char *) NULL);
724
725 the_header = read_header ();
726 command_line = alloc_string (name_length + args_size (the_header));
727 strcpy (command_line, mail_program_name);
728 parse_header (the_header, &command_line[name_length]);
729
730 the_pipe = popen (command_line, "w");
731 if (the_pipe == ((FILE *) NULL))
732 fatal ("cannot open pipe to real mailer");
733
734 add_a_stream (the_pipe, pclose);
735
736 write_header (the_header);
737
738 /* Dump the message itself */
739
740 while (!feof (stdin))
741 {
742 size = fread (buf, 1, BUFLEN, stdin);
743 buf[size] = '\0';
744 put_string (buf);
745 }
746
747 exit (close_the_streams ());
748}
749
26528bbc 750#endif /* not MSDOS */
6b3ee98a 751#endif /* not BSD 4.2 (or newer) */