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