NEWS: update for v14.5.2
[s-mailx.git] / smtp.c
blob164a405cc4a10b96090b521b678d437e857e4f7d
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ SMTP client and other internet related functions.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2014 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 2000
9 * Gunnar Ritter. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by Gunnar Ritter
22 * and his contributors.
23 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 EMPTY_FILE(smtp)
45 #ifdef HAVE_SMTP
46 #ifdef HAVE_SOCKETS
47 # include <sys/socket.h>
49 # include <netdb.h>
51 # include <netinet/in.h>
53 # ifdef HAVE_ARPA_INET_H
54 # include <arpa/inet.h>
55 # endif
56 #endif
58 #ifdef HAVE_MD5
59 # include "md5.h"
60 #endif
62 static char *smtpbuf;
63 static size_t smtpbufsize;
64 static sigjmp_buf smtpjmp;
66 static void onterm(int signo);
67 static int read_smtp(struct sock *sp, int val, int ign_eof);
68 static int talk_smtp(struct name *to, FILE *fi, struct sock *sp,
69 char *server, char *uhp, struct header *hp,
70 const char *user, const char *password,
71 const char *skinned);
73 static void
74 onterm(int signo)
76 (void)signo;
77 siglongjmp(smtpjmp, 1);
81 * Get the SMTP server's answer, expecting val.
83 static int
84 read_smtp(struct sock *sp, int val, int ign_eof)
86 int ret;
87 int len;
89 do {
90 if ((len = sgetline(&smtpbuf, &smtpbufsize, NULL, sp)) < 6) {
91 if (len >= 0 && !ign_eof)
92 fprintf(stderr, tr(241,
93 "Unexpected EOF on SMTP connection\n"));
94 return -1;
96 if (options & OPT_VERBOSE)
97 fputs(smtpbuf, stderr);
98 switch (*smtpbuf) {
99 case '1': ret = 1; break;
100 case '2': ret = 2; break;
101 case '3': ret = 3; break;
102 case '4': ret = 4; break;
103 default: ret = 5;
105 if (val != ret)
106 fprintf(stderr, tr(191, "smtp-server: %s"), smtpbuf);
107 } while (smtpbuf[3] == '-');
108 return ret;
112 * Macros for talk_smtp.
114 #define _SMTP_ANSWER(x, ign_eof) \
115 if ((options & OPT_DEBUG) == 0) { \
116 int y; \
117 if ((y = read_smtp(sp, x, ign_eof)) != (x) && \
118 (!(ign_eof) || y != -1)) { \
119 if (b != NULL) \
120 free(b); \
121 return 1; \
125 #define SMTP_ANSWER(x) _SMTP_ANSWER(x, 0)
127 #define SMTP_OUT(x) if (options & OPT_VERBOSE) \
128 fprintf(stderr, ">>> %s", x); \
129 if ((options & OPT_DEBUG) == 0) \
130 swrite(sp, x);
133 * Talk to a SMTP server.
135 static int
136 talk_smtp(struct name *to, FILE *fi, struct sock *sp,
137 char *xserver, char *uhp, struct header *hp,
138 const char *user, const char *password, const char *skinned)
140 char o[LINESIZE], *authstr, *cp, *b = NULL;
141 struct str b64;
142 struct name *n;
143 size_t blen, cnt, bsize = 0;
144 enum { AUTH_NONE, AUTH_PLAIN, AUTH_LOGIN, AUTH_CRAM_MD5 } auth;
145 int inhdr = 1, inbcc = 0;
146 (void)hp;
147 (void)xserver;
148 (void)uhp;
150 if ((authstr = smtp_auth_var("", skinned)) == NULL)
151 auth = user && password ? AUTH_LOGIN : AUTH_NONE;
152 else if (strcmp(authstr, "plain") == 0)
153 auth = AUTH_PLAIN;
154 else if (strcmp(authstr, "login") == 0)
155 auth = AUTH_LOGIN;
156 else if (strcmp(authstr, "cram-md5") == 0) {
157 #ifdef HAVE_MD5
158 auth = AUTH_CRAM_MD5;
159 #else
160 fprintf(stderr, tr(277, "No CRAM-MD5 support compiled in.\n"));
161 return (1);
162 #endif
163 } else {
164 fprintf(stderr, tr(274,
165 "Unknown SMTP authentication method: %s\n"), authstr);
166 return 1;
168 if (auth != AUTH_NONE && (user == NULL || password == NULL)) {
169 fprintf(stderr, tr(275,
170 "User and password are necessary "
171 "for SMTP authentication.\n"));
172 return 1;
174 SMTP_ANSWER(2);
175 #ifdef HAVE_SSL
176 if (!sp->s_use_ssl && ok_blook(smtp_use_starttls)) {
177 char *server;
178 if ((cp = strchr(xserver, ':')) != NULL) {
179 server = salloc(cp - xserver + 1);
180 memcpy(server, xserver, cp - xserver);
181 server[cp - xserver] = '\0';
182 } else
183 server = xserver;
184 snprintf(o, sizeof o, "EHLO %s\r\n", nodename(1));
185 SMTP_OUT(o);
186 SMTP_ANSWER(2);
187 SMTP_OUT("STARTTLS\r\n");
188 SMTP_ANSWER(2);
189 if ((options & OPT_DEBUG) == 0 &&
190 ssl_open(server, sp, uhp) != OKAY)
191 return 1;
193 #else
194 if (ok_blook(smtp_use_starttls)) {
195 fprintf(stderr, tr(225, "No SSL support compiled in.\n"));
196 return 1;
198 #endif
199 if (auth != AUTH_NONE) {
200 snprintf(o, sizeof o, "EHLO %s\r\n", nodename(1));
201 SMTP_OUT(o);
202 SMTP_ANSWER(2);
203 switch (auth) {
204 case AUTH_NONE:
205 #ifndef HAVE_MD5
206 case AUTH_CRAM_MD5:
207 #endif
208 /* FALLTRHU
209 * Won't happen, but gcc(1) and clang(1) whine without
210 * and Coverity whines with; that's a hard one.. */
211 case AUTH_LOGIN:
212 SMTP_OUT("AUTH LOGIN\r\n");
213 SMTP_ANSWER(3);
214 (void)b64_encode_cp(&b64, user, B64_SALLOC|B64_CRLF);
215 SMTP_OUT(b64.s);
216 SMTP_ANSWER(3);
217 (void)b64_encode_cp(&b64, password,
218 B64_SALLOC|B64_CRLF);
219 SMTP_OUT(b64.s);
220 SMTP_ANSWER(2);
221 break;
222 case AUTH_PLAIN:
223 SMTP_OUT("AUTH PLAIN\r\n");
224 SMTP_ANSWER(3);
225 (void)snprintf(o, sizeof o, "%c%s%c%s",
226 '\0', user, '\0', password);
227 (void)b64_encode_buf(&b64, o, strlen(user) +
228 strlen(password) + 2, B64_SALLOC|B64_CRLF);
229 SMTP_OUT(b64.s);
230 SMTP_ANSWER(2);
231 break;
232 #ifdef HAVE_MD5
233 case AUTH_CRAM_MD5:
234 SMTP_OUT("AUTH CRAM-MD5\r\n");
235 SMTP_ANSWER(3);
236 for (cp = smtpbuf; digitchar(*cp); ++cp)
238 while (blankchar(*cp))
239 ++cp;
240 cp = cram_md5_string(user, password, cp);
241 SMTP_OUT(cp);
242 SMTP_ANSWER(2);
243 break;
244 #endif
246 } else {
247 snprintf(o, sizeof o, "HELO %s\r\n", nodename(1));
248 SMTP_OUT(o);
249 SMTP_ANSWER(2);
251 snprintf(o, sizeof o, "MAIL FROM:<%s>\r\n", skinned);
252 SMTP_OUT(o);
253 SMTP_ANSWER(2);
254 for (n = to; n != NULL; n = n->n_flink) {
255 if ((n->n_type & GDEL) == 0) {
256 snprintf(o, sizeof o, "RCPT TO:<%s>\r\n",
257 skinned_name(n));
258 SMTP_OUT(o);
259 SMTP_ANSWER(2);
262 SMTP_OUT("DATA\r\n");
263 SMTP_ANSWER(3);
264 fflush(fi);
265 rewind(fi);
266 cnt = fsize(fi);
267 while (fgetline(&b, &bsize, &cnt, &blen, fi, 1) != NULL) {
268 if (inhdr) {
269 if (*b == '\n') {
270 inhdr = 0;
271 inbcc = 0;
272 } else if (inbcc && blankchar(*b & 0377))
273 continue;
275 * We know what we have generated first, so
276 * do not look for whitespace before the ':'.
278 else if (ascncasecmp(b, "bcc: ", 5) == 0) {
279 inbcc = 1;
280 continue;
281 } else
282 inbcc = 0;
284 if (*b == '.') {
285 if (options & OPT_DEBUG)
286 putc('.', stderr);
287 else
288 swrite1(sp, ".", 1, 1);
290 if (options & OPT_DEBUG) {
291 fprintf(stderr, ">>> %s", b);
292 continue;
294 b[blen-1] = '\r';
295 b[blen] = '\n';
296 swrite1(sp, b, blen+1, 1);
298 SMTP_OUT(".\r\n");
299 SMTP_ANSWER(2);
300 SMTP_OUT("QUIT\r\n");
301 _SMTP_ANSWER(2, 1);
302 if (b != NULL)
303 free(b);
304 return 0;
307 FL char *
308 smtp_auth_var(char const *atype, char const *addr)
310 size_t tl, al, len;
311 char *var, *cp;
313 tl = strlen(atype);
314 al = strlen(addr);
315 len = tl + al + 10 + 1;
316 var = ac_alloc(len);
318 /* Try a 'user@host', i.e., address specific version first */
319 (void)snprintf(var, len, "smtp-auth%s-%s", atype, addr);
320 if ((cp = vok_vlook(var)) == NULL) {
321 snprintf(var, len, "smtp-auth%s", atype);
322 cp = vok_vlook(var);
324 if (cp != NULL)
325 cp = savestr(cp);
327 ac_free(var);
328 return cp;
332 * Connect to a SMTP server.
334 FL int
335 smtp_mta(char *volatile server, struct name *volatile to, FILE *fi,
336 struct header *hp, const char *user, const char *password,
337 const char *skinned)
339 struct sock so;
340 int use_ssl, ret;
341 sighandler_type volatile saveterm;
343 memset(&so, 0, sizeof so);
344 saveterm = safe_signal(SIGTERM, SIG_IGN);
345 if (sigsetjmp(smtpjmp, 1)) {
346 safe_signal(SIGTERM, saveterm);
347 return 1;
349 if (saveterm != SIG_IGN)
350 safe_signal(SIGTERM, onterm);
351 if (strncmp(server, "smtp://", 7) == 0) {
352 use_ssl = 0;
353 server += 7;
354 #ifdef HAVE_SSL
355 } else if (strncmp(server, "smtps://", 8) == 0) {
356 use_ssl = 1;
357 server += 8;
358 #endif
359 } else
360 use_ssl = 0;
361 if ((options & OPT_DEBUG) == 0 && sopen(server, &so, use_ssl, server,
362 use_ssl ? "smtps" : "smtp",
363 (options & OPT_VERBOSE) != 0) != OKAY) {
364 safe_signal(SIGTERM, saveterm);
365 return 1;
367 so.s_desc = "SMTP";
368 ret = talk_smtp(to, fi, &so, server, server, hp,
369 user, password, skinned);
370 if ((options & OPT_DEBUG) == 0)
371 sclose(&so);
372 if (smtpbuf) {
373 free(smtpbuf);
374 smtpbuf = NULL;
375 smtpbufsize = 0;
377 safe_signal(SIGTERM, saveterm);
378 return ret;
380 #endif /* HAVE_SMTP */