f7756381d2df2aae2858534215fa9ab575f9d79b
[mmh] / mts / smtp / smtp.c
1
2 /*
3  * smtp.c -- nmh SMTP interface
4  *
5  * $Id$
6  */
7
8 #include <h/mh.h>
9 #include "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 /*
42  * these codes must all be different!
43  */
44 #define SM_OPEN  90      /* Changed from 30 in case of nameserver flakiness */
45 #define SM_HELO  20
46 #define SM_RSET  15
47 #define SM_MAIL  40
48 #define SM_RCPT 120
49 #define SM_DATA  20
50 #define SM_TEXT 150
51 #define SM_DOT  180
52 #define SM_QUIT  30
53 #define SM_CLOS  10
54
55 static int sm_addrs = 0;
56 static int sm_alarmed = 0;
57 static int sm_debug = 0;
58 static int sm_nl = TRUE;
59 static int sm_verbose = 0;
60
61 static FILE *sm_rfp = NULL;
62 static FILE *sm_wfp = NULL;
63
64 #ifdef MPOP
65 static int sm_ispool = 0;
66 static char sm_tmpfil[BUFSIZ];
67 #endif /* MPOP */
68
69 static char *sm_noreply = "No reply text given";
70 static char *sm_moreply = "; ";
71
72 struct smtp sm_reply;           /* global... */
73
74 #ifdef MPOP
75 extern int errno;
76 #endif
77
78
79 #define MAXEHLO 20
80
81 static int doingEHLO;
82 char *EHLOkeys[MAXEHLO + 1];
83
84 /*
85  * static prototypes
86  */
87 static int rclient (char *, char *, char *);
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 static char *EHLOset (char *);
98
99 #ifdef MPOP
100 /*
101  * smtp.c's own static copy of several nmh library subroutines
102  */
103 static char **smail_brkstring (char *, char *, char *);
104 static int smail_brkany (char, char *);
105 char **smail_copyip (char **, char **, int);
106 #endif
107
108
109 int
110 sm_init (char *client, char *server, int watch, int verbose,
111          int debug, int onex, int queued)
112 {
113     int result, sd1, sd2;
114
115     if (watch)
116         verbose = TRUE;
117
118     sm_verbose = verbose;
119     sm_debug = debug;
120
121 #ifdef MPOP
122     if (sm_ispool)
123         goto all_done;
124 #endif
125
126     if (sm_rfp != NULL && sm_wfp != NULL)
127         goto send_options;
128
129     if (client == NULL || *client == '\0')
130         if (clientname)
131             client = clientname;
132         else
133             client = LocalName();       /* no clientname -> LocalName */
134
135 #ifdef ZMAILER
136     if (client == NULL || *client == '\0')
137         client = "localhost";
138 #endif
139
140     if ((sd1 = rclient (server, "tcp", "smtp")) == NOTOK)
141         return RP_BHST;
142
143 #ifdef MPOP
144     if (sm_ispool) {
145         if (sm_rfp) {
146             alarm (SM_CLOS);
147             fclose (sm_rfp);
148             alarm (0);
149             sm_rfp = NULL;
150         }
151         if ((sm_wfp = fdopen (sd1, "w")) == NULL) {
152             unlink (sm_tmpfil);
153             close (sd1);
154             return sm_ierror ("unable to fdopen");
155         }
156 all_done: ;
157         sm_reply.text[sm_reply.length = 0] = NULL;
158         return (sm_reply.code = RP_OK);
159     }
160 #endif /* MPOP */
161
162     if ((sd2 = dup (sd1)) == NOTOK) {
163         close (sd1);
164         return sm_ierror ("unable to dup");
165     }
166
167     SIGNAL (SIGALRM, alrmser);
168     SIGNAL (SIGPIPE, SIG_IGN);
169
170     if ((sm_rfp = fdopen (sd1, "r")) == NULL
171             || (sm_wfp = fdopen (sd2, "w")) == NULL) {
172         close (sd1);
173         close (sd2);
174         sm_rfp = sm_wfp = NULL;
175         return sm_ierror ("unable to fdopen");
176     }
177
178     sm_alarmed = 0;
179     alarm (SM_OPEN);
180     result = smhear ();
181     alarm (0);
182
183     switch (result) {
184         case 220: 
185             break;
186
187         default: 
188             sm_end (NOTOK);
189             return RP_RPLY;
190     }
191
192     /*
193      * Give EHLO or HELO command
194      */
195     if (client && *client) {
196         doingEHLO = 1;
197         result = smtalk (SM_HELO, "EHLO %s", client);
198         doingEHLO = 0;
199
200         if (result >= 500 && result <= 599)
201             result = smtalk (SM_HELO, "HELO %s", client);
202
203         if (result != 250) {
204             sm_end (NOTOK);
205             return RP_RPLY;
206         }
207     }
208
209 send_options: ;
210     if (watch && EHLOset ("XVRB"))
211         smtalk (SM_HELO, "VERB on");
212     if (onex && EHLOset ("XONE"))
213         smtalk (SM_HELO, "ONEX");
214     if (queued && EHLOset ("XQUE"))
215         smtalk (SM_HELO, "QUED");
216
217     return RP_OK;
218 }
219
220
221 #ifdef MPOP
222 # define MAXARGS  1000
223 #endif /* MPOP */
224
225 static int
226 rclient (char *server, char *protocol, char *service)
227 {
228     int sd;
229     char response[BUFSIZ];
230 #ifdef MPOP
231     char *cp;
232 #endif /* MPOP */
233
234     if ((sd = client (server, protocol, service, FALSE, response, sizeof(response))) != NOTOK)
235         return sd;
236
237 #ifdef MPOP
238     if (!server && servers && (cp = strchr(servers, '/'))) {
239         char **ap;
240         char *arguments[MAXARGS];
241
242         smail_copyip (smail_brkstring (cp = getcpy (servers), " ", "\n"), arguments, MAXARGS);
243
244         for (ap = arguments; *ap; ap++)
245             if (**ap == '/') {
246                 char *dp;
247
248                 if ((dp = strrchr(*ap, '/')) && *++dp == NULL)
249                     *--dp = NULL;
250                 snprintf (sm_tmpfil, sizeof(sm_tmpfil), "%s/smtpXXXXXX", *ap);
251                 mktemp (sm_tmpfil);
252
253                 if ((sd = creat (sm_tmpfil, 0600)) != NOTOK) {
254                     sm_ispool = 1;
255                     break;
256                 }
257             }
258
259         free (cp);
260         if (sd != NOTOK)
261             return sd;
262     }
263 #endif /* MPOP */
264
265     sm_ierror ("%s", response);
266     return NOTOK;
267 }
268
269
270 int
271 sm_winit (int mode, char *from)
272 {
273     char *smtpcom;
274
275 #ifdef MPOP
276     if (sm_ispool && !sm_wfp) {
277         strlen (strcpy (sm_reply.text, "unable to create new spool file"));
278         sm_reply.code = NOTOK;
279         return RP_BHST;
280     }
281 #endif /* MPOP */
282
283     switch (mode) {
284         case S_MAIL:
285             smtpcom = "MAIL";
286             break;
287
288         case S_SEND:
289             smtpcom = "SEND";
290             break;
291
292         case S_SOML:
293             smtpcom = "SOML";
294             break;
295
296         case S_SAML:
297             smtpcom = "SAML";
298             break;
299     }
300
301     switch (smtalk (SM_MAIL, "%s FROM:<%s>", smtpcom, from)) {
302         case 250: 
303             sm_addrs = 0;
304             return RP_OK;
305
306         case 500: 
307         case 501: 
308         case 552: 
309             return RP_PARM;
310
311         default: 
312             return RP_RPLY;
313     }
314 }
315
316
317 int
318 sm_wadr (char *mbox, char *host, char *path)
319 {
320     switch (smtalk (SM_RCPT, host && *host ? "RCPT TO:<%s%s@%s>"
321                                            : "RCPT TO:<%s%s>",
322                              path ? path : "", mbox, host)) {
323         case 250: 
324         case 251: 
325             sm_addrs++;
326             return RP_OK;
327
328         case 451: 
329 #ifdef SENDMAILBUG
330             sm_addrs++;
331             return RP_OK;
332 #endif /* SENDMAILBUG */
333         case 421: 
334         case 450: 
335         case 452: 
336             return RP_NO;
337
338         case 500: 
339         case 501: 
340             return RP_PARM;
341
342         case 550: 
343         case 551: 
344         case 552: 
345         case 553: 
346             return RP_USER;
347
348         default: 
349             return RP_RPLY;
350     }
351 }
352
353
354 int
355 sm_waend (void)
356 {
357     switch (smtalk (SM_DATA, "DATA")) {
358         case 354: 
359             sm_nl = TRUE;
360             return RP_OK;
361
362         case 451: 
363 #ifdef SENDMAILBUG
364             sm_nl = TRUE;
365             return RP_OK;
366 #endif /* SENDMAILBUG */
367         case 421: 
368             return RP_NO;
369
370         case 500: 
371         case 501: 
372         case 503: 
373         case 554: 
374             return RP_NDEL;
375
376         default: 
377             return RP_RPLY;
378     }
379 }
380
381
382 int
383 sm_wtxt (char *buffer, int len)
384 {
385     int result;
386
387     sm_alarmed = 0;
388     alarm (SM_TEXT);
389     result = sm_wstream (buffer, len);
390     alarm (0);
391
392     return (result == NOTOK ? RP_BHST : RP_OK);
393 }
394
395
396 int
397 sm_wtend (void)
398 {
399     if (sm_wstream ((char *) NULL, 0) == NOTOK)
400         return RP_BHST;
401
402     switch (smtalk (SM_DOT + 3 * sm_addrs, ".")) {
403         case 250: 
404         case 251: 
405             return RP_OK;
406
407         case 451: 
408 #ifdef SENDMAILBUG
409             return RP_OK;
410 #endif /* SENDMAILBUG */
411         case 452: 
412         default: 
413             return RP_NO;
414
415         case 552: 
416         case 554: 
417             return RP_NDEL;
418     }
419 }
420
421
422 int
423 sm_end (int type)
424 {
425     int status;
426     struct smtp sm_note;
427
428     if (sm_rfp == NULL && sm_wfp == NULL)
429         return RP_OK;
430
431     switch (type) {
432         case OK: 
433             smtalk (SM_QUIT, "QUIT");
434             break;
435
436         case NOTOK: 
437             sm_note.code = sm_reply.code;
438             strncpy (sm_note.text, sm_reply.text, sm_note.length = sm_reply.length);/* fall */
439         case DONE: 
440             if (smtalk (SM_RSET, "RSET") == 250 && type == DONE)
441                 return RP_OK;
442             smtalk (SM_QUIT, "QUIT");
443             if (type == NOTOK) {
444                 sm_reply.code = sm_note.code;
445                 strncpy (sm_reply.text, sm_note.text, sm_reply.length = sm_note.length);
446             }
447             break;
448     }
449
450 #ifdef MPOP
451     if (sm_ispool) {
452         sm_ispool = 0;
453
454         if (sm_wfp) {
455             unlink (sm_tmpfil);
456             fclose (sm_wfp);
457             sm_wfp = NULL;
458         }
459     }
460 #endif /* MPOP */
461
462     if (sm_rfp != NULL) {
463         alarm (SM_CLOS);
464         fclose (sm_rfp);
465         alarm (0);
466     }
467     if (sm_wfp != NULL) {
468         alarm (SM_CLOS);
469         fclose (sm_wfp);
470         alarm (0);
471     }
472
473     status = 0;
474     sm_rfp = sm_wfp = NULL;
475     return (status ? RP_BHST : RP_OK);
476 }
477
478
479 #ifdef MPOP
480
481 int
482 sm_bulk (char *file)
483 {
484     int cc, i, j, k, result;
485     long pos;
486     char *dp, *bp, *cp, s;
487     char buffer[BUFSIZ], sender[BUFSIZ];
488     FILE *fp, *gp;
489
490     gp = NULL;
491     k = strlen (file) - sizeof(".bulk");
492     if ((fp = fopen (file, "r")) == NULL) {
493         int len;
494
495         snprintf (sm_reply.text, sizeof(sm_reply.text),
496                 "unable to read %s: ", file);
497         bp = sm_reply.text;
498         len = strlen (bp);
499         bp += len;
500         if ((s = strerror (errno)))
501             strncpy (bp, s, sizeof(sm_reply.text) - len);
502         else
503             snprintf (bp, sizeof(sm_reply.text) - len, "Error %d", errno);
504         sm_reply.length = strlen (sm_reply.text);
505         sm_reply.code = NOTOK;
506         return RP_BHST;
507     }
508     if (sm_debug) {
509         printf ("reading file %s\n", file);
510         fflush (stdout);
511     }
512
513     i = j = 0;
514     while (fgets (buffer, sizeof(buffer), fp)) {
515         if (j++ == 0)
516             strncpy (sender, buffer + sizeof("MAIL FROM:") - 1, sizeof(sender));
517         if (strcmp (buffer, "DATA\r\n") == 0) {
518             i = 1;
519             break;
520         }
521     }
522     if (i == 0) {
523         if (sm_debug) {
524             printf ("no DATA...\n");
525             fflush (stdout);
526         }
527 losing0:
528         snprintf (buffer, sizeof(buffer), "%s.bad", file);
529         rename (file, buffer);
530         if (gp) {
531             snprintf (buffer, sizeof(buffer), "%*.*sA.bulk", k, k, file);
532             unlink (buffer);
533             fclose (gp);
534         }
535         fclose (fp);
536         return RP_OK;
537     }
538     if (j < 3) {
539         if (sm_debug) {
540             printf ("no %srecipients...\n", j < 1 ? "sender or " : "");
541             fflush (stdout);
542         }
543         goto losing0;
544     }
545
546     if ((cp = malloc ((size_t) (cc = (pos = ftell (fp)) + 1))) == NULL) {
547         sm_reply.length = strlen (strcpy (sm_reply.text, "out of memory"));
548 losing1: ;
549         sm_reply.code = NOTOK;
550         fclose (fp);
551         return RP_BHST;
552     }
553     fseek (fp, 0L, SEEK_SET);
554     for (dp = cp, i = 0; i++ < j; dp += strlen (dp))
555         if (fgets (dp, cc - (dp - cp), fp) == NULL) {
556             sm_reply.length = strlen (strcpy (sm_reply.text, "premature eof"));
557 losing2:
558             free (cp);
559             goto losing1;
560         }
561     *dp = NULL;
562
563     for (dp = cp, i = cc - 1; i > 0; dp += cc, i -= cc)
564         if ((cc = write (fileno (sm_wfp), dp, i)) == NOTOK) {
565             int len;
566 losing3:
567             strcpy (sm_reply.text, "error writing to server: ",
568                 sizeof(sm_reply.text));
569             bp = sm_reply.text;
570             len = strlen (bp);
571             bp += len;
572             if ((s = strerror (errno)))
573                 strncpy (bp, s, sizeof(sm_reply.text) - len);
574             else
575                 snprintf (bp, sizeof(sm_reply.text) - len,
576                         "unknown error %d", errno);
577             sm_reply.length = strlen (sm_reply.text);
578             goto losing2;
579         }
580         else
581             if (sm_debug) {
582                 printf ("wrote %d octets to server\n", cc);
583                 fflush (stdout);
584             }
585
586     for (dp = cp, i = 0; i++ < j; dp = strchr(dp, '\n'), dp++) {
587         if (sm_debug) {
588             if (bp = strchr(dp, '\r'))
589                 *bp = NULL;
590             printf ("=> %s\n", dp);
591             fflush (stdout);
592             if (bp)
593                 *bp = '\r';
594         }
595
596         switch (smhear () + (i == 1 ? 1000 : i != j ? 2000 : 3000)) {
597             case 1000 + 250:
598                 sm_addrs = 0;
599                 result = RP_OK;
600                 break;
601
602             case 1000 + 500: 
603             case 1000 + 501: 
604             case 1000 + 552: 
605             case 2000 + 500: 
606             case 2000 + 501:
607                 result = RP_PARM;
608                 smtalk (SM_RSET, "RSET");
609                 free (cp);
610                 goto losing0;
611
612             case 2000 + 250:
613             case 2000 + 251:
614                 sm_addrs++;
615                 result = RP_OK;
616                 break;
617
618             case 2000 + 451: 
619 #ifdef SENDMAILBUG
620                 sm_addrs++;
621                 result = RP_OK;
622                 break;
623 #endif
624             case 2000 + 421: 
625             case 2000 + 450: 
626             case 2000 + 452: 
627                 result = RP_NO;
628                 goto bad_addr;
629
630             case 2000 + 550: 
631             case 2000 + 551: 
632             case 2000 + 552: 
633             case 2000 + 553: 
634                 result = RP_USER;
635 bad_addr:
636                 if (k <= 0 || strcmp (sender, "<>\r\n") == 0)
637                     break;
638                 if (gp == NULL) {
639                     int     l;
640                     snprintf (buffer, sizeof(buffer), "%*.*sA.bulk", k, k, file);
641                     if ((gp = fopen (buffer, "w+")) == NULL)
642                         goto bad_data;
643                     fprintf (gp, "MAIL FROM:<>\r\nRCPT TO:%sDATA\r\n", sender);
644                     l = strlen (sender);
645                     fprintf (gp,
646                              "To: %*.*s\r\nSubject: Invalid addresses (%s)\r\n",
647                              l - 4, l - 4, sender + 1, file);
648                     fprintf (gp, "Date: %s\r\nFrom: Postmaster@%s\r\n\r\n",
649                              dtimenow (0), LocalName ());
650                 }
651                 if (bp = strchr(dp, '\r'))
652                     *bp = NULL;
653                 fprintf (gp, "=>        %s\r\n", dp);
654                 if (bp)
655                     *bp = '\r';
656                 fprintf (gp, "<= %s\r\n", rp_string (result));
657                 fflush (gp);
658                 break;
659
660             case 3000 + 354: 
661 #ifdef SENDMAILBUG
662 ok_data:
663 #endif
664                 result = RP_OK;
665                 break;
666
667             case 3000 + 451: 
668 #ifdef SENDMAILBUG
669                 goto ok_data;
670 #endif
671             case 3000 + 421:
672                 result = RP_NO;
673 bad_data:
674                 smtalk (SM_RSET, "RSET");
675                 free (cp);
676                 if (gp) {
677                     snprintf (buffer, sizeof(buffer), "%*.*sA.bulk", k, k, file);
678                     unlink (buffer);
679                     fclose (gp);
680                 }
681                 fclose (fp);
682                 return result;
683
684             case 3000 + 500: 
685             case 3000 + 501: 
686             case 3000 + 503: 
687             case 3000 + 554: 
688                 smtalk (SM_RSET, "RSET");
689                 free (cp);
690                 goto no_dice;
691
692             default:
693                 result = RP_RPLY;
694                 goto bad_data;
695         }
696     }
697     free (cp);
698
699     {
700 #ifdef HAVE_ST_BLKSIZE
701         struct stat st;
702
703         if (fstat (fileno (sm_wfp), &st) == NOTOK || (cc = st.st_blksize) < BUFSIZ)
704             cc = BUFSIZ;
705 #else
706         cc = BUFSIZ;
707 #endif
708         if ((cp = malloc ((size_t) cc)) == NULL) {
709             smtalk (SM_RSET, "RSET");
710             sm_reply.length = strlen (strcpy (sm_reply.text, "out of memory"));
711             goto losing1;
712         }
713     }
714
715     fseek (fp, pos, SEEK_SET);
716     for (;;) {
717         int eof = 0;
718
719         for (dp = cp, i = cc; i > 0; dp += j, i -= j)
720             if ((j = fread (cp, sizeof(*cp), i, fp)) == OK) {
721                 if (ferror (fp)) {
722                     int len;
723
724                     snprintf (sm_reply.text, sizeof(sm_reply.text),
725                         "error reading %s: ", file);
726                     bp = sm_reply.text;
727                     len = strlen (bp);
728                     bp += len;
729                     if ((s = strerror (errno)))
730                         strncpy (bp, s, sizeof(sm_reply.text) - len);
731                     else
732                         snprintf (bp, sizeof(sm_reply.text) - len,
733                                 "unknown error %d", errno);
734                     sm_reply.length = strlen (sm_reply.text);
735                     goto losing2;
736                 }
737                 cc = dp - cp;
738                 eof = 1;
739                 break;
740             }
741
742         for (dp = cp, i = cc; i > 0; dp += j, i -= j)
743             if ((j = write (fileno (sm_wfp), dp, i)) == NOTOK)
744                 goto losing3;
745             else
746                 if (sm_debug) {
747                     printf ("wrote %d octets to server\n", j);
748                     fflush (stdout);
749                 }
750
751         if (eof)
752             break;
753     }
754     free (cp);
755
756     switch (smhear ()) {
757         case 250: 
758         case 251: 
759 #ifdef SENDMAILBUG
760 ok_dot:
761 #endif
762             result = RP_OK;
763             unlink (file);
764             break;
765
766         case 451: 
767 #ifdef SENDMAILBUG
768             goto ok_dot;
769 #endif
770         case 452: 
771         default: 
772             result = RP_NO;
773             if (gp) {
774                 snprintf (buffer, sizeof(buffer), "%*.*sA.bulk", k, k, file);
775                 unlink (buffer);
776                 fclose (gp);
777                 gp = NULL;
778             }
779             break;
780
781         case 552: 
782         case 554: 
783 no_dice:
784             result = RP_NDEL;
785             if (k <= 0 || strcmp (sender, "<>\r\n") == 0) {
786                 unlink (file);
787                 break;
788             }
789             if (gp) {
790                 fflush (gp);
791                 ftruncate (fileno (gp), 0L);
792                 fseek (gp, 0L, SEEK_SET);
793             }
794             else {
795                 snprintf (buffer, sizeof(buffer), "%*.*sA.bulk", k, k, file);
796                 if ((gp = fopen (buffer, "w")) == NULL)
797                     break;
798             }
799             fprintf (gp, "MAIL FROM:<>\r\nRCPT TO:%sDATA\r\n", sender);
800             i = strlen (sender);
801             fprintf (gp, "To: %*.*s\r\nSubject: Failed mail (%s)\r\n",
802                      i - 4, i - 4, sender + 1, file);
803             fprintf (gp, "Date: %s\r\nFrom: Postmaster@%s\r\n\r\n",
804                      dtimenow (0), LocalName ());
805             break;
806     }
807
808     if (gp) {
809         fputs ("\r\n------- Begin Returned message\r\n\r\n", gp);
810         fseek (fp, pos, SEEK_SET);
811         while (fgets (buffer, sizeof(buffer), fp)) {
812             if (buffer[0] == '-')
813                 fputs ("- ", gp);
814             if (strcmp (buffer, ".\r\n"))
815                 fputs (buffer, gp);
816         }
817         fputs ("\r\n------- End Returned Message\r\n\r\n.\r\n", gp);
818         fflush (gp);
819         if (!ferror (gp))
820             unlink (file);
821         fclose (gp);
822     }
823     fclose (fp);
824
825     return result;
826 }
827 #endif /* MPOP */
828
829
830 static int
831 sm_ierror (char *fmt, ...)
832 {
833     va_list ap;
834
835     va_start(ap, fmt);
836     vsnprintf (sm_reply.text, sizeof(sm_reply.text), fmt, ap);
837     va_end(ap);
838
839     sm_reply.length = strlen (sm_reply.text);
840     sm_reply.code = NOTOK;
841
842     return RP_BHST;
843 }
844
845
846 static int
847 smtalk (int time, char *fmt, ...)
848 {
849     va_list ap;
850     int result;
851     char buffer[BUFSIZ];
852
853     va_start(ap, fmt);
854     vsnprintf (buffer, sizeof(buffer), fmt, ap);
855     va_end(ap);
856
857     if (sm_debug) {
858         printf ("=> %s\n", buffer);
859         fflush (stdout);
860     }
861
862 #ifdef MPOP
863     if (sm_ispool) {
864         char    file[BUFSIZ];
865
866         if (strcmp (buffer, ".") == 0)
867             time = SM_DOT;
868         fprintf (sm_wfp, "%s\r\n", buffer);
869         switch (time) {
870             case SM_DOT:
871                 fflush (sm_wfp);
872                 if (ferror (sm_wfp))
873                     return sm_werror ();
874                 snprintf (file, sizeof(file), "%s%c.bulk", sm_tmpfil,
875                                 (char) (sm_ispool + 'a' - 1));
876                 if (rename (sm_tmpfil, file) == NOTOK) {
877                     int len;
878                     char *bp;
879
880                     snprintf (sm_reply.text, sizeof(sm_reply.text),
881                             "error renaming %s to %s: ", sm_tmpfil, file);
882                     bp = sm_reply.text;
883                     len = strlen (bp);
884                     bp += len;
885                     if ((s = strerror (errno)))
886                         strncpy (bp, s, sizeof(sm_reply.text) - len);
887                     else
888                         snprintf (bp, sizeof(sm_reply.text) - len,
889                                 "unknown error %d", errno);
890                     sm_reply.length = strlen (sm_reply.text);
891                     sm_reply.code = NOTOK;
892                     return RP_BHST;
893                 }
894                 fclose (sm_wfp);
895                 if (sm_wfp = fopen (sm_tmpfil, "w"))
896                     chmod (sm_tmpfil, 0600);
897                 sm_ispool++;
898                 /* and fall... */
899
900             case SM_MAIL:
901             case SM_RCPT:
902                 result = 250;
903                 break;
904
905             case SM_RSET:
906                 fflush (sm_wfp);
907                 ftruncate (fileno (sm_wfp), 0L);
908                 fseek (sm_wfp, 0L, SEEK_SET);
909                 result = 250;
910                 break;
911
912             case SM_DATA:
913                 result = 354;
914                 break;
915
916             case SM_QUIT:
917                 unlink (sm_tmpfil);
918                 sm_ispool = 0;
919                 result = 221;
920                 break;
921
922             default:
923                 result = 500;
924                 break;
925         }
926         if (sm_debug) {
927             printf ("<= %d\n", result);
928             fflush (stdout);
929         }
930
931         sm_reply.text[sm_reply.length = 0] = NULL;
932         return (sm_reply.code = result);
933     }
934 #endif /* MPOP */
935
936     sm_alarmed = 0;
937     alarm ((unsigned) time);
938     if ((result = sm_wrecord (buffer, strlen (buffer))) != NOTOK)
939         result = smhear ();
940     alarm (0);
941
942     return result;
943 }
944
945
946 /*
947  * write the buffer to the open SMTP channel
948  */
949
950 static int
951 sm_wrecord (char *buffer, int len)
952 {
953     if (sm_wfp == NULL)
954         return sm_werror ();
955
956     fwrite (buffer, sizeof(*buffer), len, sm_wfp);
957     fputs ("\r\n", sm_wfp);
958     fflush (sm_wfp);
959
960     return (ferror (sm_wfp) ? sm_werror () : OK);
961 }
962
963
964 static int
965 sm_wstream (char *buffer, int len)
966 {
967     char  *bp;
968     static char lc = '\0';
969
970     if (sm_wfp == NULL)
971         return sm_werror ();
972
973     if (buffer == NULL && len == 0) {
974         if (lc != '\n')
975             fputs ("\r\n", sm_wfp);
976         lc = '\0';
977         return (ferror (sm_wfp) ? sm_werror () : OK);
978     }
979
980     for (bp = buffer; len > 0; bp++, len--) {
981         switch (*bp) {
982             case '\n': 
983                 sm_nl = TRUE;
984                 fputc ('\r', sm_wfp);
985                 break;
986
987             case '.': 
988                 if (sm_nl)
989                     fputc ('.', sm_wfp);/* FALL THROUGH */
990             default: 
991                 sm_nl = FALSE;
992         }
993         fputc (*bp, sm_wfp);
994         if (ferror (sm_wfp))
995             return sm_werror ();
996     }
997
998     if (bp > buffer)
999         lc = *--bp;
1000     return (ferror (sm_wfp) ? sm_werror () : OK);
1001 }
1002
1003
1004 #ifdef _AIX
1005 /*
1006  * AIX by default will inline the strlen and strcpy commands by redefining
1007  * them as __strlen and __strcpy respectively.  This causes compile problems
1008  * with the #ifdef MPOP in the middle.  Should the #ifdef MPOP be removed,
1009  * remove these #undefs.
1010  */
1011 # undef strlen
1012 # undef strcpy
1013 #endif /* _AIX */
1014
1015 static int
1016 sm_werror (void)
1017 {
1018     sm_reply.length =
1019         strlen (strcpy (sm_reply.text, sm_wfp == NULL ? "no socket opened"
1020             : sm_alarmed ? "write to socket timed out"
1021 #ifdef MPOP
1022             : sm_ispool ? "error writing to spool file"
1023 #endif
1024             : "error writing to socket"));
1025
1026     return (sm_reply.code = NOTOK);
1027 }
1028
1029
1030 static int
1031 smhear (void)
1032 {
1033     int i, code, cont, bc, rc, more;
1034     char *bp, *rp;
1035     char **ehlo, buffer[BUFSIZ];
1036
1037     if (doingEHLO) {
1038         static int at_least_once = 0;
1039
1040         if (at_least_once) {
1041             char *ep;
1042
1043             for (ehlo = EHLOkeys; *ehlo; ehlo++) {
1044                 ep = *ehlo;
1045                 free (ep);
1046             }
1047         } else {
1048             at_least_once = 1;
1049         }
1050
1051         ehlo = EHLOkeys;
1052         *ehlo = NULL;
1053     }
1054
1055 again: ;
1056
1057     sm_reply.length = 0;
1058     sm_reply.text[0] = 0;
1059     rp = sm_reply.text;
1060     rc = sizeof(sm_reply.text) - 1;
1061
1062     for (more = FALSE; sm_rrecord (bp = buffer, &bc) != NOTOK;) {
1063         if (sm_debug) {
1064             printf ("<= %s\n", buffer);
1065             fflush (stdout);
1066         }
1067
1068         if (doingEHLO
1069                 && strncmp (buffer, "250", sizeof("250") - 1) == 0
1070                 && (buffer[3] == '-' || doingEHLO == 2)
1071                 && buffer[4]) {
1072             if (doingEHLO == 2) {
1073                 if ((*ehlo = malloc ((size_t) (strlen (buffer + 4) + 1)))) {
1074                     strcpy (*ehlo++, buffer + 4);
1075                     *ehlo = NULL;
1076                     if (ehlo >= EHLOkeys + MAXEHLO)
1077                         doingEHLO = 0;
1078                 }
1079                 else
1080                     doingEHLO = 0;
1081             }
1082             else
1083                 doingEHLO = 2;
1084         }
1085
1086         for (; bc > 0 && (!isascii (*bp) || !isdigit (*bp)); bp++, bc--)
1087             continue;
1088
1089         cont = FALSE;
1090         code = atoi (bp);
1091         bp += 3, bc -= 3;
1092         for (; bc > 0 && isspace (*bp); bp++, bc--)
1093             continue;
1094         if (bc > 0 && *bp == '-') {
1095             cont = TRUE;
1096             bp++, bc--;
1097             for (; bc > 0 && isspace (*bp); bp++, bc--)
1098                 continue;
1099         }
1100
1101         if (more) {
1102             if (code != sm_reply.code || cont)
1103                 continue;
1104             more = FALSE;
1105         } else {
1106             sm_reply.code = code;
1107             more = cont;
1108             if (bc <= 0) {
1109                 strncpy (buffer, sm_noreply, sizeof(buffer));
1110                 bp = buffer;
1111                 bc = strlen (sm_noreply);
1112             }
1113         }
1114
1115         if ((i = min (bc, rc)) > 0) {
1116             strncpy (rp, bp, i);
1117             rp += i, rc -= i;
1118             if (more && rc > strlen (sm_moreply) + 1) {
1119                 strcpy (sm_reply.text + rc, sm_moreply);
1120                 rc += strlen (sm_moreply);
1121             }
1122         }
1123         if (more)
1124             continue;
1125         if (sm_reply.code < 100) {
1126             if (sm_verbose) {
1127                 printf ("%s\n", sm_reply.text);
1128                 fflush (stdout);
1129             }
1130             goto again;
1131         }
1132
1133         sm_reply.length = rp - sm_reply.text;
1134         return sm_reply.code;
1135     }
1136     return NOTOK;
1137 }
1138
1139
1140 static int
1141 sm_rrecord (char *buffer, int *len)
1142 {
1143     if (sm_rfp == NULL)
1144         return sm_rerror ();
1145
1146     buffer[*len = 0] = 0;
1147
1148     fgets (buffer, BUFSIZ, sm_rfp);
1149     *len = strlen (buffer);
1150     if (ferror (sm_rfp) || feof (sm_rfp))
1151         return sm_rerror ();
1152     if (buffer[*len - 1] != '\n')
1153         while (getc (sm_rfp) != '\n' && !ferror (sm_rfp) && !feof (sm_rfp))
1154             continue;
1155     else
1156         if (buffer[*len - 2] == '\r')
1157             *len -= 1;
1158     buffer[*len - 1] = 0;
1159
1160     return OK;
1161 }
1162
1163
1164 static int
1165 sm_rerror (void)
1166 {
1167     sm_reply.length =
1168         strlen (strcpy (sm_reply.text, sm_rfp == NULL ? "no socket opened"
1169             : sm_alarmed ? "read from socket timed out"
1170             : feof (sm_rfp) ? "premature end-of-file on socket"
1171             : "error reading from socket"));
1172
1173     return (sm_reply.code = NOTOK);
1174 }
1175
1176
1177 static RETSIGTYPE
1178 alrmser (int i)
1179 {
1180 #ifndef RELIABLE_SIGNALS
1181     SIGNAL (SIGALRM, alrmser);
1182 #endif
1183
1184     sm_alarmed++;
1185     if (sm_debug) {
1186         printf ("timed out...\n");
1187         fflush (stdout);
1188     }
1189 }
1190
1191
1192 char *
1193 rp_string (int code)
1194 {
1195     char *text;
1196     static char buffer[BUFSIZ];
1197
1198     switch (sm_reply.code != NOTOK ? code : NOTOK) {
1199         case RP_AOK:
1200             text = "AOK";
1201             break;
1202
1203         case RP_MOK:
1204             text = "MOK";
1205             break;
1206
1207         case RP_OK: 
1208             text = "OK";
1209             break;
1210
1211         case RP_RPLY: 
1212             text = "RPLY";
1213             break;
1214
1215         case RP_BHST: 
1216         default: 
1217             text = "BHST";
1218             snprintf (buffer, sizeof(buffer), "[%s] %s", text, sm_reply.text);
1219             return buffer;
1220
1221         case RP_PARM: 
1222             text = "PARM";
1223             break;
1224
1225         case RP_NO: 
1226             text = "NO";
1227             break;
1228
1229         case RP_USER: 
1230             text = "USER";
1231             break;
1232
1233         case RP_NDEL: 
1234             text = "NDEL";
1235             break;
1236     }
1237
1238     snprintf (buffer, sizeof(buffer), "[%s] %3d %s",
1239                 text, sm_reply.code, sm_reply.text);
1240     return buffer;
1241 }
1242
1243
1244 #ifdef MPOP
1245
1246 static char *broken[MAXARGS + 1];
1247
1248 static char **
1249 smail_brkstring (char *strg, char *brksep, char *brkterm)
1250 {
1251     int bi;
1252     char c, *sp;
1253
1254     sp = strg;
1255
1256     for (bi = 0; bi < MAXARGS; bi++) {
1257         while (smail_brkany (c = *sp, brksep))
1258             *sp++ = 0;
1259         if (!c || smail_brkany (c, brkterm)) {
1260             *sp = 0;
1261             broken[bi] = 0;
1262             return broken;
1263         }
1264
1265         broken[bi] = sp;
1266         while ((c = *++sp) && !smail_brkany (c, brksep) && !smail_brkany (c, brkterm))
1267             continue;
1268     }
1269     broken[MAXARGS] = 0;
1270
1271     return broken;
1272 }
1273
1274
1275 /*
1276  * returns 1 if chr in strg, 0 otherwise
1277  */
1278 static int
1279 smail_brkany (char chr, char *strg)
1280 {
1281     char *sp;
1282  
1283     if (strg)
1284         for (sp = strg; *sp; sp++)
1285             if (chr == *sp)
1286                 return 1;
1287     return 0;
1288 }
1289
1290 /*
1291  * copy a string array and return pointer to end
1292  */
1293 char **
1294 smail_copyip (char **p, char **q, int len_q)
1295 {
1296     while (*p && --len_q > 0)
1297         *q++ = *p++;
1298
1299     *q = NULL;
1300  
1301     return q;
1302 }
1303
1304 #endif /* MPOP */
1305
1306
1307 static char *
1308 EHLOset (char *s)
1309 {
1310     size_t len;
1311     char *ep, **ehlo;
1312
1313     len = strlen (s);
1314
1315     for (ehlo = EHLOkeys; *ehlo; ehlo++) {
1316         ep = *ehlo;
1317         if (strncmp (ep, s, len) == 0) {
1318             for (ep += len; *ep == ' '; ep++)
1319                 continue;
1320             return ep;
1321         }
1322     }
1323
1324     return 0;
1325 }