]>
diplodocus.org Git - nmh/blob - mts/smtp/smtp.c
1 /* smtp.c -- nmh SMTP interface
3 * This code is Copyright (c) 2002, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
11 #include <h/signals.h>
15 #include <sys/socket.h>
16 #include "sbr/base64.h"
19 * This module implements an interface to SendMail very similar
20 * to the MMDF mm_(3) routines. The sm_() routines herein talk
21 * SMTP to a sendmail process, mapping SMTP reply codes into
25 #define NBITS ((sizeof (int)) * 8)
27 /* Timeout in seconds for SMTP commands.
28 * Lore has it they must be distinct. */
29 #define SM_OPEN 300 /* Changed to 5 minutes to comply with a SHOULD in RFC 1123 */
32 #define SM_MAIL 301 /* changed to 5 minutes and a second (for uniqueness), see above */
33 #define SM_RCPT 302 /* see above */
34 #define SM_DATA 120 /* see above */
35 #define SM_DOT 600 /* see above */
38 static int sm_addrs
= 0;
39 static int sm_child
= NOTOK
;
40 static int sm_debug
= 0;
41 static bool sm_nl
= true;
42 static int sm_verbose
= 0;
43 static netsec_context
*nsc
= NULL
;
45 static char *sm_noreply
= "No reply text given";
46 static char *sm_moreply
= "; ";
47 static struct smtp sm_reply
;
52 static char *EHLOkeys
[MAXEHLO
+ 1];
57 static int smtp_init (char *, char *, char *, int, int, int, int, const char *,
58 const char *, const char *, int);
59 static int sendmail_init (char *, int, int, int, int, const char *,
62 static int rclient (char *, char *, char **);
63 static int sm_ierror (const char *fmt
, ...) CHECK_PRINTF(1, 2);
64 static int sm_nerror (char *);
65 static int smtalk (int time
, char *fmt
, ...) CHECK_PRINTF(2, 3);
66 static int sm_wstream (char *, int);
67 static int smhear (void);
68 static char *EHLOset (char *) PURE
;
69 static int sm_sasl_callback(enum sasl_message_type
, unsigned const char *,
70 unsigned int, unsigned char **, unsigned int *,
74 sm_init (char *client
, char *server
, char *port
, int watch
, int verbose
,
75 int debug
, int sasl
, const char *saslmech
, const char *user
,
76 const char *oauth_svc
, int tls
)
78 if (sm_mts
== MTS_SMTP
)
79 return smtp_init (client
, server
, port
, watch
, verbose
,
80 debug
, sasl
, saslmech
, user
, oauth_svc
, tls
);
82 return sendmail_init (client
, watch
, verbose
, debug
, sasl
,
87 smtp_init (char *client
, char *server
, char *port
, int watch
, int verbose
,
88 int debug
, int sasl
, const char *saslmech
, const char *user
,
89 const char *oauth_svc
, int tls
)
92 char *errstr
, *chosen_server
;
103 if (client
== NULL
|| *client
== '\0') {
107 client
= LocalName(1); /* no clientname -> LocalName */
111 * Last-ditch check just in case client still isn't set to anything
113 if (client
== NULL
|| *client
== '\0')
114 client
= "localhost";
120 netsec_set_userid(nsc
, user
);
122 if ((sd1
= rclient (server
, port
, &chosen_server
)) == NOTOK
)
125 SIGNAL (SIGPIPE
, SIG_IGN
);
127 netsec_set_fd(nsc
, sd1
, sd1
);
129 netsec_set_hostname(nsc
, chosen_server
);
132 netsec_set_snoop(nsc
, 1);
135 if (netsec_set_sasl_params(nsc
, "smtp", saslmech
, sm_sasl_callback
,
137 return sm_nerror(errstr
);
141 if (netsec_set_oauth_service(nsc
, oauth_svc
) != OK
)
142 return sm_ierror("OAuth2 not supported");
145 if (tls
& S_TLSENABLEMASK
) {
146 if (netsec_set_tls(nsc
, 1, tls
& S_NOVERIFY
, &errstr
) != OK
)
147 return sm_nerror(errstr
);
151 * If tls == S_INITTLS, that means that the user requested
152 * "initial" TLS, which happens right after the connection has
153 * opened. Do that negotiation now
156 if (tls
& S_INITTLS
) {
157 if (netsec_negotiate_tls(nsc
, &errstr
) != OK
) {
159 return sm_nerror(errstr
);
163 netsec_set_timeout(nsc
, SM_OPEN
);
176 * Give EHLO or HELO command
180 result
= smtalk (SM_HELO
, "EHLO %s", client
);
183 if (result
>= 500 && result
<= 599)
184 result
= smtalk (SM_HELO
, "HELO %s", client
);
192 * If the user requested TLS support, then try to do the STARTTLS command
193 * as part of the initial dialog. Assuming this works, we then need to
194 * restart the EHLO dialog after TLS negotiation is complete.
197 if (tls
& S_STARTTLS
) {
198 if (! EHLOset("STARTTLS")) {
200 return sm_ierror("SMTP server does not support TLS");
203 result
= smtalk(SM_HELO
, "STARTTLS");
211 * Okay, the other side should be waiting for us to start TLS
212 * negotiation. Oblige them.
215 if (netsec_negotiate_tls(nsc
, &errstr
) != OK
) {
217 return sm_nerror(errstr
);
221 result
= smtalk (SM_HELO
, "EHLO %s", client
);
231 * If the user asked for SASL, then check to see if the SMTP server
232 * supports it. Otherwise, error out (because the SMTP server
233 * might have been spoofed; we don't want to just silently not
239 if (! (server_mechs
= EHLOset("AUTH"))) {
241 return sm_ierror("SMTP server does not support SASL");
244 if (netsec_negotiate_sasl(nsc
, server_mechs
, &errstr
) != OK
) {
246 return sm_nerror(errstr
);
251 if (watch
&& EHLOset ("XVRB"))
252 smtalk (SM_HELO
, "VERB on");
258 sendmail_init (char *client
, int watch
, int verbose
, int debug
, int sasl
,
259 const char *saslmech
, const char *user
)
261 unsigned int i
, result
, vecp
;
263 char *vec
[15], *errstr
;
268 sm_verbose
= verbose
;
273 if (client
== NULL
|| *client
== '\0') {
277 client
= LocalName(1); /* no clientname -> LocalName */
280 * Last-ditch check just in case client still isn't set to anything
282 if (client
== NULL
|| *client
== '\0')
283 client
= "localhost";
289 netsec_set_userid(nsc
, user
);
291 netsec_set_hostname(nsc
, client
);
294 netsec_set_snoop(nsc
, 1);
297 if (netsec_set_sasl_params(nsc
, "smtp", saslmech
, sm_sasl_callback
,
299 return sm_nerror(errstr
);
302 if (pipe (pdi
) == NOTOK
)
303 return sm_ierror ("no pipes");
304 if (pipe (pdo
) == NOTOK
) {
307 return sm_ierror ("no pipes");
317 return sm_ierror ("unable to fork");
320 if (pdo
[0] != fileno (stdin
))
321 dup2 (pdo
[0], fileno (stdin
));
322 if (pdi
[1] != fileno (stdout
))
323 dup2 (pdi
[1], fileno (stdout
));
324 if (pdi
[1] != fileno (stderr
))
325 dup2 (pdi
[1], fileno (stderr
));
326 for (i
= fileno (stderr
) + 1; i
< NBITS
; i
++)
330 vec
[vecp
++] = r1bindex (sendmail
, '/');
332 vec
[vecp
++] = watch
? "-odi" : "-odb";
333 vec
[vecp
++] = "-oem";
339 execvp (sendmail
, vec
);
340 fprintf (stderr
, "unable to exec ");
342 _exit (-1); /* NOTREACHED */
345 SIGNAL (SIGPIPE
, SIG_IGN
);
350 netsec_set_fd(nsc
, pdi
[0], pdo
[1]);
351 netsec_set_timeout(nsc
, SM_OPEN
);
363 result
= smtalk (SM_HELO
, "EHLO %s", client
);
366 if (500 <= result
&& result
<= 599)
367 result
= smtalk (SM_HELO
, "HELO %s", client
);
379 * If the user asked for SASL, then check to see if the SMTP server
380 * supports it. Otherwise, error out (because the SMTP server
381 * might have been spoofed; we don't want to just silently not
387 if (! (server_mechs
= EHLOset("AUTH"))) {
389 return sm_ierror("SMTP server does not support SASL");
391 if (netsec_negotiate_sasl(nsc
, server_mechs
, &errstr
) != OK
) {
393 return sm_nerror(errstr
);
398 smtalk (SM_HELO
, "VERB on");
405 rclient (char *server
, char *service
, char **chosen_server
)
408 char response
[BUFSIZ
];
413 *chosen_server
= server
;
415 if ((sd
= client (server
, service
, response
, sizeof(response
),
419 sm_ierror ("%s", response
);
424 sm_winit (char *from
, int smtputf8
, int eightbit
)
426 const char *mail_parameters
= "";
429 /* Just for information, if an attempt is made to send to an 8-bit
430 address without specifying SMTPUTF8, Gmail responds with
431 555 5.5.2 Syntax error.
432 Gmail doesn't require the 8BITMIME, but RFC 6531 Sec. 1.2 does. */
433 if (EHLOset ("8BITMIME") && EHLOset ("SMTPUTF8")) {
434 mail_parameters
= " BODY=8BITMIME SMTPUTF8";
436 inform("SMTP server does not support %s, not sending.\n"
437 "Rebuild message with 7-bit headers, WITHOUT -headerencoding utf-8.",
438 EHLOset ("SMTPUTF8") ? "8BITMIME" : "SMTPUTF8");
442 } else if (eightbit
) {
443 /* Comply with RFC 6152, for messages that have any 8-bit characters
445 if (EHLOset ("8BITMIME")) {
446 mail_parameters
= " BODY=8BITMIME";
448 inform("SMTP server does not support 8BITMIME, not sending.\n"
449 "Suggest encoding message for 7-bit transport by setting your\n"
450 "locale to C, and/or specifying *b64 in mhbuild directives.");
456 switch (smtalk (SM_MAIL
, "MAIL FROM:<%s>%s", from
, mail_parameters
)) {
473 sm_wadr (char *mbox
, char *host
, char *path
)
475 switch (smtalk (SM_RCPT
, host
&& *host
? "RCPT TO:<%s%s@%s>"
477 FENDNULL(path
), mbox
, host
)) {
508 switch (smtalk (SM_DATA
, "DATA")) {
530 sm_wtxt (char *buffer
, int len
)
534 result
= sm_wstream (buffer
, len
);
536 return result
== NOTOK
? RP_BHST
: RP_OK
;
543 if (sm_wstream(NULL
, 0) == NOTOK
)
546 switch (smtalk (SM_DOT
+ 3 * sm_addrs
, ".")) {
569 if (sm_mts
== MTS_SENDMAIL_SMTP
) {
585 smtalk (SM_QUIT
, "QUIT");
589 sm_note
.code
= sm_reply
.code
;
590 sm_note
.length
= sm_reply
.length
;
591 memcpy (sm_note
.text
, sm_reply
.text
, sm_reply
.length
+ 1);
594 if (smtalk (SM_RSET
, "RSET") == 250 && type
== DONE
)
596 if (sm_mts
== MTS_SMTP
)
597 smtalk (SM_QUIT
, "QUIT");
599 /* The SIGPIPE block replaces old calls to discard ().
600 We're not sure what the discard () calls were for,
601 maybe to prevent deadlock on old systems. In any
602 case, blocking SIGPIPE should be harmless.
603 Because the file handles are closed below, leave it
607 sigaddset (&set
, SIGPIPE
);
608 sigprocmask (SIG_BLOCK
, &set
, &oset
);
610 kill (sm_child
, SIGKILL
);
614 sm_reply
.code
= sm_note
.code
;
615 sm_reply
.length
= sm_note
.length
;
616 memcpy (sm_reply
.text
, sm_note
.text
, sm_note
.length
+ 1);
622 netsec_shutdown(nsc
);
626 if (sm_mts
== MTS_SMTP
) {
628 } else if (sm_child
!= NOTOK
) {
629 status
= pidwait (sm_child
, OK
);
635 return status
? RP_BHST
: RP_OK
;
640 sm_ierror (const char *fmt
, ...)
645 vsnprintf (sm_reply
.text
, sizeof(sm_reply
.text
), fmt
, ap
);
648 sm_reply
.length
= strlen (sm_reply
.text
);
649 sm_reply
.code
= NOTOK
;
655 * Like sm_ierror, but assume it's an allocated error string we need to free.
659 sm_nerror (char *str
)
661 strncpy(sm_reply
.text
, str
, sizeof(sm_reply
.text
));
662 sm_reply
.text
[sizeof(sm_reply
.text
) - 1] = '\0';
663 sm_reply
.length
= strlen(sm_reply
.text
);
664 sm_reply
.code
= NOTOK
;
671 smtalk (int time
, char *fmt
, ...)
678 result
= netsec_vprintf (nsc
, &errstr
, fmt
, ap
);
682 return sm_nerror(errstr
);
684 if (netsec_printf (nsc
, &errstr
, "\r\n") != OK
)
685 return sm_nerror(errstr
);
687 if (netsec_flush (nsc
, &errstr
) != OK
)
688 return sm_nerror(errstr
);
690 netsec_set_timeout(nsc
, time
);
697 sm_wstream (char *buffer
, int len
)
700 static char lc
= '\0';
704 sm_ierror("No socket opened");
708 if (buffer
== NULL
&& len
== 0) {
711 rc
= netsec_write(nsc
, "\r\n", 2, &errstr
);
718 for (bp
= buffer
; bp
&& len
> 0; bp
++, len
--) {
722 if (netsec_write(nsc
, "\r", 1, &errstr
) != OK
) {
730 if (netsec_write(nsc
, ".", 1, &errstr
) != OK
) {
739 if (netsec_write(nsc
, bp
, 1, &errstr
) != OK
) {
760 char **ehlo
= EHLOkeys
, *buffer
;
763 static int at_least_once
= 0;
768 for (ehlo
= EHLOkeys
; *ehlo
; ehlo
++) {
783 sm_reply
.text
[0] = 0;
785 rc
= sizeof(sm_reply
.text
) - 1;
787 for (more
= false; (buffer
= netsec_readline(nsc
, &buflen
,
788 &errstr
)) != NULL
; ) {
791 && has_prefix(buffer
, "250")
792 && (buffer
[3] == '-' || doingEHLO
== 2)
794 if (doingEHLO
== 2) {
795 if ((*ehlo
= malloc ((size_t) (strlen (buffer
+ 4) + 1)))) {
796 strcpy (*ehlo
++, buffer
+ 4);
798 if (ehlo
>= EHLOkeys
+ MAXEHLO
)
808 bp
= (unsigned char *) buffer
;
810 for (; buflen
> 0 && (!isascii (*bp
) || !isdigit (*bp
)); bp
++, buflen
--)
814 code
= atoi ((char *) bp
);
815 bp
+= 3, buflen
-= 3;
816 for (; buflen
> 0 && isspace (*bp
); bp
++, buflen
--)
818 if (buflen
> 0 && *bp
== '-') {
821 for (; buflen
> 0 && isspace (*bp
); bp
++, buflen
--)
826 if (code
!= sm_reply
.code
|| cont
)
830 sm_reply
.code
= code
;
833 bp
= (unsigned char *) sm_noreply
;
834 buflen
= strlen (sm_noreply
);
838 if ((i
= min (buflen
, rc
)) > 0) {
842 i
= strlen(sm_moreply
);
843 if (more
&& (int) rc
> i
+ 1) {
844 memcpy (rp
, sm_moreply
, i
); /* safe because of check in if() */
851 if (sm_reply
.code
< 100) {
859 sm_reply
.length
= rp
- sm_reply
.text
;
860 sm_reply
.text
[sm_reply
.length
] = 0;
861 return sm_reply
.code
;
873 /* The additional space is to avoid warning from gcc -Wformat-truncation. */
874 static char buffer
[BUFSIZ
+ 19];
876 switch (sm_reply
.code
!= NOTOK
? code
: NOTOK
) {
896 snprintf (buffer
, sizeof(buffer
), "[%s] %s", text
, sm_reply
.text
);
916 snprintf (buffer
, sizeof(buffer
), "[%s] %3d %s",
917 text
, sm_reply
.code
, sm_reply
.text
);
929 for (ehlo
= EHLOkeys
; *ehlo
; ehlo
++) {
931 if (has_prefix(ep
, s
)) {
932 for (ep
+= len
; *ep
== ' '; ep
++)
942 * Our SASL callback; we are either given SASL tokens to generate network
943 * protocols messages for, or we decode incoming protocol messages and
944 * convert them to binary SASL tokens to pass up into the SASL library.
948 sm_sasl_callback(enum sasl_message_type mtype
, unsigned const char *indata
,
949 unsigned int indatalen
, unsigned char **outdata
,
950 unsigned int *outdatalen
, char **errstr
)
957 case NETSEC_SASL_START
:
959 * Generate an AUTH message; if we were given an input token
960 * then generate a an AUTH message that includes the initial
964 mech
= netsec_get_sasl_mechanism(nsc
);
969 b64data
= mh_xmalloc(BASE64SIZE(indatalen
));
970 writeBase64raw(indata
, indatalen
, (unsigned char *) b64data
);
972 netsec_set_snoop_callback(nsc
, netsec_b64_snoop_decoder
,
974 snoopoffset
= 6 + strlen(mech
);
975 rc
= netsec_printf(nsc
, errstr
, "AUTH %s %s\r\n", mech
, b64data
);
977 netsec_set_snoop_callback(nsc
, NULL
, NULL
);
979 rc
= netsec_printf(nsc
, errstr
, "AUTH %s\r\n", mech
);
985 if (netsec_flush(nsc
, errstr
) != OK
)
990 case NETSEC_SASL_READ
:
992 * Read in a line that should contain a 334 response code, followed
993 * by base64 response data.
996 netsec_set_snoop_callback(nsc
, netsec_b64_snoop_decoder
, &snoopoffset
);
998 line
= netsec_readline(nsc
, &len
, errstr
);
999 netsec_set_snoop_callback(nsc
, NULL
, NULL
);
1005 netsec_err(errstr
, "Invalid format for SASL response");
1009 if (!has_prefix(line
, "334 ")) {
1010 netsec_err(errstr
, "Improper SASL protocol response: %s", line
);
1018 rc
= decodeBase64(line
+ 4, outdata
, &len
, 0, NULL
);
1020 netsec_err(errstr
, "Unable to decode base64 response");
1027 case NETSEC_SASL_WRITE
:
1029 * The output encoding is pretty simple, so this is easy.
1031 if (indatalen
== 0) {
1032 rc
= netsec_printf(nsc
, errstr
, "\r\n");
1034 unsigned char *b64data
;
1035 b64data
= mh_xmalloc(BASE64SIZE(indatalen
));
1036 writeBase64raw(indata
, indatalen
, b64data
);
1037 netsec_set_snoop_callback(nsc
, netsec_b64_snoop_decoder
, NULL
);
1038 rc
= netsec_printf(nsc
, errstr
, "%s\r\n", b64data
);
1039 netsec_set_snoop_callback(nsc
, NULL
, NULL
);
1046 if (netsec_flush(nsc
, errstr
) != OK
)
1050 case NETSEC_SASL_FINISH
:
1052 * Finish the protocol; we're looking for a 235 message.
1054 line
= netsec_readline(nsc
, &len
, errstr
);
1058 if (!has_prefix(line
, "235 ")) {
1060 netsec_err(errstr
, "Authentication failed: %s", line
+ 4);
1062 netsec_err(errstr
, "Authentication failed: %s", line
);
1067 case NETSEC_SASL_CANCEL
:
1069 * Cancel the SASL exchange; this is done by sending a single "*".
1071 rc
= netsec_printf(nsc
, errstr
, "*\r\n");
1073 rc
= netsec_flush(nsc
, errstr
);