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