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