Got rid of the rest of the warnings that I didn't have time for on 1999-07-15
[mmh] / mts / sendmail / sendmail.c
1
2 /*
3  * sendmail.c -- nmh sendmail interface
4  *
5  * $Id$
6  */
7
8 #include <h/mh.h>
9 #include <mts/smtp/smtp.h>
10 #include <zotnet/mts/mts.h>
11 #include <signal.h>
12 #include "h/signals.h"  /* for SIGNAL() */
13 #ifdef MPOP
14 #include <errno.h>
15 #endif
16
17 /*
18  * This module implements an interface to SendMail very similar
19  * to the MMDF mm_(3) routines.  The sm_() routines herein talk
20  * SMTP to a sendmail process, mapping SMTP reply codes into
21  * RP_-style codes.
22  */
23
24 /*
25  * On older 4.2BSD machines without the POSIX function `sigaction',
26  * the alarm handing stuff for time-outs will NOT work due to the way
27  * syscalls get restarted.  This is not really crucial, since SendMail
28  * is generally well-behaved in this area.
29  */
30
31 #ifdef SENDMAILBUG
32 /*
33  * It appears that some versions of Sendmail will return Code 451
34  * when they don't really want to indicate a failure.
35  * "Code 451 almost always means sendmail has deferred; we don't
36  * really want bomb out at this point since sendmail will rectify
37  * things later."  So, if you define SENDMAILBUG, Code 451 is
38  * considered the same as Code 250.  Yuck!
39  */
40 #endif
41
42 #define TRUE    1
43 #define FALSE   0
44
45 #define NBITS ((sizeof (int)) * 8)
46
47 /*
48  * these codes must all be different!
49  */
50 #define SM_OPEN  90      /* Changed from 30 in case of nameserver flakiness */
51 #define SM_HELO  20
52 #define SM_RSET  15
53 #define SM_MAIL  40
54 #define SM_RCPT 120
55 #define SM_DATA  20
56 #define SM_TEXT 150
57 #define SM_DOT  180
58 #define SM_QUIT  30
59 #define SM_CLOS  10
60
61 static int sm_addrs = 0;
62 static int sm_alarmed = 0;
63 static int sm_child = NOTOK;
64 static int sm_debug = 0;
65 static int sm_nl = TRUE;
66 static int sm_verbose = 0;
67
68 static FILE *sm_rfp = NULL;
69 static FILE *sm_wfp = NULL;
70
71 #ifdef MPOP
72 static int sm_ispool = 0;
73 static char sm_tmpfil[BUFSIZ];
74 #endif /* MPOP */
75
76 static char *sm_noreply = "No reply text given";
77 static char *sm_moreply = "; ";
78
79 struct smtp sm_reply;           /* global... */
80
81 static int doingEHLO;
82
83 #define MAXEHLO 20
84 char *EHLOkeys[MAXEHLO + 1];
85
86 /*
87  * static prototypes
88  */
89 static int sm_ierror (char *fmt, ...);
90 static int smtalk (int time, char *fmt, ...);
91 static int sm_wrecord (char *, int);
92 static int sm_wstream (char *, int);
93 static int sm_werror (void);
94 static int smhear (void);
95 static int sm_rrecord (char *, int *);
96 static int sm_rerror (void);
97 static RETSIGTYPE alrmser (int);
98
99
100 int
101 sm_init (char *client, char *server, int watch, int verbose,
102          int debug, int onex, int queued)
103 {
104     int i, result, vecp;
105     int pdi[2], pdo[2];
106     char *vec[15];
107
108     if (watch)
109         verbose = TRUE;
110
111     sm_verbose = verbose;
112     sm_debug = debug;
113     if (sm_rfp != NULL && sm_wfp != NULL)
114         return RP_OK;
115
116     if (client == NULL || *client == '\0') {
117         if (clientname)
118             client = clientname;
119         else
120             client = LocalName();       /* no clientname -> LocalName */
121     }
122
123 #ifdef ZMAILER
124     if (client == NULL || *client == '\0')
125         client = "localhost";
126 #endif
127
128     if (pipe (pdi) == NOTOK)
129         return sm_ierror ("no pipes");
130     if (pipe (pdo) == NOTOK) {
131         close (pdi[0]);
132         close (pdi[1]);
133         return sm_ierror ("no pipes");
134     }
135
136     for (i = 0; (sm_child = fork ()) == NOTOK && i < 5; i++)
137         sleep (5);
138
139     switch (sm_child) {
140         case NOTOK: 
141             close (pdo[0]);
142             close (pdo[1]);
143             close (pdi[0]);
144             close (pdi[1]);
145             return sm_ierror ("unable to fork");
146
147         case OK: 
148             if (pdo[0] != fileno (stdin))
149                 dup2 (pdo[0], fileno (stdin));
150             if (pdi[1] != fileno (stdout))
151                 dup2 (pdi[1], fileno (stdout));
152             if (pdi[1] != fileno (stderr))
153                 dup2 (pdi[1], fileno (stderr));
154             for (i = fileno (stderr) + 1; i < NBITS; i++)
155                 close (i);
156
157             vecp = 0;
158             vec[vecp++] = r1bindex (sendmail, '/');
159             vec[vecp++] = "-bs";
160 #ifndef ZMAILER
161             vec[vecp++] = watch ? "-odi" : queued ? "-odq" : "-odb";
162             vec[vecp++] = "-oem";
163             vec[vecp++] = "-om";
164 # ifndef RAND
165             if (verbose)
166                 vec[vecp++] = "-ov";
167 # endif /* not RAND */
168 #endif /* not ZMAILER */
169             vec[vecp++] = NULL;
170
171             setgid (getegid ());
172             setuid (geteuid ());
173             execvp (sendmail, vec);
174             fprintf (stderr, "unable to exec ");
175             perror (sendmail);
176             _exit (-1);         /* NOTREACHED */
177
178         default: 
179             SIGNAL (SIGALRM, alrmser);
180             SIGNAL (SIGPIPE, SIG_IGN);
181
182             close (pdi[1]);
183             close (pdo[0]);
184             if ((sm_rfp = fdopen (pdi[0], "r")) == NULL
185                     || (sm_wfp = fdopen (pdo[1], "w")) == NULL) {
186                 close (pdi[0]);
187                 close (pdo[1]);
188                 sm_rfp = sm_wfp = NULL;
189                 return sm_ierror ("unable to fdopen");
190             }
191             sm_alarmed = 0;
192             alarm (SM_OPEN);
193             result = smhear ();
194             alarm (0);
195             switch (result) {
196                 case 220: 
197                     break;
198
199                 default: 
200                     sm_end (NOTOK);
201                     return RP_RPLY;
202             }
203
204             if (client && *client) {
205                 doingEHLO = 1;
206                 result = smtalk (SM_HELO, "EHLO %s", client);
207                 doingEHLO = 0;
208
209                 if (500 <= result && result <= 599)
210                     result = smtalk (SM_HELO, "HELO %s", client);
211
212                 switch (result) {
213                     case 250:
214                         break;
215
216                     default:
217                         sm_end (NOTOK);
218                         return RP_RPLY;
219                 }
220             }
221
222 #ifndef ZMAILER
223             if (onex)
224                 smtalk (SM_HELO, "ONEX");
225 #endif
226             if (watch)
227                 smtalk (SM_HELO, "VERB on");
228
229             return RP_OK;
230     }
231 }
232
233
234 int
235 sm_winit (int mode, char *from)
236 {
237 #ifdef MPOP
238     if (sm_ispool && !sm_wfp) {
239         strlen (strcpy (sm_reply.text, "unable to create new spool file"));
240         sm_reply.code = NOTOK;
241         return RP_BHST;
242     }
243 #endif /* MPOP */
244
245     switch (smtalk (SM_MAIL, "%s FROM:<%s>",
246                 mode == S_SEND ? "SEND" : mode == S_SOML ? "SOML"
247                 : mode == S_SAML ? "SAML" : "MAIL", from)) {
248         case 250: 
249             sm_addrs = 0;
250             return RP_OK;
251
252         case 500: 
253         case 501: 
254         case 552: 
255             return RP_PARM;
256
257         default: 
258             return RP_RPLY;
259     }
260 }
261
262
263 int
264 sm_wadr (char *mbox, char *host, char *path)
265 {
266     switch (smtalk (SM_RCPT, host && *host ? "RCPT TO:<%s%s@%s>"
267                                            : "RCPT TO:<%s%s>",
268                              path ? path : "", mbox, host)) {
269         case 250: 
270         case 251: 
271             sm_addrs++;
272             return RP_OK;
273
274         case 451: 
275 #ifdef SENDMAILBUG
276             sm_addrs++;
277             return RP_OK;
278 #endif /* SENDMAILBUG */
279         case 421: 
280         case 450: 
281         case 452: 
282             return RP_NO;
283
284         case 500: 
285         case 501: 
286             return RP_PARM;
287
288         case 550: 
289         case 551: 
290         case 552: 
291         case 553: 
292             return RP_USER;
293
294         default: 
295             return RP_RPLY;
296     }
297 }
298
299
300 int
301 sm_waend (void)
302 {
303     switch (smtalk (SM_DATA, "DATA")) {
304         case 354: 
305             sm_nl = TRUE;
306             return RP_OK;
307
308         case 451: 
309 #ifdef SENDMAILBUG
310             sm_nl = TRUE;
311             return RP_OK;
312 #endif /* SENDMAILBUG */
313         case 421: 
314             return RP_NO;
315
316         case 500: 
317         case 501: 
318         case 503: 
319         case 554: 
320             return RP_NDEL;
321
322         default: 
323             return RP_RPLY;
324     }
325 }
326
327
328 int
329 sm_wtxt (char *buffer, int len)
330 {
331     int result;
332
333     sm_alarmed = 0;
334     alarm (SM_TEXT);
335     result = sm_wstream (buffer, len);
336     alarm (0);
337
338     return (result == NOTOK ? RP_BHST : RP_OK);
339 }
340
341
342 int
343 sm_wtend (void)
344 {
345     if (sm_wstream ((char *) NULL, 0) == NOTOK)
346         return RP_BHST;
347
348     switch (smtalk (SM_DOT + 3 * sm_addrs, ".")) {
349         case 250: 
350         case 251: 
351             return RP_OK;
352
353         case 451: 
354 #ifdef SENDMAILBUG
355             return RP_OK;
356 #endif /* SENDMAILBUG */
357         case 452: 
358         default: 
359             return RP_NO;
360
361         case 552: 
362         case 554: 
363             return RP_NDEL;
364     }
365 }
366
367
368 int
369 sm_end (int type)
370 {
371     int status;
372     struct smtp sm_note;
373
374     switch (sm_child) {
375         case NOTOK: 
376         case OK: 
377             return RP_OK;
378
379         default: 
380             break;
381     }
382
383     if (sm_rfp == NULL && sm_wfp == NULL)
384         return RP_OK;
385
386     switch (type) {
387         case OK: 
388             smtalk (SM_QUIT, "QUIT");
389             break;
390
391         case NOTOK: 
392             sm_note.code = sm_reply.code;
393             strncpy (sm_note.text, sm_reply.text, sm_note.length = sm_reply.length);/* fall */
394         case DONE: 
395             if (smtalk (SM_RSET, "RSET") == 250 && type == DONE)
396                 return RP_OK;
397             kill (sm_child, SIGKILL);
398             discard (sm_rfp);
399             discard (sm_wfp);
400             if (type == NOTOK) {
401                 sm_reply.code = sm_note.code;
402                 strncpy (sm_reply.text, sm_note.text, sm_reply.length = sm_note.length);
403             }
404             break;
405     }
406     if (sm_rfp != NULL) {
407         alarm (SM_CLOS);
408         fclose (sm_rfp);
409         alarm (0);
410     }
411     if (sm_wfp != NULL) {
412         alarm (SM_CLOS);
413         fclose (sm_wfp);
414         alarm (0);
415     }
416
417     status = pidwait (sm_child, OK);
418
419     sm_child = NOTOK;
420     sm_rfp = sm_wfp = NULL;
421
422     return (status ? RP_BHST : RP_OK);
423 }
424
425
426 static int
427 sm_ierror (char *fmt, ...)
428 {
429     va_list ap;
430
431     va_start(ap, fmt);
432     vsnprintf (sm_reply.text, sizeof(sm_reply.text), fmt, ap);
433     va_end(ap);
434
435     sm_reply.length = strlen (sm_reply.text);
436     sm_reply.code = NOTOK;
437
438     return RP_BHST;
439 }
440
441
442 static int
443 smtalk (int time, char *fmt, ...)
444 {
445     int result;
446     char buffer[BUFSIZ];
447     va_list ap;
448
449     va_start(ap, fmt);
450     vsnprintf (buffer, sizeof(buffer), fmt, ap);
451     va_end(ap);
452
453     if (sm_debug) {
454         printf ("=> %s\n", buffer);
455         fflush (stdout);
456     }
457
458 #ifdef MPOP
459     if (sm_ispool) {
460         char file[BUFSIZ];
461
462         if (strcmp (buffer, ".") == 0)
463             time = SM_DOT;
464         fprintf (sm_wfp, "%s\r\n", buffer);
465         switch (time) {
466             case SM_DOT:
467                 fflush (sm_wfp);
468                 if (ferror (sm_wfp))
469                     return sm_werror ();
470                 snprintf (file, sizeof(file), "%s%c.bulk", sm_tmpfil,
471                                 (char) (sm_ispool + 'a' - 1));
472                 if (rename (sm_tmpfil, file) == NOTOK) {
473                     int len;
474                     char *bp;
475
476                     snprintf (sm_reply.text, sizeof(sm_reply.text),
477                         "error renaming %s to %s: ", sm_tmpfil, file);
478                     bp = sm_reply.text;
479                     len = strlen (bp);
480                     bp += len;
481                     if ((s = strerror (errno)))
482                         strncpy (bp, s, sizeof(sm_reply.text) - len);
483                     else
484                         snprintf (bp, sizeof(sm_reply.text) - len,
485                                 "unknown error %d", errno);
486                     sm_reply.length = strlen (sm_reply.text);
487                     sm_reply.code = NOTOK;
488                     return RP_BHST;
489                 }
490                 fclose (sm_wfp);
491                 if (sm_wfp = fopen (sm_tmpfil, "w"))
492                     chmod (sm_tmpfil, 0600);
493                 sm_ispool++;
494                 /* and fall... */
495
496             case SM_MAIL:
497             case SM_RCPT:
498                 result = 250;
499                 break;
500
501             case SM_RSET:
502                 fflush (sm_wfp);
503                 ftruncate (fileno (sm_wfp), 0L);
504                 fseek (sm_wfp, 0L, SEEK_SET);
505                 result = 250;
506                 break;
507
508             case SM_DATA:
509                 result = 354;
510                 break;
511
512             case SM_QUIT:
513                 unlink (sm_tmpfil);
514                 sm_ispool = 0;
515                 result = 221;
516                 break;
517
518             default:
519                 result = 500;
520                 break;
521         }
522         if (sm_debug) {
523             printf ("<= %d\n", result);
524             fflush (stdout);
525         }
526
527         sm_reply.text[sm_reply.length = 0] = NULL;
528         return (sm_reply.code = result);
529     }
530 #endif /* MPOP */
531
532     sm_alarmed = 0;
533     alarm ((unsigned) time);
534     if ((result = sm_wrecord (buffer, strlen (buffer))) != NOTOK)
535         result = smhear ();
536     alarm (0);
537
538     return result;
539 }
540
541
542 static int
543 sm_wrecord (char *buffer, int len)
544 {
545     if (sm_wfp == NULL)
546         return sm_werror ();
547
548     fwrite (buffer, sizeof *buffer, len, sm_wfp);
549     fputs ("\r\n", sm_wfp);
550     fflush (sm_wfp);
551
552     return (ferror (sm_wfp) ? sm_werror () : OK);
553 }
554
555
556 static int
557 sm_wstream (char *buffer, int len)
558 {
559     char *bp;
560     static char lc = 0;
561
562     if (sm_wfp == NULL)
563         return sm_werror ();
564
565     if (buffer == NULL && len == 0) {
566         if (lc != '\n')
567             fputs ("\r\n", sm_wfp);
568         lc = 0;
569         return (ferror (sm_wfp) ? sm_werror () : OK);
570     }
571
572     for (bp = buffer; len > 0; bp++, len--) {
573         switch (*bp) {
574             case '\n': 
575                 sm_nl = TRUE;
576                 fputc ('\r', sm_wfp);
577                 break;
578
579             case '.': 
580                 if (sm_nl)
581                     fputc ('.', sm_wfp);/* FALL THROUGH */
582             default: 
583                 sm_nl = FALSE;
584         }
585         fputc (*bp, sm_wfp);
586         if (ferror (sm_wfp))
587             return sm_werror ();
588     }
589
590     if (bp > buffer)
591         lc = *--bp;
592     return (ferror (sm_wfp) ? sm_werror () : OK);
593 }
594
595
596 #ifdef _AIX
597 /*
598  * AIX by default will inline the strlen and strcpy commands by redefining
599  * them as __strlen and __strcpy respectively.  This causes compile problems
600  * with the #ifdef MPOP in the middle.  Should the #ifdef MPOP be removed,
601  * remove these #undefs.
602  */
603 # undef strlen
604 # undef strcpy
605 #endif /* _AIX */
606
607 static int
608 sm_werror (void)
609 {
610     sm_reply.length =
611         strlen (strcpy (sm_reply.text, sm_wfp == NULL ? "no pipe opened"
612             : sm_alarmed ? "write to pipe timed out"
613             : "error writing to pipe"));
614
615     return (sm_reply.code = NOTOK);
616 }
617
618
619 static int
620 smhear (void)
621 {
622     int i, code, cont, bc, rc, more;
623     char *bp, *rp;
624     char **ehlo, buffer[BUFSIZ];
625
626     if (doingEHLO) {
627         static int at_least_once = 0;
628
629         if (at_least_once) {
630             for (ehlo = EHLOkeys; *ehlo; ehlo++)
631                 free (*ehlo);
632         } else {
633             at_least_once = 1;
634         }
635
636         *(ehlo = EHLOkeys) = NULL;
637     }
638
639 again:
640
641     sm_reply.text[sm_reply.length = 0] = 0;
642
643     rp = sm_reply.text;
644     rc = sizeof(sm_reply.text) - 1;
645
646     for (more = FALSE; sm_rrecord (bp = buffer, &bc) != NOTOK;) {
647         if (sm_debug) {
648             printf ("<= %s\n", buffer);
649             fflush (stdout);
650         }
651
652         if (doingEHLO
653                 && strncmp (buffer, "250", sizeof("250") - 1) == 0
654                 && (buffer[3] == '-' || doingEHLO == 2)
655                 && buffer[4]) {
656             if (doingEHLO == 2) {
657                 if ((*ehlo = malloc ((size_t) (strlen (buffer + 4) + 1)))) {
658                     strcpy (*ehlo++, buffer + 4);
659                     *ehlo = NULL;
660                     if (ehlo >= EHLOkeys + MAXEHLO)
661                         doingEHLO = 0;
662                 }
663                 else
664                     doingEHLO = 0;
665             }
666             else
667                 doingEHLO = 2;
668         }
669
670         for (; bc > 0 && (!isascii (*bp) || !isdigit (*bp)); bp++, bc--)
671             continue;
672
673         cont = FALSE;
674         code = atoi (bp);
675         bp += 3, bc -= 3;
676         for (; bc > 0 && isspace (*bp); bp++, bc--)
677             continue;
678         if (bc > 0 && *bp == '-') {
679             cont = TRUE;
680             bp++, bc--;
681             for (; bc > 0 && isspace (*bp); bp++, bc--)
682                 continue;
683         }
684
685         if (more) {
686             if (code != sm_reply.code || cont)
687                 continue;
688             more = FALSE;
689         } else {
690             sm_reply.code = code;
691             more = cont;
692             if (bc <= 0) {
693                 strncpy (buffer, sm_noreply, sizeof(buffer));
694                 bp = buffer;
695                 bc = strlen (sm_noreply);
696             }
697         }
698         if ((i = min (bc, rc)) > 0) {
699             strncpy (rp, bp, i);
700             rp += i;
701             rc -= i;
702             if (more && rc > strlen (sm_moreply) + 1) {
703                 strncpy (sm_reply.text + rc, sm_moreply, sizeof(sm_reply.text) - rc);
704                 rc += strlen (sm_moreply);
705             }
706         }
707         if (more)
708             continue;
709         if (sm_reply.code < 100) {
710             if (sm_verbose) {
711                 printf ("%s\n", sm_reply.text);
712                 fflush (stdout);
713             }
714             goto again;
715         }
716
717         sm_reply.length = rp - sm_reply.text;
718
719         return sm_reply.code;
720     }
721
722     return NOTOK;
723 }
724
725
726 static int
727 sm_rrecord (char *buffer, int *len)
728 {
729     if (sm_rfp == NULL)
730         return sm_rerror ();
731
732     buffer[*len = 0] = 0;
733
734     fgets (buffer, BUFSIZ, sm_rfp);
735     *len = strlen (buffer);
736     if (ferror (sm_rfp) || feof (sm_rfp))
737         return sm_rerror ();
738     if (buffer[*len - 1] != '\n')
739         while (getc (sm_rfp) != '\n' && !ferror (sm_rfp) && !feof (sm_rfp))
740             continue;
741     else
742         if (buffer[*len - 2] == '\r')
743             *len -= 1;
744     buffer[*len - 1] = 0;
745
746     return OK;
747 }
748
749
750 static int
751 sm_rerror (void)
752 {
753     sm_reply.length =
754         strlen (strcpy (sm_reply.text, sm_rfp == NULL ? "no pipe opened"
755             : sm_alarmed ? "read from pipe timed out"
756             : feof (sm_rfp) ? "premature end-of-file on pipe"
757             : "error reading from pipe"));
758
759     return (sm_reply.code = NOTOK);
760 }
761
762
763 static RETSIGTYPE
764 alrmser (int i)
765 {
766 #ifndef RELIABLE_SIGNALS
767     SIGNAL (SIGALRM, alrmser);
768 #endif
769
770     sm_alarmed++;
771     if (sm_debug) {
772         printf ("timed out...\n");
773         fflush (stdout);
774     }
775 }
776
777
778 char *
779 rp_string (int code)
780 {
781     char  *text;
782     static char buffer[BUFSIZ];
783
784     switch (sm_reply.code != NOTOK ? code : NOTOK) {
785         case RP_AOK:
786             text = "AOK";
787             break;
788
789         case RP_MOK:
790             text = "MOK";
791             break;
792
793         case RP_OK: 
794             text = "OK";
795             break;
796
797         case RP_RPLY: 
798             text = "RPLY";
799             break;
800
801         case RP_BHST: 
802         default: 
803             text = "BHST";
804             snprintf (buffer, sizeof(buffer), "[%s] %s", text, sm_reply.text);
805             return buffer;
806
807         case RP_PARM: 
808             text = "PARM";
809             break;
810
811         case RP_NO: 
812             text = "NO";
813             break;
814
815         case RP_USER: 
816             text = "USER";
817             break;
818
819         case RP_NDEL: 
820             text = "NDEL";
821             break;
822     }
823
824     snprintf (buffer, sizeof(buffer), "[%s] %3d %s",
825                 text, sm_reply.code, sm_reply.text);
826     return buffer;
827 }
828