make-config.in: complete path (leftover of [807f64e2], 2015-12-26!)
[s-mailx.git] / smtp.c
blob37a6ca5e3dd4b58624fe67b3265bc32f8af7fd02
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ SMTP client.
3 *@ TODO - use initial responses to save a round-trip (RFC 4954)
4 *@ TODO - more (verbose) understanding+rection upon STATUS CODES
6 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
7 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
8 * SPDX-License-Identifier: BSD-4-Clause
9 */
11 * Copyright (c) 2000
12 * Gunnar Ritter. All rights reserved.
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions
16 * are met:
17 * 1. Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 * 2. Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
22 * 3. All advertising materials mentioning features or use of this software
23 * must display the following acknowledgement:
24 * This product includes software developed by Gunnar Ritter
25 * and his contributors.
26 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
27 * may be used to endorse or promote products derived from this software
28 * without specific prior written permission.
30 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
31 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
34 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 * SUCH DAMAGE.
42 #undef n_FILE
43 #define n_FILE smtp
45 #ifndef HAVE_AMALGAMATION
46 # include "nail.h"
47 #endif
49 EMPTY_FILE()
50 #ifdef HAVE_SMTP
51 #include <sys/socket.h>
53 struct smtp_line {
54 char *dat; /* Actual data */
55 size_t datlen;
56 char *buf; /* Memory buffer */
57 size_t bufsize;
60 static sigjmp_buf _smtp_jmp;
62 static void _smtp_onterm(int signo);
64 /* Get the SMTP server's answer, expecting val */
65 static int _smtp_read(struct sock *sp, struct smtp_line *slp, int val,
66 bool_t ign_eof, bool_t want_dat);
68 /* Talk to a SMTP server */
69 static bool_t _smtp_talk(struct sock *sp, struct sendbundle *sbp);
71 #ifdef HAVE_GSSAPI
72 static bool_t _smtp_gssapi(struct sock *sp, struct sendbundle *sbp,
73 struct smtp_line *slp);
74 #endif
76 static void
77 _smtp_onterm(int signo)
79 NYD_X; /* Signal handler */
80 n_UNUSED(signo);
81 siglongjmp(_smtp_jmp, 1);
84 static int
85 _smtp_read(struct sock *sp, struct smtp_line *slp, int val,
86 bool_t ign_eof, bool_t want_dat)
88 int rv, len;
89 char *cp;
90 NYD_ENTER;
92 do {
93 if ((len = sgetline(&slp->buf, &slp->bufsize, NULL, sp)) < 6) {
94 if (len >= 0 && !ign_eof)
95 n_err(_("Unexpected EOF on SMTP connection\n"));
96 rv = -1;
97 goto jleave;
99 if (n_poption & n_PO_VERBVERB)
100 n_err(slp->buf);
101 switch (slp->buf[0]) {
102 case '1': rv = 1; break;
103 case '2': rv = 2; break;
104 case '3': rv = 3; break;
105 case '4': rv = 4; break;
106 default: rv = 5; break;
108 if (val != rv)
109 n_err(_("smtp-server: %s"), slp->buf);
110 } while (slp->buf[3] == '-');
112 if (want_dat) {
113 for (cp = slp->buf; digitchar(*cp); --len, ++cp)
115 for (; blankchar(*cp); --len, ++cp)
117 slp->dat = cp;
118 assert(len >= 2);
119 len -= 2;
120 cp[slp->datlen = (size_t)len] = '\0';
122 jleave:
123 NYD_LEAVE;
124 return rv;
127 /* Indirect SMTP I/O */
128 #define _ANSWER(X, IGNEOF, WANTDAT) \
129 do if (!(n_poption & n_PO_DEBUG)) {\
130 int y;\
131 if ((y = _smtp_read(sp, slp, X, IGNEOF, WANTDAT)) != (X) &&\
132 (!(IGNEOF) || y != -1))\
133 goto jleave;\
134 } while (0)
135 #define _OUT(X) \
136 do {\
137 if (n_poption & n_PO_D_VV){\
138 /* TODO for now n_err() cannot normalize newlines in %s expansions */\
139 char *__x__ = savestr(X), *__y__ = &__x__[strlen(__x__)];\
140 while(__y__ > __x__ && (__y__[-1] == '\n' || __y__[-1] == '\r'))\
141 --__y__;\
142 *__y__ = '\0';\
143 n_err(">>> %s\n", __x__);\
145 if (!(n_poption & n_PO_DEBUG))\
146 swrite(sp, X);\
147 } while (0)
149 static bool_t
150 _smtp_talk(struct sock *sp, struct sendbundle *sbp) /* TODO n_string etc. */
152 char o[LINESIZE];
153 char const *hostname;
154 struct smtp_line _sl, *slp = &_sl;
155 struct str b64;
156 struct name *n;
157 size_t blen, cnt;
158 bool_t inhdr = TRU1, inbcc = FAL0, rv = FAL0;
159 NYD_ENTER;
161 hostname = n_nodename(TRU1);
162 slp->buf = NULL;
163 slp->bufsize = 0;
165 /* Read greeting */
166 _ANSWER(2, FAL0, FAL0);
168 #ifdef HAVE_TLS
169 if (!sp->s_use_tls && xok_blook(smtp_use_starttls, &sbp->sb_url, OXM_ALL)) {
170 snprintf(o, sizeof o, NETLINE("EHLO %s"), hostname);
171 _OUT(o);
172 _ANSWER(2, FAL0, FAL0);
174 _OUT(NETLINE("STARTTLS"));
175 _ANSWER(2, FAL0, FAL0);
177 if(!(n_poption & n_PO_DEBUG) && !n_tls_open(&sbp->sb_url, sp))
178 goto jleave;
180 #else
181 if (xok_blook(smtp_use_starttls, &sbp->sb_url, OXM_ALL)) {
182 n_err(_("No TLS support compiled in\n"));
183 goto jleave;
185 #endif
187 /* Shorthand: no authentication, plain HELO? */
188 if (sbp->sb_ccred.cc_authtype == AUTHTYPE_NONE) {
189 snprintf(o, sizeof o, NETLINE("HELO %s"), hostname);
190 _OUT(o);
191 _ANSWER(2, FAL0, FAL0);
192 goto jsend;
195 /* We'll have to deal with authentication */
196 snprintf(o, sizeof o, NETLINE("EHLO %s"), hostname);
197 _OUT(o);
198 _ANSWER(2, FAL0, FAL0);
200 switch (sbp->sb_ccred.cc_authtype) {
201 default:
202 /* FALLTHRU (doesn't happen) */
203 case AUTHTYPE_PLAIN:
204 cnt = sbp->sb_ccred.cc_user.l;
205 if(sbp->sb_ccred.cc_pass.l >= UIZ_MAX - 2 ||
206 cnt >= UIZ_MAX - 2 - sbp->sb_ccred.cc_pass.l){
207 jerr_cred:
208 n_err(_("Credentials overflow buffer sizes\n"));
209 goto jleave;
211 cnt += sbp->sb_ccred.cc_pass.l;
213 if(cnt >= sizeof(o) - 2)
214 goto jerr_cred;
215 cnt += 2;
216 if(b64_encode_calc_size(cnt) == UIZ_MAX)
217 goto jerr_cred;
219 _OUT(NETLINE("AUTH PLAIN"));
220 _ANSWER(3, FAL0, FAL0);
222 snprintf(o, sizeof o, "%c%s%c%s",
223 '\0', sbp->sb_ccred.cc_user.s, '\0', sbp->sb_ccred.cc_pass.s);
224 if(b64_encode_buf(&b64, o, cnt, B64_SALLOC | B64_CRLF) == NULL)
225 goto jleave;
226 _OUT(b64.s);
227 _ANSWER(2, FAL0, FAL0);
228 break;
229 case AUTHTYPE_LOGIN:
230 if(b64_encode_calc_size(sbp->sb_ccred.cc_user.l) == UIZ_MAX ||
231 b64_encode_calc_size(sbp->sb_ccred.cc_pass.l) == UIZ_MAX)
232 goto jerr_cred;
234 _OUT(NETLINE("AUTH LOGIN"));
235 _ANSWER(3, FAL0, FAL0);
237 if(b64_encode_buf(&b64, sbp->sb_ccred.cc_user.s, sbp->sb_ccred.cc_user.l,
238 B64_SALLOC | B64_CRLF) == NULL)
239 goto jleave;
240 _OUT(b64.s);
241 _ANSWER(3, FAL0, FAL0);
243 if(b64_encode_buf(&b64, sbp->sb_ccred.cc_pass.s, sbp->sb_ccred.cc_pass.l,
244 B64_SALLOC | B64_CRLF) == NULL)
245 goto jleave;
246 _OUT(b64.s);
247 _ANSWER(2, FAL0, FAL0);
248 break;
249 #ifdef HAVE_MD5
250 case AUTHTYPE_CRAM_MD5:{
251 char *cp;
253 _OUT(NETLINE("AUTH CRAM-MD5"));
254 _ANSWER(3, FAL0, TRU1);
256 if((cp = cram_md5_string(&sbp->sb_ccred.cc_user, &sbp->sb_ccred.cc_pass,
257 slp->dat)) == NULL)
258 goto jerr_cred;
259 _OUT(cp);
260 _ANSWER(2, FAL0, FAL0);
261 }break;
262 #endif
263 #ifdef HAVE_GSSAPI
264 case AUTHTYPE_GSSAPI:
265 if (n_poption & n_PO_DEBUG)
266 n_err(_(">>> We would perform GSS-API authentication now\n"));
267 else if (!_smtp_gssapi(sp, sbp, slp))
268 goto jleave;
269 break;
270 #endif
273 jsend:
274 snprintf(o, sizeof o, NETLINE("MAIL FROM:<%s>"), sbp->sb_url.url_u_h.s);
275 _OUT(o);
276 _ANSWER(2, FAL0, FAL0);
278 for(n = sbp->sb_to; n != NULL; n = n->n_flink){
279 if (!(n->n_type & GDEL)) { /* TODO should not happen!?! */
280 if(n->n_flags & NAME_ADDRSPEC_WITHOUT_DOMAIN)
281 snprintf(o, sizeof o, NETLINE("RCPT TO:<%s@%s>"),
282 skinned_name(n), hostname);
283 else
284 snprintf(o, sizeof o, NETLINE("RCPT TO:<%s>"), skinned_name(n));
285 _OUT(o);
286 _ANSWER(2, FAL0, FAL0);
290 _OUT(NETLINE("DATA"));
291 _ANSWER(3, FAL0, FAL0);
293 fflush_rewind(sbp->sb_input);
294 cnt = fsize(sbp->sb_input);
295 while (fgetline(&slp->buf, &slp->bufsize, &cnt, &blen, sbp->sb_input, 1)
296 != NULL) {
297 if (inhdr) {
298 if (*slp->buf == '\n')
299 inhdr = inbcc = FAL0;
300 else if (inbcc && blankchar(*slp->buf))
301 continue;
302 /* We know what we have generated first, so do not look for whitespace
303 * before the ':' */
304 else if (!ascncasecmp(slp->buf, "bcc: ", 5)) {
305 inbcc = TRU1;
306 continue;
307 } else
308 inbcc = FAL0;
311 if (n_poption & n_PO_DEBUG) {
312 slp->buf[blen - 1] = '\0';
313 n_err(">>> %s%s\n", (*slp->buf == '.' ? "." : n_empty), slp->buf);
314 continue;
316 if (*slp->buf == '.')
317 swrite1(sp, ".", 1, 1); /* TODO I/O rewrite.. */
318 slp->buf[blen - 1] = NETNL[0];
319 slp->buf[blen] = NETNL[1];
320 swrite1(sp, slp->buf, blen + 1, 1);
322 _OUT(NETLINE("."));
323 _ANSWER(2, FAL0, FAL0);
325 _OUT(NETLINE("QUIT"));
326 _ANSWER(2, TRU1, FAL0);
327 rv = TRU1;
328 jleave:
329 if (slp->buf != NULL)
330 n_free(slp->buf);
331 NYD_LEAVE;
332 return rv;
335 #ifdef HAVE_GSSAPI
336 # include "smtp-gssapi.h"
337 #endif
339 #undef _OUT
340 #undef _ANSWER
342 FL bool_t
343 smtp_mta(struct sendbundle *sbp)
345 struct sock so;
346 sighandler_type volatile saveterm;
347 bool_t volatile rv = FAL0;
348 NYD_ENTER;
350 saveterm = safe_signal(SIGTERM, SIG_IGN);
351 if (sigsetjmp(_smtp_jmp, 1))
352 goto jleave;
353 if (saveterm != SIG_IGN)
354 safe_signal(SIGTERM, &_smtp_onterm);
356 if(n_poption & n_PO_DEBUG)
357 memset(&so, 0, sizeof so);
358 else if(!sopen(&so, &sbp->sb_url))
359 goto jleave;
361 so.s_desc = "SMTP";
362 rv = _smtp_talk(&so, sbp);
364 if (!(n_poption & n_PO_DEBUG))
365 sclose(&so);
366 jleave:
367 safe_signal(SIGTERM, saveterm);
368 NYD_LEAVE;
369 return rv;
371 #endif /* HAVE_SMTP */
373 /* s-it-mode */