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