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