Another go at fixing 131
[sxemacs] / lib-src / fakemail.c
1 /* sendmail-like interface to /bin/mail for system V,
2    Copyright (C) 1985, 1994 Free Software Foundation, Inc.
3
4 This file is part of SXEmacs.
5
6 SXEmacs is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 SXEmacs is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
18
19 /* Synched up with: FSF 19.28. */
20
21 #define NO_SHORTNAMES
22 #include <config.h>
23
24 #if defined (BSD) && !defined (BSD4_1) && !defined (USE_FAKEMAIL)
25 /* This program is not used in BSD, so just avoid loader complaints.  */
26 int main(int argc, char *argv[])
27 {
28         return 0;
29 }
30 #elif defined (LINUX)
31 #include <stdio.h>
32 #include <stdlib.h>
33 int main(int argc, char *argv[])
34 {
35         /* Linux /bin/mail, if it exists, is NOT the Unix v7 mail that
36            fakemail depends on!  This causes garbled mail.  Better to
37            output an error message. */
38         fprintf(stderr, "Sorry, fakemail does not work on Linux.\n");
39         fprintf(stderr, "Make sure you have the sendmail program, and\n");
40         fprintf(stderr, "set the Lisp variable `sendmail-program' to point\n");
41         fprintf(stderr, "to the path of the sendmail binary.\n");
42         return 1;
43 }
44 #else                           /* not BSD 4.2 (or newer) */
45
46 /* These are defined in config in some versions. */
47
48 #ifdef static
49 #undef static
50 #endif
51
52 #ifdef read
53 #undef read
54 #undef write
55 #undef open
56 #undef close
57 #endif
58
59 #include <stdio.h>
60 #if __STDC__ || defined(STDC_HEADERS)
61 #include <stdlib.h>
62 #include <unistd.h>
63 #endif
64 #include <string.h>
65 #include <ctype.h>
66 #include <time.h>
67 #include <pwd.h>
68 \f
69 /* Type definitions */
70
71 #define boolean int
72 #define true 1
73 #define false 0
74
75 /* Various lists */
76
77 struct line_record {
78         char *string;
79         struct line_record *continuation;
80 };
81 typedef struct line_record *line_list;
82
83 struct header_record {
84         line_list text;
85         struct header_record *next;
86         struct header_record *previous;
87 };
88 typedef struct header_record *header;
89
90 struct stream_record {
91         FILE *handle;
92         int (*action) (FILE *);
93         struct stream_record *rest_streams;
94 };
95 typedef 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
102 struct linebuffer {
103         size_t size;
104         char *buffer;
105 };
106
107 struct 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
131 static const char *my_name;
132 static char *the_date;
133 static char *the_user;
134 static line_list file_preface;
135 static stream_list the_streams;
136 static boolean no_problems = true;
137
138 #if !__STDC__ && !defined(STDC_HEADERS)
139 extern FILE *popen();
140 extern int fclose(), pclose();
141 extern char *malloc(), *realloc();
142 #endif
143
144 #if defined(__FreeBSD_version) && __FreeBSD_version >= 400000
145 #define CURRENT_USER
146 #endif
147
148 #ifdef CURRENT_USER
149 extern struct passwd *getpwuid();
150 #if defined(__FreeBSD_version) && __FreeBSD_version >= 400000
151 extern uid_t geteuid ();
152 #else
153 extern unsigned short geteuid();
154 #endif
155 static struct passwd *my_entry;
156 #define cuserid(s)                              \
157 (my_entry = getpwuid ((int) geteuid ()),        \
158  my_entry->pw_name)
159 #endif
160 \f
161 /* Utilities */
162
163 /* Print error message.  `s1' is printf control string, `s2' is arg for it. */
164
165 static void error(const char *s1, const char *s2)
166 {
167         printf("%s: ", my_name);
168         printf(s1, s2);
169         printf("\n");
170         no_problems = false;
171 }
172
173 /* Print error message and exit.  */
174
175 static void fatal(const char *s1, const char *s2)
176 {
177         error(s1, s2);
178         exit(1);
179 }
180
181 /* Like malloc but get fatal error if memory is exhausted.  */
182
183 static void *xmalloc(size_t size)
184 {
185         void *result = malloc(size);
186         if (result == NULL)
187                 fatal("virtual memory exhausted", (char *)0);
188         return result;
189 }
190
191 static void *xrealloc(void *ptr, size_t size)
192 {
193         void *result = realloc(ptr, size);
194         if (result == NULL)
195                 fatal("virtual memory exhausted", (char *)0);
196         return result;
197 }
198 \f
199 /* Initialize a linebuffer for use */
200
201 static void init_linebuffer(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
211 static long readline(struct linebuffer *linebuffer, FILE * stream)
212 {
213         char *buffer = linebuffer->buffer;
214         char *p = linebuffer->buffer;
215         char *end = p + linebuffer->size;
216
217         while (true) {
218                 int c = getc(stream);
219                 if (p == end) {
220                         linebuffer->size *= 2;
221                         buffer = (char *)xrealloc(buffer, linebuffer->size);
222                         p = buffer + (p - linebuffer->buffer);
223                         end = buffer + linebuffer->size;
224                         linebuffer->buffer = buffer;
225                 }
226                 if (c < 0 || c == '\n') {
227                         *p = 0;
228                         break;
229                 }
230                 *p++ = c;
231         }
232
233         return p - buffer;
234 }
235 \f
236 static char *get_keyword(register char *field, char **rest)
237 {
238         static char keyword[KEYWORD_SIZE];
239         register char *ptr;
240         register char c;
241
242         ptr = &keyword[0];
243         c = *field++;
244         if ((isspace((int)(unsigned char)c)) || (c == ':'))
245                 return (char *)NULL;
246         *ptr++ = ((islower((int)(unsigned char)c)) ?
247                   (toupper((int)(unsigned char)c)) : c);
248         while (((c = *field++) != ':') && (!(isspace((int)(unsigned char)c))))
249                 *ptr++ = ((islower((int)(unsigned char)c)) ?
250                           (toupper((int)(unsigned char)c)) : c);
251         *ptr++ = '\0';
252         while (isspace((int)(unsigned char)c))
253                 c = *field++;
254         if (c != ':')
255                 return (char *)NULL;
256         *rest = field;
257         return &keyword[0];
258 }
259
260 static boolean has_keyword(char *field)
261 {
262         char *ignored;
263         return (get_keyword(field, &ignored) != (char *)NULL);
264 }
265
266 static char *add_field(line_list the_list, register char *field,
267                        register char *where)
268 {
269         register char c;
270         while (true) {
271                 *where++ = ' ';
272                 while ((c = *field++) != '\0') {
273                         if (c == '(') {
274                                 while (*field && *field != ')')
275                                         ++field;
276                                 if (!(*field++))
277                                         break;  /* no closer */
278                                 if (!(*field))
279                                         break;  /* closerNULL */
280                                 c = *field;
281                         }
282                         *where++ =
283                             ((c == ',' || c == '>' || c == '<') ? ' ' : c);
284                 }
285                 if (the_list == NIL)
286                         break;
287                 field = the_list->string;
288                 the_list = the_list->continuation;
289         }
290         return where;
291 }
292 \f
293 static line_list make_file_preface(void)
294 {
295         char *the_string, *temp;
296         time_t idiotic_interface;
297         long prefix_length;
298         long user_length;
299         long date_length;
300         line_list result;
301         size_t the_string_len, the_user_len, temp_len;
302
303         prefix_length = strlen(FROM_PREFIX);
304         time(&idiotic_interface);
305         the_date = ctime(&idiotic_interface);
306         /* the_date has an unwanted newline at the end */
307         date_length = strlen(the_date) - 1;
308         if (the_date[date_length] == '\n')
309                 the_date[date_length] = '\0';
310         temp = cuserid((char *)NULL);
311         /* the_user */
312         the_user_len = strlen(temp);
313         the_user = alloc_string(the_user_len + 1);
314         strncpy(the_user, the_user_len, temp);
315         /* alloc the_string */
316         the_string_len = 3 + prefix_length + the_user_len + date_length;
317         the_string = alloc_string(the_string_len);
318         temp_len = the_string_len;
319         temp = the_string;
320         strncpy(temp, temp_len, FROM_PREFIX);
321
322         temp = &temp[prefix_length];
323         *temp++ = ' ';
324         temp_len -= prefix_length + 1;
325         strncpy(temp, temp_len, the_user);
326
327         temp = &temp[the_user_len];
328         *temp++ = ' ';
329         temp_len -= the_user_len + 1;
330         strncpy(temp, temp_len, the_date);
331
332         result = new_list();
333         result->string = the_string;
334         result->continuation = ((line_list) NULL);
335         return result;
336 }
337
338 static void write_line_list(register line_list the_list, FILE * the_stream)
339 {
340         for (;
341              the_list != ((line_list) NULL);
342              the_list = the_list->continuation) {
343                 fputs(the_list->string, the_stream);
344                 putc('\n', the_stream);
345         }
346         return;
347 }
348 \f
349 static int close_the_streams(void)
350 {
351         register stream_list rem;
352         for (rem = the_streams;
353              rem != ((stream_list) NULL); rem = rem->rest_streams)
354                 no_problems = (no_problems &&
355                                ((*rem->action) (rem->handle) == 0));
356         the_streams = ((stream_list) NULL);
357         return (no_problems ? 0 : 1);
358 }
359
360 static void add_a_stream(FILE * the_stream, int (*closing_action) (FILE *))
361 {
362         stream_list old = the_streams;
363         the_streams = new_stream();
364         the_streams->handle = the_stream;
365         the_streams->action = closing_action;
366         the_streams->rest_streams = old;
367         return;
368 }
369
370 static int my_fclose(FILE * the_file)
371 {
372         putc('\n', the_file);
373         fflush(the_file);
374         return fclose(the_file);
375 }
376
377 static boolean open_a_file(char *name)
378 {
379         FILE *the_stream = fopen(name, "a");
380         if (the_stream != ((FILE *) NULL)) {
381                 add_a_stream(the_stream, my_fclose);
382                 if (the_user == (char *)NULL)
383                         file_preface = make_file_preface();
384                 write_line_list(file_preface, the_stream);
385                 return true;
386         }
387         return false;
388 }
389
390 static void put_string(char *s)
391 {
392         register stream_list rem;
393         for (rem = the_streams;
394              rem != ((stream_list) NULL); rem = rem->rest_streams)
395                 fputs(s, rem->handle);
396         return;
397 }
398
399 static void put_line(const char *string)
400 {
401         register stream_list rem;
402         for (rem = the_streams;
403              rem != ((stream_list) NULL); rem = rem->rest_streams) {
404                 const char *s = string;
405                 int column = 0;
406
407                 /* Divide STRING into lines.  */
408                 while (*s != 0) {
409                         const char *breakpos;
410
411                         /* Find the last char that fits.  */
412                         for (breakpos = s; *breakpos && column < 78; ++breakpos) {
413                                 if (*breakpos == '\t')
414                                         column += 8;
415                                 else
416                                         column++;
417                         }
418                         /* If we didn't reach end of line, break the line.  */
419                         if (*breakpos) {
420                                 /* Back up to just after the last comma that fits.  */
421                                 while (breakpos != s && breakpos[-1] != ',')
422                                         --breakpos;
423
424                                 if (breakpos == s) {
425                                         /* If no comma fits, move past the first address anyway.  */
426                                         while (*breakpos != 0
427                                                && *breakpos != ',')
428                                                 ++breakpos;
429                                         if (*breakpos != 0)
430                                                 /* Include the comma after it.  */
431                                                 ++breakpos;
432                                 }
433                         }
434                         /* Output that much, then break the line.  */
435                         fwrite(s, 1, breakpos - s, rem->handle);
436                         column = 8;
437
438                         /* Skip whitespace and prepare to print more addresses.  */
439                         s = breakpos;
440                         while (*s == ' ' || *s == '\t')
441                                 ++s;
442                         if (*s != 0)
443                                 fputs("\n\t", rem->handle);
444                 }
445                 putc('\n', rem->handle);
446         }
447         return;
448 }
449 \f
450 #define mail_error error
451
452 static void setup_files(register line_list the_list, register char *field)
453 {
454         register char *start;
455         register char c;
456         while (true) {
457                 while (((c = *field) != '\0') &&
458                        ((c == ' ') || (c == '\t') || (c == ',')))
459                         field += 1;
460                 if (c != '\0') {
461                         start = field;
462                         while (((c = *field) != '\0') &&
463                                (c != ' ') && (c != '\t') && (c != ','))
464                                 field += 1;
465                         *field = '\0';
466                         if (!open_a_file(start))
467                                 mail_error("Could not open file %s", start);
468                         *field = c;
469                         if (c != '\0')
470                                 continue;
471                 }
472                 if (the_list == ((line_list) NULL))
473                         return;
474                 field = the_list->string;
475                 the_list = the_list->continuation;
476         }
477 }
478 \f
479 static int args_size(header the_header)
480 {
481         register header old = the_header;
482         register line_list rem;
483         register int size = 0;
484         do {
485                 char *field;
486                 register char *keyword =
487                     get_keyword(the_header->text->string, &field);
488                 if ((strcmp(keyword, "TO") == 0) || (strcmp(keyword, "CC") == 0)
489                     || (strcmp(keyword, "BCC") == 0)) {
490                         size += 1 + strlen(field);
491                         for (rem = the_header->text->continuation;
492                              rem != NIL; rem = rem->continuation)
493                                 size += 1 + strlen(rem->string);
494                 }
495                 the_header = the_header->next;
496         } while (the_header != old);
497         return size;
498 }
499
500 static void parse_header(header the_header, register char *where)
501 {
502         register header old = the_header;
503         do {
504                 char *field;
505                 register char *keyword =
506                     get_keyword(the_header->text->string, &field);
507                 if (strcmp(keyword, "TO") == 0)
508                         where =
509                             add_field(the_header->text->continuation, field,
510                                       where);
511                 else if (strcmp(keyword, "CC") == 0)
512                         where =
513                             add_field(the_header->text->continuation, field,
514                                       where);
515                 else if (strcmp(keyword, "BCC") == 0) {
516                         where =
517                             add_field(the_header->text->continuation, field,
518                                       where);
519                         the_header->previous->next = the_header->next;
520                         the_header->next->previous = the_header->previous;
521                 } else if (strcmp(keyword, "FCC") == 0)
522                         setup_files(the_header->text->continuation, field);
523                 the_header = the_header->next;
524         } while (the_header != old);
525         *where = '\0';
526         return;
527 }
528 \f
529 static header read_header(void)
530 {
531         register header the_header = ((header) NULL);
532         register line_list *next_line = ((line_list *) NULL);
533
534         init_linebuffer(&lb);
535
536         do {
537                 long length;
538                 register char *line;
539
540                 readline(&lb, stdin);
541                 line = lb.buffer;
542                 length = strlen(line);
543                 if (length == 0)
544                         break;
545
546                 if (has_keyword(line)) {
547                         register header old = the_header;
548                         the_header = new_header();
549                         if (old == ((header) NULL)) {
550                                 the_header->next = the_header;
551                                 the_header->previous = the_header;
552                         } else {
553                                 the_header->previous = old;
554                                 the_header->next = old->next;
555                                 old->next = the_header;
556                         }
557                         next_line = &(the_header->text);
558                 }
559
560                 if (next_line == ((line_list *) NULL)) {
561                         /* Not a valid header */
562                         exit(1);
563                 }
564                 *next_line = new_list();
565                 (*next_line)->string = alloc_string((size_t) length);
566                 strncpy(((*next_line)->string), length, line);
567                 next_line = &((*next_line)->continuation);
568                 *next_line = NIL;
569
570         } while (true);
571
572         return the_header->next;
573 }
574 \f
575 static void write_header(header the_header)
576 {
577         register header old = the_header;
578         do {
579                 register line_list the_list;
580                 for (the_list = the_header->text;
581                      the_list != NIL; the_list = the_list->continuation)
582                         put_line(the_list->string);
583                 the_header = the_header->next;
584         } while (the_header != old);
585         put_line("");
586         return;
587 }
588 \f
589 int main(int argc, char *argv[])
590 {
591         char *command_line;
592         size_t command_line_len;
593         header the_header;
594         long name_length;
595         char *mail_program_name;
596         char buf[BUFLEN + 1];
597         register int size;
598         FILE *the_pipe;
599
600         mail_program_name = getenv("FAKEMAILER");
601         if (!(mail_program_name && *mail_program_name))
602                 mail_program_name = (char *)MAIL_PROGRAM_NAME;
603         name_length = strlen(mail_program_name);
604
605         my_name = MY_NAME;
606         the_streams = ((stream_list) NULL);
607         the_date = (char *)NULL;
608         the_user = (char *)NULL;
609
610         the_header = read_header();
611         command_line_len = name_length + args_size(the_header);
612         command_line = alloc_string(command_line_len);
613         strncpy(command_line, command_line_len, mail_program_name);
614         parse_header(the_header, &command_line[name_length]);
615
616         the_pipe = popen(command_line, "w");
617         if (the_pipe == ((FILE *) NULL))
618                 fatal("cannot open pipe to real mailer", (char *)NULL);
619
620         add_a_stream(the_pipe, pclose);
621
622         write_header(the_header);
623
624         /* Dump the message itself */
625
626         while (!feof(stdin)) {
627                 size = fread(buf, 1, BUFLEN, stdin);
628                 buf[size] = '\0';
629                 put_string(buf);
630         }
631
632         return close_the_streams();
633 }
634
635 #endif                          /* not BSD 4.2 (or newer) */