nodename(): query sys_hostname() only once
[s-mailx.git] / smtp.c
blob6c8e836f409c82d37fc42fec3ce37880d872b952
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 - 2014 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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.
42 #ifndef HAVE_AMALGAMATION
43 # include "nail.h"
44 #endif
46 EMPTY_FILE(smtp)
47 #ifdef HAVE_SMTP
48 #include <sys/socket.h>
50 #include <netdb.h>
52 #include <netinet/in.h>
54 #ifdef HAVE_ARPA_INET_H
55 # include <arpa/inet.h>
56 #endif
58 #undef NL
59 #undef LINE
60 #define NL "\015\012"
61 #define LINE(X) X NL
63 struct smtp_line {
64 char *dat; /* Actual data */
65 size_t datlen;
66 char *buf; /* Memory buffer */
67 size_t bufsize;
70 static sigjmp_buf _smtp_jmp;
72 static void _smtp_onterm(int signo);
74 /* Get the SMTP server's answer, expecting val */
75 static int _smtp_read(struct sock *sp, struct smtp_line *slp, int val,
76 bool_t ign_eof, bool_t want_dat);
78 /* Talk to a SMTP server */
79 static int _smtp_talk(struct name *to, struct header *hp, FILE *fi,
80 struct sock *sp, struct url *urlp, struct ccred *ccred);
82 #ifdef HAVE_GSSAPI
83 static bool_t _smtp_gssapi(struct sock *sp, struct url *urlp,
84 struct ccred *ccred, struct smtp_line *slp);
85 #endif
87 static void
88 _smtp_onterm(int signo)
90 NYD_X; /* Signal handler */
91 UNUSED(signo);
92 siglongjmp(_smtp_jmp, 1);
95 static int
96 _smtp_read(struct sock *sp, struct smtp_line *slp, int val,
97 bool_t ign_eof, bool_t want_dat)
99 int rv, len;
100 char *cp;
101 NYD_ENTER;
103 do {
104 if ((len = sgetline(&slp->buf, &slp->bufsize, NULL, sp)) < 6) {
105 if (len >= 0 && !ign_eof)
106 fprintf(stderr, tr(241, "Unexpected EOF on SMTP connection\n"));
107 rv = -1;
108 goto jleave;
110 if (options & OPT_VERBOSE)
111 fputs(slp->buf, stderr);
112 switch (slp->buf[0]) {
113 case '1': rv = 1; break;
114 case '2': rv = 2; break;
115 case '3': rv = 3; break;
116 case '4': rv = 4; break;
117 default: rv = 5; break;
119 if (val != rv)
120 fprintf(stderr, tr(191, "smtp-server: %s"), slp->buf);
121 } while (slp->buf[3] == '-');
123 if (want_dat) {
124 for (cp = slp->buf; digitchar(*cp); --len, ++cp)
126 for (; blankchar(*cp); --len, ++cp)
128 slp->dat = cp;
129 assert(len >= 2);
130 len -= 2;
131 cp[slp->datlen = (size_t)len] = '\0';
133 jleave:
134 NYD_LEAVE;
135 return rv;
138 /* Indirect SMTP I/O */
139 #define _ANSWER(X, IGNEOF, WANTDAT) \
140 do if (!(options & OPT_DEBUG)) {\
141 int y;\
142 if ((y = _smtp_read(sp, slp, X, IGNEOF, WANTDAT)) != (X) &&\
143 (!(IGNEOF) || y != -1))\
144 goto jleave;\
145 } while (0)
146 #define _OUT(X) \
147 do {\
148 if (options & OPT_VERBOSE)\
149 fprintf(stderr, ">>> %s", X);\
150 if (!(options & OPT_DEBUG))\
151 swrite(sp, X);\
152 } while (0)
154 static int
155 _smtp_talk(struct name *to, struct header *hp, FILE *fi, struct sock *sp,
156 struct url *urlp, struct ccred *ccred)
158 char o[LINESIZE], *cp;
159 struct smtp_line _sl, *slp = &_sl;
160 struct str b64;
161 struct name *n;
162 size_t blen, cnt;
163 int inhdr = 1, inbcc = 0, rv = 1;
164 NYD_ENTER;
165 UNUSED(hp);
167 slp->buf = NULL;
168 slp->bufsize = 0;
170 /* Read greeting */
171 _ANSWER(2, FAL0, FAL0);
173 #ifdef HAVE_SSL
174 if (!sp->s_use_ssl && ok_blook(smtp_use_starttls)) {
175 snprintf(o, sizeof o, LINE("EHLO %s"), nodename(1));
176 _OUT(o);
177 _ANSWER(2, FAL0, FAL0);
179 _OUT(LINE("STARTTLS"));
180 _ANSWER(2, FAL0, FAL0);
182 if (!(options & OPT_DEBUG) &&
183 ssl_open(urlp->url_host.s, sp, urlp->url_uhp.s) != OKAY)
184 goto jleave;
186 #else
187 if (ok_blook(smtp_use_starttls)) {
188 fprintf(stderr, tr(225, "No SSL support compiled in.\n"));
189 goto jleave;
191 #endif
193 /* Shorthand: no authentication, plain HELO? */
194 if (ccred->cc_authtype == AUTHTYPE_NONE) {
195 snprintf(o, sizeof o, LINE("HELO %s"), nodename(1));
196 _OUT(o);
197 _ANSWER(2, FAL0, FAL0);
198 goto jsend;
201 /* We'll have to deal with authentication */
202 snprintf(o, sizeof o, LINE("EHLO %s"), nodename(1));
203 _OUT(o);
204 _ANSWER(2, FAL0, FAL0);
206 switch (ccred->cc_authtype) {
207 default:
208 /* FALLTHRU (doesn't happen) */
209 case AUTHTYPE_PLAIN:
210 _OUT(LINE("AUTH PLAIN"));
211 _ANSWER(3, FAL0, FAL0);
213 snprintf(o, sizeof o, "%c%s%c%s",
214 '\0', ccred->cc_user.s, '\0', ccred->cc_pass.s);
215 b64_encode_buf(&b64, o, ccred->cc_user.l + ccred->cc_pass.l + 2,
216 B64_SALLOC | B64_CRLF);
217 _OUT(b64.s);
218 _ANSWER(2, FAL0, FAL0);
219 break;
220 case AUTHTYPE_LOGIN:
221 _OUT(LINE("AUTH LOGIN"));
222 _ANSWER(3, FAL0, FAL0);
224 b64_encode_cp(&b64, ccred->cc_user.s, B64_SALLOC | B64_CRLF);
225 _OUT(b64.s);
226 _ANSWER(3, FAL0, FAL0);
228 b64_encode_cp(&b64, ccred->cc_pass.s, B64_SALLOC | B64_CRLF);
229 _OUT(b64.s);
230 _ANSWER(2, FAL0, FAL0);
231 break;
232 #ifdef HAVE_MD5
233 case AUTHTYPE_CRAM_MD5:
234 _OUT(LINE("AUTH CRAM-MD5"));
235 _ANSWER(3, FAL0, TRU1);
236 cp = cram_md5_string(ccred->cc_user.s, ccred->cc_pass.s, slp->dat);
237 _OUT(cp);
238 _ANSWER(2, FAL0, FAL0);
239 break;
240 #endif
241 #ifdef HAVE_GSSAPI
242 case AUTHTYPE_GSSAPI:
243 if (!_smtp_gssapi(sp, urlp, ccred, slp))
244 goto jleave;
245 break;
246 #endif
249 jsend:
250 snprintf(o, sizeof o, LINE("MAIL FROM:<%s>"), urlp->url_uh.s);
251 _OUT(o);
252 _ANSWER(2, FAL0, FAL0);
254 for (n = to; n != NULL; n = n->n_flink) {
255 if (!(n->n_type & GDEL)) {
256 snprintf(o, sizeof o, LINE("RCPT TO:<%s>"), skinned_name(n));
257 _OUT(o);
258 _ANSWER(2, FAL0, FAL0);
262 _OUT(LINE("DATA"));
263 _ANSWER(3, FAL0, FAL0);
265 fflush_rewind(fi);
266 cnt = fsize(fi);
267 while (fgetline(&slp->buf, &slp->bufsize, &cnt, &blen, fi, 1) != NULL) {
268 if (inhdr) {
269 if (*slp->buf == '\n') {
270 inhdr = 0;
271 inbcc = 0;
272 } else if (inbcc && blankchar(*slp->buf))
273 continue;
274 /* We know what we have generated first, so do not look for whitespace
275 * before the ':' */
276 else if (!ascncasecmp(slp->buf, "bcc: ", 5)) {
277 inbcc = 1;
278 continue;
279 } else
280 inbcc = 0;
283 if (*slp->buf == '.') {
284 if (options & OPT_DEBUG)
285 putc('.', stderr);
286 else
287 swrite1(sp, ".", 1, 1);
289 if (options & OPT_DEBUG) {
290 fprintf(stderr, ">>> %s", slp->buf);
291 continue;
293 slp->buf[blen - 1] = NL[0];
294 slp->buf[blen] = NL[1];
295 swrite1(sp, slp->buf, blen + 1, 1);
297 _OUT(LINE("."));
298 _ANSWER(2, FAL0, FAL0);
300 _OUT(LINE("QUIT"));
301 _ANSWER(2, TRU1, FAL0);
302 rv = 0;
303 jleave:
304 if (slp->buf != NULL)
305 free(slp->buf);
306 NYD_LEAVE;
307 return rv;
310 #ifdef HAVE_GSSAPI
311 # include "smtp_gssapi.h"
312 #endif
314 #undef _OUT
315 #undef _ANSWER
317 FL int
318 smtp_mta(struct url *urlp, struct name * volatile to, FILE *fi,
319 struct header *hp, struct ccred *ccred)
321 struct sock so;
322 sighandler_type volatile saveterm;
323 int rv = 1;
324 NYD_ENTER;
326 saveterm = safe_signal(SIGTERM, SIG_IGN);
327 if (sigsetjmp(_smtp_jmp, 1))
328 goto jleave;
329 if (saveterm != SIG_IGN)
330 safe_signal(SIGTERM, &_smtp_onterm);
332 memset(&so, 0, sizeof so);
333 if (!(options & OPT_DEBUG) && sopen(&so, urlp) != OKAY)
334 goto jleave;
336 so.s_desc = "SMTP";
337 rv = _smtp_talk(to, hp, fi, &so, urlp, ccred);
339 if (!(options & OPT_DEBUG))
340 sclose(&so);
341 jleave:
342 safe_signal(SIGTERM, saveterm);
343 NYD_LEAVE;
344 return rv;
347 #undef LINE
348 #undef NL
349 #endif /* HAVE_SMTP */
351 /* vim:set fenc=utf-8:s-it-mode */