]>
diplodocus.org Git - nmh/blob - mts/smtp/smtp.c
7808e1a70871454de78e9e306e4ad147f7383bdd
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>
18 * This module implements an interface to SendMail very similar
19 * to the MMDF mm_(3) routines. The sm_() routines herein talk
20 * SMTP to a sendmail process, mapping SMTP reply codes into
24 #define NBITS ((sizeof (int)) * 8)
26 /* Timeout in seconds for SMTP commands.
27 * Lore has it they must be distinct. */
28 #define SM_OPEN 300 /* Changed to 5 minutes to comply with a SHOULD in RFC 1123 */
31 #define SM_MAIL 301 /* changed to 5 minutes and a second (for uniqueness), see above */
32 #define SM_RCPT 302 /* see above */
33 #define SM_DATA 120 /* see above */
34 #define SM_DOT 600 /* see above */
37 static int sm_addrs
= 0;
38 static int sm_child
= NOTOK
;
39 static int sm_debug
= 0;
40 static int sm_nl
= TRUE
;
41 static int sm_verbose
= 0;
42 static netsec_context
*nsc
= NULL
;
44 static char *sm_noreply
= "No reply text given";
45 static char *sm_moreply
= "; ";
46 static struct smtp sm_reply
;
51 static char *EHLOkeys
[MAXEHLO
+ 1];
56 static int smtp_init (char *, char *, char *, int, int, int, int, const char *,
57 const char *, const char *, int);
58 static int sendmail_init (char *, int, int, int, int, const char *,
61 static int rclient (char *, char *, char **);
62 static int sm_ierror (const char *fmt
, ...);
63 static int sm_nerror (char *);
64 static int smtalk (int time
, char *fmt
, ...);
65 static int sm_wstream (char *, int);
66 static int smhear (void);
67 static char *EHLOset (char *);
68 static int sm_sasl_callback(enum sasl_message_type
, unsigned const char *,
69 unsigned int, unsigned char **, unsigned int *,
73 sm_init (char *client
, char *server
, char *port
, int watch
, int verbose
,
74 int debug
, int sasl
, const char *saslmech
, const char *user
,
75 const char *oauth_svc
, int tls
)
77 if (sm_mts
== MTS_SMTP
)
78 return smtp_init (client
, server
, port
, watch
, verbose
,
79 debug
, sasl
, saslmech
, user
, oauth_svc
, tls
);
81 return sendmail_init (client
, watch
, verbose
, debug
, sasl
,
86 smtp_init (char *client
, char *server
, char *port
, int watch
, int verbose
,
87 int debug
, int sasl
, const char *saslmech
, const char *user
,
88 const char *oauth_svc
, int tls
)
91 char *errstr
, *chosen_server
;
102 if (client
== NULL
|| *client
== '\0') {
106 client
= LocalName(1); /* no clientname -> LocalName */
110 * Last-ditch check just in case client still isn't set to anything
112 if (client
== NULL
|| *client
== '\0')
113 client
= "localhost";
119 netsec_set_userid(nsc
, user
);
121 if ((sd1
= rclient (server
, port
, &chosen_server
)) == NOTOK
)
124 SIGNAL (SIGPIPE
, SIG_IGN
);
126 netsec_set_fd(nsc
, sd1
, sd1
);
128 netsec_set_hostname(nsc
, chosen_server
);
131 netsec_set_snoop(nsc
, 1);
134 if (netsec_set_sasl_params(nsc
, "smtp", saslmech
, sm_sasl_callback
,
136 return sm_nerror(errstr
);
140 if (netsec_set_oauth_service(nsc
, oauth_svc
) != OK
)
141 return sm_ierror("OAuth2 not supported");
144 if (tls
& S_TLSENABLEMASK
) {
145 if (netsec_set_tls(nsc
, 1, tls
& S_NOVERIFY
, &errstr
) != OK
)
146 return sm_nerror(errstr
);
150 * If tls == S_INITTLS, that means that the user requested
151 * "initial" TLS, which happens right after the connection has
152 * opened. Do that negotiation now
155 if (tls
& S_INITTLS
) {
156 if (netsec_negotiate_tls(nsc
, &errstr
) != OK
) {
158 return sm_nerror(errstr
);
162 netsec_set_timeout(nsc
, SM_OPEN
);
175 * Give EHLO or HELO command
179 result
= smtalk (SM_HELO
, "EHLO %s", client
);
182 if (result
>= 500 && result
<= 599)
183 result
= smtalk (SM_HELO
, "HELO %s", client
);
191 * If the user requested TLS support, then try to do the STARTTLS command
192 * as part of the initial dialog. Assuming this works, we then need to
193 * restart the EHLO dialog after TLS negotiation is complete.
196 if (tls
& S_STARTTLS
) {
197 if (! EHLOset("STARTTLS")) {
199 return sm_ierror("SMTP server does not support TLS");
202 result
= smtalk(SM_HELO
, "STARTTLS");
210 * Okay, the other side should be waiting for us to start TLS
211 * negotiation. Oblige them.
214 if (netsec_negotiate_tls(nsc
, &errstr
) != OK
) {
216 return sm_nerror(errstr
);
220 result
= smtalk (SM_HELO
, "EHLO %s", client
);
230 * If the user asked for SASL, then check to see if the SMTP server
231 * supports it. Otherwise, error out (because the SMTP server
232 * might have been spoofed; we don't want to just silently not
238 if (! (server_mechs
= EHLOset("AUTH"))) {
240 return sm_ierror("SMTP server does not support SASL");
243 if (netsec_negotiate_sasl(nsc
, server_mechs
, &errstr
) != OK
) {
245 return sm_nerror(errstr
);
250 if (watch
&& EHLOset ("XVRB"))
251 smtalk (SM_HELO
, "VERB on");
257 sendmail_init (char *client
, int watch
, int verbose
, int debug
, int sasl
,
258 const char *saslmech
, const char *user
)
260 unsigned int i
, result
, vecp
;
262 char *vec
[15], *errstr
;
267 sm_verbose
= verbose
;
272 if (client
== NULL
|| *client
== '\0') {
276 client
= LocalName(1); /* no clientname -> LocalName */
279 * Last-ditch check just in case client still isn't set to anything
281 if (client
== NULL
|| *client
== '\0')
282 client
= "localhost";
288 netsec_set_userid(nsc
, user
);
290 netsec_set_hostname(nsc
, client
);
293 netsec_set_snoop(nsc
, 1);
296 if (netsec_set_sasl_params(nsc
, "smtp", saslmech
, sm_sasl_callback
,
298 return sm_nerror(errstr
);
301 if (pipe (pdi
) == NOTOK
)
302 return sm_ierror ("no pipes");
303 if (pipe (pdo
) == NOTOK
) {
306 return sm_ierror ("no pipes");
309 for (i
= 0; (sm_child
= fork ()) == NOTOK
&& i
< 5; i
++)
318 return sm_ierror ("unable to fork");
321 if (pdo
[0] != fileno (stdin
))
322 dup2 (pdo
[0], fileno (stdin
));
323 if (pdi
[1] != fileno (stdout
))
324 dup2 (pdi
[1], fileno (stdout
));
325 if (pdi
[1] != fileno (stderr
))
326 dup2 (pdi
[1], fileno (stderr
));
327 for (i
= fileno (stderr
) + 1; i
< NBITS
; i
++)
331 vec
[vecp
++] = r1bindex (sendmail
, '/');
333 vec
[vecp
++] = watch
? "-odi" : "-odb";
334 vec
[vecp
++] = "-oem";
340 execvp (sendmail
, vec
);
341 fprintf (stderr
, "unable to exec ");
343 _exit (-1); /* NOTREACHED */
346 SIGNAL (SIGPIPE
, SIG_IGN
);
351 netsec_set_fd(nsc
, pdi
[i
], pdo
[1]);
352 netsec_set_timeout(nsc
, SM_OPEN
);
364 result
= smtalk (SM_HELO
, "EHLO %s", client
);
367 if (500 <= result
&& result
<= 599)
368 result
= smtalk (SM_HELO
, "HELO %s", client
);
380 * If the user asked for SASL, then check to see if the SMTP server
381 * supports it. Otherwise, error out (because the SMTP server
382 * might have been spoofed; we don't want to just silently not
388 if (! (server_mechs
= EHLOset("AUTH"))) {
390 return sm_ierror("SMTP server does not support SASL");
392 if (netsec_negotiate_sasl(nsc
, server_mechs
, &errstr
) != OK
) {
394 return sm_nerror(errstr
);
399 smtalk (SM_HELO
, "VERB on");
406 rclient (char *server
, char *service
, char **chosen_server
)
409 char response
[BUFSIZ
];
414 *chosen_server
= server
;
416 if ((sd
= client (server
, service
, response
, sizeof(response
),
420 sm_ierror ("%s", response
);
425 sm_winit (char *from
, int smtputf8
, int eightbit
)
427 const char *mail_parameters
= "";
430 /* Just for information, if an attempt is made to send to an 8-bit
431 address without specifying SMTPUTF8, Gmail responds with
432 555 5.5.2 Syntax error.
433 Gmail doesn't require the 8BITMIME, but RFC 6531 Sec. 1.2 does. */
434 if (EHLOset ("8BITMIME") && EHLOset ("SMTPUTF8")) {
435 mail_parameters
= " BODY=8BITMIME SMTPUTF8";
437 inform("SMTP server does not support %s, not sending.\n"
438 "Rebuild message with 7-bit headers, WITHOUT -headerencoding utf-8.",
439 EHLOset ("SMTPUTF8") ? "8BITMIME" : "SMTPUTF8");
443 } else if (eightbit
) {
444 /* Comply with RFC 6152, for messages that have any 8-bit characters
446 if (EHLOset ("8BITMIME")) {
447 mail_parameters
= " BODY=8BITMIME";
449 inform("SMTP server does not support 8BITMIME, not sending.\n"
450 "Suggest encoding message for 7-bit transport by setting your\n"
451 "locale to C, and/or specifying *b64 in mhbuild directives.");
457 switch (smtalk (SM_MAIL
, "MAIL FROM:<%s>%s", from
, mail_parameters
)) {
474 sm_wadr (char *mbox
, char *host
, char *path
)
476 switch (smtalk (SM_RCPT
, host
&& *host
? "RCPT TO:<%s%s@%s>"
478 FENDNULL(path
), mbox
, host
)) {
509 switch (smtalk (SM_DATA
, "DATA")) {
531 sm_wtxt (char *buffer
, int len
)
535 result
= sm_wstream (buffer
, len
);
537 return (result
== NOTOK
? RP_BHST
: RP_OK
);
544 if (sm_wstream ((char *) NULL
, 0) == NOTOK
)
547 switch (smtalk (SM_DOT
+ 3 * sm_addrs
, ".")) {
570 if (sm_mts
== MTS_SENDMAIL_SMTP
) {
586 smtalk (SM_QUIT
, "QUIT");
590 sm_note
.code
= sm_reply
.code
;
591 sm_note
.length
= sm_reply
.length
;
592 memcpy (sm_note
.text
, sm_reply
.text
, sm_reply
.length
+ 1);
595 if (smtalk (SM_RSET
, "RSET") == 250 && type
== DONE
)
597 if (sm_mts
== MTS_SMTP
)
598 smtalk (SM_QUIT
, "QUIT");
600 /* The SIGPIPE block replaces old calls to discard ().
601 We're not sure what the discard () calls were for,
602 maybe to prevent deadlock on old systems. In any
603 case, blocking SIGPIPE should be harmless.
604 Because the file handles are closed below, leave it
608 sigaddset (&set
, SIGPIPE
);
609 sigprocmask (SIG_BLOCK
, &set
, &oset
);
611 kill (sm_child
, SIGKILL
);
615 sm_reply
.code
= sm_note
.code
;
616 sm_reply
.length
= sm_note
.length
;
617 memcpy (sm_reply
.text
, sm_note
.text
, sm_note
.length
+ 1);
623 netsec_shutdown(nsc
);
627 if (sm_mts
== MTS_SMTP
) {
629 } else if (sm_child
!= NOTOK
) {
630 status
= pidwait (sm_child
, OK
);
636 return (status
? RP_BHST
: RP_OK
);
641 sm_ierror (const char *fmt
, ...)
646 vsnprintf (sm_reply
.text
, sizeof(sm_reply
.text
), fmt
, ap
);
649 sm_reply
.length
= strlen (sm_reply
.text
);
650 sm_reply
.code
= NOTOK
;
656 * Like sm_ierror, but assume it's an allocated error string we need to free.
660 sm_nerror (char *str
)
662 strncpy(sm_reply
.text
, str
, sizeof(sm_reply
.text
));
663 sm_reply
.text
[sizeof(sm_reply
.text
) - 1] = '\0';
664 sm_reply
.length
= strlen(sm_reply
.text
);
665 sm_reply
.code
= NOTOK
;
672 smtalk (int time
, char *fmt
, ...)
679 result
= netsec_vprintf (nsc
, &errstr
, fmt
, ap
);
683 return sm_nerror(errstr
);
685 if (netsec_printf (nsc
, &errstr
, "\r\n") != OK
)
686 return sm_nerror(errstr
);
688 if (netsec_flush (nsc
, &errstr
) != OK
)
689 return sm_nerror(errstr
);
691 netsec_set_timeout(nsc
, time
);
698 sm_wstream (char *buffer
, int len
)
701 static char lc
= '\0';
705 sm_ierror("No socket opened");
709 if (buffer
== NULL
&& len
== 0) {
712 rc
= netsec_write(nsc
, "\r\n", 2, &errstr
);
719 for (bp
= buffer
; bp
&& len
> 0; bp
++, len
--) {
723 if (netsec_write(nsc
, "\r", 1, &errstr
) != OK
) {
731 if (netsec_write(nsc
, ".", 1, &errstr
) != OK
) {
740 if (netsec_write(nsc
, bp
, 1, &errstr
) != OK
) {
755 int i
, code
, cont
, more
;
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
);