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