Add support for large files. Merge glibc 2.1.2.
[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
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
7c544420
RS
51#ifdef WINDOWSNT
52#include "ntlib.h"
53#endif
54
6b3ee98a
RS
55#include <stdio.h>
56#include <string.h>
57#include <ctype.h>
58#include <time.h>
59#include <pwd.h>
7543028e
RS
60
61/* This is to declare cuserid. */
62#ifdef HAVE_UNISTD_H
63#include <unistd.h>
64#endif
6b3ee98a
RS
65\f
66/* Type definitions */
67
68#define boolean int
69#define true 1
70#define false 0
71
72/* Various lists */
73
74struct line_record
75{
76 char *string;
77 struct line_record *continuation;
78};
79typedef struct line_record *line_list;
80
81struct header_record
82{
83 line_list text;
84 struct header_record *next;
85 struct header_record *previous;
86};
87typedef struct header_record *header;
88
89struct stream_record
90{
91 FILE *handle;
92 int (*action)();
93 struct stream_record *rest_streams;
94};
95typedef struct stream_record *stream_list;
96
97/* A `struct linebuffer' is a structure which holds a line of text.
98 * `readline' reads a line from a stream into a linebuffer
99 * and works regardless of the length of the line.
100 */
101
102struct linebuffer
103{
104 long size;
105 char *buffer;
106};
107
108struct linebuffer lb;
109
110#define new_list() \
111 ((line_list) xmalloc (sizeof (struct line_record)))
112#define new_header() \
113 ((header) xmalloc (sizeof (struct header_record)))
114#define new_stream() \
115 ((stream_list) xmalloc (sizeof (struct stream_record)))
116#define alloc_string(nchars) \
117 ((char *) xmalloc ((nchars) + 1))
118\f
119/* Global declarations */
120
121#define BUFLEN 1024
122#define KEYWORD_SIZE 256
123#define FROM_PREFIX "From"
124#define MY_NAME "fakemail"
125#define NIL ((line_list) NULL)
126#define INITIAL_LINE_SIZE 200
127
128#ifndef MAIL_PROGRAM_NAME
129#define MAIL_PROGRAM_NAME "/bin/mail"
130#endif
131
132static char *my_name;
133static char *the_date;
134static char *the_user;
135static line_list file_preface;
136static stream_list the_streams;
137static boolean no_problems = true;
138
139extern FILE *popen ();
140extern int fclose (), pclose ();
6b3ee98a
RS
141
142#ifdef CURRENT_USER
143extern struct passwd *getpwuid ();
144extern unsigned short geteuid ();
145static struct passwd *my_entry;
146#define cuserid(s) \
147(my_entry = getpwuid (((int) geteuid ())), \
148 my_entry->pw_name)
149#endif
150\f
151/* Utilities */
152
153/* Print error message. `s1' is printf control string, `s2' is arg for it. */
154
155static void
156error (s1, s2)
157 char *s1, *s2;
158{
159 printf ("%s: ", my_name);
160 printf (s1, s2);
161 printf ("\n");
162 no_problems = false;
163}
164
165/* Print error message and exit. */
166
167static void
168fatal (s1, s2)
169 char *s1, *s2;
170{
171 error (s1, s2);
172 exit (1);
173}
174
175/* Like malloc but get fatal error if memory is exhausted. */
176
65119039 177static long *
6b3ee98a
RS
178xmalloc (size)
179 int size;
180{
65119039
RS
181 long *result = (long *) malloc (((unsigned) size));
182 if (result == ((long *) NULL))
6b3ee98a
RS
183 fatal ("virtual memory exhausted", 0);
184 return result;
185}
186
65119039 187static long *
6b3ee98a 188xrealloc (ptr, size)
65119039 189 long *ptr;
6b3ee98a
RS
190 int size;
191{
65119039 192 long *result = (long *) realloc (ptr, ((unsigned) size));
5391a863 193 if (result == ((long *) NULL))
6b3ee98a
RS
194 fatal ("virtual memory exhausted");
195 return result;
196}
197\f
198/* Initialize a linebuffer for use */
199
200void
201init_linebuffer (linebuffer)
202 struct linebuffer *linebuffer;
203{
204 linebuffer->size = INITIAL_LINE_SIZE;
205 linebuffer->buffer = ((char *) xmalloc (INITIAL_LINE_SIZE));
206}
207
208/* Read a line of text from `stream' into `linebuffer'.
209 * Return the length of the line.
210 */
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;
227 buffer = ((char *) xrealloc (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;
256 register char c;
257
258 ptr = &keyword[0];
259 c = *field++;
3d23b985 260 if (isspace (c) || c == ':')
6b3ee98a 261 return ((char *) NULL);
3d23b985
RS
262 *ptr++ = (islower (c) ? toupper (c) : c);
263 while (((c = *field++) != ':') && ! isspace (c))
264 *ptr++ = (islower (c) ? toupper (c) : c);
6b3ee98a 265 *ptr++ = '\0';
3d23b985
RS
266 while (isspace (c))
267 c = *field++;
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);
414 return (no_problems ? 0 : 1);
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}
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. */
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 */
671 exit (1);
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]);
731
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) */