THANKS: Olav Mørkrid
[s-mailx.git] / smtp.c
blob90521c2a4a77a696bc34e8ae11eb1561f10efd3e
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 */
9 /*
10 * Copyright (c) 2000
11 * Gunnar Ritter. All rights reserved.
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * 3. All advertising materials mentioning features or use of this software
22 * must display the following acknowledgement:
23 * This product includes software developed by Gunnar Ritter
24 * and his contributors.
25 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
26 * may be used to endorse or promote products derived from this software
27 * without specific prior written permission.
29 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
30 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
33 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
35 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
37 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
38 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39 * SUCH DAMAGE.
41 #undef n_FILE
42 #define n_FILE smtp
44 #ifndef HAVE_AMALGAMATION
45 # include "nail.h"
46 #endif
48 EMPTY_FILE()
49 #ifdef HAVE_SMTP
50 #include <sys/socket.h>
52 struct smtp_line {
53 char *dat; /* Actual data */
54 size_t datlen;
55 char *buf; /* Memory buffer */
56 size_t bufsize;
59 static sigjmp_buf _smtp_jmp;
61 static void _smtp_onterm(int signo);
63 /* Get the SMTP server's answer, expecting val */
64 static int _smtp_read(struct sock *sp, struct smtp_line *slp, int val,
65 bool_t ign_eof, bool_t want_dat);
67 /* Talk to a SMTP server */
68 static bool_t _smtp_talk(struct sock *sp, struct sendbundle *sbp);
70 #ifdef HAVE_GSSAPI
71 static bool_t _smtp_gssapi(struct sock *sp, struct sendbundle *sbp,
72 struct smtp_line *slp);
73 #endif
75 static void
76 _smtp_onterm(int signo)
78 NYD_X; /* Signal handler */
79 n_UNUSED(signo);
80 siglongjmp(_smtp_jmp, 1);
83 static int
84 _smtp_read(struct sock *sp, struct smtp_line *slp, int val,
85 bool_t ign_eof, bool_t want_dat)
87 int rv, len;
88 char *cp;
89 NYD_ENTER;
91 do {
92 if ((len = sgetline(&slp->buf, &slp->bufsize, NULL, sp)) < 6) {
93 if (len >= 0 && !ign_eof)
94 n_err(_("Unexpected EOF on SMTP connection\n"));
95 rv = -1;
96 goto jleave;
98 if (n_poption & n_PO_VERBVERB)
99 n_err(slp->buf);
100 switch (slp->buf[0]) {
101 case '1': rv = 1; break;
102 case '2': rv = 2; break;
103 case '3': rv = 3; break;
104 case '4': rv = 4; break;
105 default: rv = 5; break;
107 if (val != rv)
108 n_err(_("smtp-server: %s"), slp->buf);
109 } while (slp->buf[3] == '-');
111 if (want_dat) {
112 for (cp = slp->buf; digitchar(*cp); --len, ++cp)
114 for (; blankchar(*cp); --len, ++cp)
116 slp->dat = cp;
117 assert(len >= 2);
118 len -= 2;
119 cp[slp->datlen = (size_t)len] = '\0';
121 jleave:
122 NYD_LEAVE;
123 return rv;
126 /* Indirect SMTP I/O */
127 #define _ANSWER(X, IGNEOF, WANTDAT) \
128 do if (!(n_poption & n_PO_DEBUG)) {\
129 int y;\
130 if ((y = _smtp_read(sp, slp, X, IGNEOF, WANTDAT)) != (X) &&\
131 (!(IGNEOF) || y != -1))\
132 goto jleave;\
133 } while (0)
134 #define _OUT(X) \
135 do {\
136 if (n_poption & n_PO_D_VV){\
137 /* TODO for now n_err() cannot normalize newlines in %s expansions */\
138 char *__x__ = savestr(X), *__y__ = &__x__[strlen(__x__)];\
139 while(__y__ > __x__ && (__y__[-1] == '\n' || __y__[-1] == '\r'))\
140 --__y__;\
141 *__y__ = '\0';\
142 n_err(">>> %s\n", __x__);\
144 if (!(n_poption & n_PO_DEBUG))\
145 swrite(sp, X);\
146 } while (0)
148 static bool_t
149 _smtp_talk(struct sock *sp, struct sendbundle *sbp) /* TODO n_string etc. */
151 char o[LINESIZE];
152 char const *hostname;
153 struct smtp_line _sl, *slp = &_sl;
154 struct str b64;
155 struct name *n;
156 size_t blen, cnt;
157 bool_t inhdr = TRU1, inbcc = FAL0, rv = FAL0;
158 NYD_ENTER;
160 hostname = n_nodename(TRU1);
161 slp->buf = NULL;
162 slp->bufsize = 0;
164 /* Read greeting */
165 _ANSWER(2, FAL0, FAL0);
167 #ifdef HAVE_SSL
168 if (!sp->s_use_ssl && xok_blook(smtp_use_starttls, &sbp->sb_url, OXM_ALL)) {
169 snprintf(o, sizeof o, NETLINE("EHLO %s"), hostname);
170 _OUT(o);
171 _ANSWER(2, FAL0, FAL0);
173 _OUT(NETLINE("STARTTLS"));
174 _ANSWER(2, FAL0, FAL0);
176 if (!(n_poption & n_PO_DEBUG) && ssl_open(&sbp->sb_url, sp) != OKAY)
177 goto jleave;
179 #else
180 if (xok_blook(smtp_use_starttls, &sbp->sb_url, OXM_ALL)) {
181 n_err(_("No SSL support compiled in\n"));
182 goto jleave;
184 #endif
186 /* Shorthand: no authentication, plain HELO? */
187 if (sbp->sb_ccred.cc_authtype == AUTHTYPE_NONE) {
188 snprintf(o, sizeof o, NETLINE("HELO %s"), hostname);
189 _OUT(o);
190 _ANSWER(2, FAL0, FAL0);
191 goto jsend;
194 /* We'll have to deal with authentication */
195 snprintf(o, sizeof o, NETLINE("EHLO %s"), hostname);
196 _OUT(o);
197 _ANSWER(2, FAL0, FAL0);
199 switch (sbp->sb_ccred.cc_authtype) {
200 default:
201 /* FALLTHRU (doesn't happen) */
202 case AUTHTYPE_PLAIN:
203 cnt = sbp->sb_ccred.cc_user.l;
204 if(sbp->sb_ccred.cc_pass.l >= UIZ_MAX - 2 ||
205 cnt >= UIZ_MAX - 2 - sbp->sb_ccred.cc_pass.l){
206 jerr_cred:
207 n_err(_("Credentials overflow buffer sizes\n"));
208 goto jleave;
210 cnt += sbp->sb_ccred.cc_pass.l;
212 if(cnt >= sizeof(o) - 2)
213 goto jerr_cred;
214 cnt += 2;
215 if(b64_encode_calc_size(cnt) == UIZ_MAX)
216 goto jerr_cred;
218 _OUT(NETLINE("AUTH PLAIN"));
219 _ANSWER(3, FAL0, FAL0);
221 snprintf(o, sizeof o, "%c%s%c%s",
222 '\0', sbp->sb_ccred.cc_user.s, '\0', sbp->sb_ccred.cc_pass.s);
223 if(b64_encode_buf(&b64, o, cnt, B64_SALLOC | B64_CRLF) == NULL)
224 goto jleave;
225 _OUT(b64.s);
226 _ANSWER(2, FAL0, FAL0);
227 break;
228 case AUTHTYPE_LOGIN:
229 if(b64_encode_calc_size(sbp->sb_ccred.cc_user.l) == UIZ_MAX ||
230 b64_encode_calc_size(sbp->sb_ccred.cc_pass.l) == UIZ_MAX)
231 goto jerr_cred;
233 _OUT(NETLINE("AUTH LOGIN"));
234 _ANSWER(3, FAL0, FAL0);
236 if(b64_encode_buf(&b64, sbp->sb_ccred.cc_user.s, sbp->sb_ccred.cc_user.l,
237 B64_SALLOC | B64_CRLF) == NULL)
238 goto jleave;
239 _OUT(b64.s);
240 _ANSWER(3, FAL0, FAL0);
242 if(b64_encode_buf(&b64, sbp->sb_ccred.cc_pass.s, sbp->sb_ccred.cc_pass.l,
243 B64_SALLOC | B64_CRLF) == NULL)
244 goto jleave;
245 _OUT(b64.s);
246 _ANSWER(2, FAL0, FAL0);
247 break;
248 #ifdef HAVE_MD5
249 case AUTHTYPE_CRAM_MD5:{
250 char *cp;
252 _OUT(NETLINE("AUTH CRAM-MD5"));
253 _ANSWER(3, FAL0, TRU1);
255 if((cp = cram_md5_string(&sbp->sb_ccred.cc_user, &sbp->sb_ccred.cc_pass,
256 slp->dat)) == NULL)
257 goto jerr_cred;
258 _OUT(cp);
259 _ANSWER(2, FAL0, FAL0);
260 }break;
261 #endif
262 #ifdef HAVE_GSSAPI
263 case AUTHTYPE_GSSAPI:
264 if (n_poption & n_PO_DEBUG)
265 n_err(_(">>> We would perform GSS-API authentication now\n"));
266 else if (!_smtp_gssapi(sp, sbp, slp))
267 goto jleave;
268 break;
269 #endif
272 jsend:
273 snprintf(o, sizeof o, NETLINE("MAIL FROM:<%s>"), sbp->sb_url.url_u_h.s);
274 _OUT(o);
275 _ANSWER(2, FAL0, FAL0);
277 for(n = sbp->sb_to; n != NULL; n = n->n_flink){
278 if (!(n->n_type & GDEL)) { /* TODO should not happen!?! */
279 if(n->n_flags & NAME_ADDRSPEC_WITHOUT_DOMAIN)
280 snprintf(o, sizeof o, NETLINE("RCPT TO:<%s@%s>"),
281 skinned_name(n), hostname);
282 else
283 snprintf(o, sizeof o, NETLINE("RCPT TO:<%s>"), skinned_name(n));
284 _OUT(o);
285 _ANSWER(2, FAL0, FAL0);
289 _OUT(NETLINE("DATA"));
290 _ANSWER(3, FAL0, FAL0);
292 fflush_rewind(sbp->sb_input);
293 cnt = fsize(sbp->sb_input);
294 while (fgetline(&slp->buf, &slp->bufsize, &cnt, &blen, sbp->sb_input, 1)
295 != NULL) {
296 if (inhdr) {
297 if (*slp->buf == '\n')
298 inhdr = inbcc = FAL0;
299 else if (inbcc && blankchar(*slp->buf))
300 continue;
301 /* We know what we have generated first, so do not look for whitespace
302 * before the ':' */
303 else if (!ascncasecmp(slp->buf, "bcc: ", 5)) {
304 inbcc = TRU1;
305 continue;
306 } else
307 inbcc = FAL0;
310 if (n_poption & n_PO_DEBUG) {
311 slp->buf[blen - 1] = '\0';
312 n_err(">>> %s%s\n", (*slp->buf == '.' ? "." : n_empty), slp->buf);
313 continue;
315 if (*slp->buf == '.')
316 swrite1(sp, ".", 1, 1); /* TODO I/O rewrite.. */
317 slp->buf[blen - 1] = NETNL[0];
318 slp->buf[blen] = NETNL[1];
319 swrite1(sp, slp->buf, blen + 1, 1);
321 _OUT(NETLINE("."));
322 _ANSWER(2, FAL0, FAL0);
324 _OUT(NETLINE("QUIT"));
325 _ANSWER(2, TRU1, FAL0);
326 rv = TRU1;
327 jleave:
328 if (slp->buf != NULL)
329 free(slp->buf);
330 NYD_LEAVE;
331 return rv;
334 #ifdef HAVE_GSSAPI
335 # include "smtp-gssapi.h"
336 #endif
338 #undef _OUT
339 #undef _ANSWER
341 FL bool_t
342 smtp_mta(struct sendbundle *sbp)
344 struct sock so;
345 sighandler_type volatile saveterm;
346 bool_t volatile rv = FAL0;
347 NYD_ENTER;
349 saveterm = safe_signal(SIGTERM, SIG_IGN);
350 if (sigsetjmp(_smtp_jmp, 1))
351 goto jleave;
352 if (saveterm != SIG_IGN)
353 safe_signal(SIGTERM, &_smtp_onterm);
355 memset(&so, 0, sizeof so);
356 if (!(n_poption & n_PO_DEBUG) && !sopen(&so, &sbp->sb_url))
357 goto jleave;
359 so.s_desc = "SMTP";
360 rv = _smtp_talk(&so, sbp);
362 if (!(n_poption & n_PO_DEBUG))
363 sclose(&so);
364 jleave:
365 safe_signal(SIGTERM, saveterm);
366 NYD_LEAVE;
367 return rv;
369 #endif /* HAVE_SMTP */
371 /* s-it-mode */