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