Review: auxlily.c
[s-mailx.git] / smtp.c
blobdcda27549daff95cd6ea51fa0de53734d6cefaa0
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ SMTP client.
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 #undef NL
59 #undef LINE
60 #define NL "\015\012"
61 #define LINE(X) X NL
63 static sigjmp_buf _smtp_jmp;
65 static void _smtp_onterm(int signo);
67 /* Get the SMTP server's answer, expecting val */
68 static int _smtp_read(struct sock *sp, char **buf, size_t *bufsize, int val,
69 int ign_eof);
71 /* Talk to a SMTP server */
72 static int _smtp_talk(struct name *to, FILE *fi, struct sock *sp,
73 char *server, char *uhp, struct header *hp, char const *user,
74 char const *password, char const *skinned);
76 static void
77 _smtp_onterm(int signo)
79 NYD_X; /* Signal handler */
80 UNUSED(signo);
81 siglongjmp(_smtp_jmp, 1);
84 static int
85 _smtp_read(struct sock *sp, char **buf, size_t *bufsize, int val, int ign_eof)
87 int rv, len;
88 NYD_ENTER;
90 do {
91 if ((len = sgetline(buf, bufsize, NULL, sp)) < 6) {
92 if (len >= 0 && !ign_eof)
93 fprintf(stderr, tr(241, "Unexpected EOF on SMTP connection\n"));
94 rv = -1;
95 goto jleave;
97 if (options & OPT_VERBOSE)
98 fputs(*buf, stderr);
99 switch (**buf) {
100 case '1': rv = 1; break;
101 case '2': rv = 2; break;
102 case '3': rv = 3; break;
103 case '4': rv = 4; break;
104 default: rv = 5; break;
106 if (val != rv)
107 fprintf(stderr, tr(191, "smtp-server: %s"), *buf);
108 } while ((*buf)[3] == '-');
109 jleave:
110 NYD_LEAVE;
111 return rv;
114 static int
115 _smtp_talk(struct name *to, FILE *fi, struct sock *sp, char *xserver,
116 char *uhp, struct header *hp, char const *user, char const *password,
117 char const *skinned)
119 #define _ANSWER(X, IGNEOF) \
120 do if (!(options & OPT_DEBUG)) {\
121 int y;\
122 if ((y = _smtp_read(sp, &buf, &bsize, X, IGNEOF)) != (X) &&\
123 (!(IGNEOF) || y != -1))\
124 goto jleave;\
125 } while (0)
126 #define _OUT(X) \
127 do {\
128 if (options & OPT_VERBOSE)\
129 fprintf(stderr, ">>> %s", X);\
130 if (!(options & OPT_DEBUG))\
131 swrite(sp, X);\
132 } while (0)
134 char o[LINESIZE], *authstr, *cp, *buf = NULL;
135 struct str b64;
136 struct name *n;
137 size_t blen, cnt, bsize = 0;
138 enum {AUTH_NONE, AUTH_PLAIN, AUTH_LOGIN, AUTH_CRAM_MD5} auth;
139 int inhdr = 1, inbcc = 0, rv = 1;
140 NYD_ENTER;
141 UNUSED(hp);
142 UNUSED(xserver);
143 UNUSED(uhp);
145 if ((authstr = smtp_auth_var("", skinned)) == NULL)
146 auth = (user != NULL && password != NULL) ? AUTH_LOGIN : AUTH_NONE;
147 else if (!strcmp(authstr, "plain"))
148 auth = AUTH_PLAIN;
149 else if (!strcmp(authstr, "login"))
150 auth = AUTH_LOGIN;
151 else if (!strcmp(authstr, "cram-md5")) {
152 #ifdef HAVE_MD5
153 auth = AUTH_CRAM_MD5;
154 #else
155 fprintf(stderr, tr(277, "No CRAM-MD5 support compiled in.\n"));
156 goto jleave;
157 #endif
158 } else {
159 fprintf(stderr, tr(274, "Unknown SMTP authentication method: %s\n"),
160 authstr);
161 goto jleave;
164 if (auth != AUTH_NONE && (user == NULL || password == NULL)) {
165 fprintf(stderr, tr(275,
166 "User and password are necessary for SMTP authentication.\n"));
167 goto jleave;
169 _ANSWER(2, 0);
171 #ifdef HAVE_SSL
172 if (!sp->s_use_ssl && ok_blook(smtp_use_starttls)) {
173 char *server;
175 if ((cp = strchr(xserver, ':')) != NULL)
176 server = savestrbuf(xserver, PTR2SIZE(cp - xserver));
177 else
178 server = xserver;
179 snprintf(o, sizeof o, LINE("EHLO %s"), nodename(1));
180 _OUT(o);
181 _ANSWER(2, 0);
183 _OUT(LINE("STARTTLS"));
184 _ANSWER(2, 0);
186 if ((options & OPT_DEBUG) == 0 && ssl_open(server, sp, uhp) != OKAY)
187 goto jleave;
189 #else
190 if (ok_blook(smtp_use_starttls)) {
191 fprintf(stderr, tr(225, "No SSL support compiled in.\n"));
192 goto jleave;
194 #endif
196 if (auth != AUTH_NONE) {
197 snprintf(o, sizeof o, LINE("EHLO %s"), nodename(1));
198 _OUT(o);
199 _ANSWER(2, 0);
201 switch (auth) {
202 case AUTH_NONE:
203 #ifndef HAVE_MD5
204 case AUTH_CRAM_MD5:
205 #endif
206 /* FALLTRHU
207 * Won't happen, but gcc(1) and clang(1) whine without
208 * and Coverity whines with; that's a hard one.. */
209 case AUTH_LOGIN:
210 _OUT(LINE("AUTH LOGIN"));
211 _ANSWER(3, 0);
213 b64_encode_cp(&b64, user, B64_SALLOC | B64_CRLF);
214 _OUT(b64.s);
215 _ANSWER(3, 0);
217 b64_encode_cp(&b64, password, B64_SALLOC | B64_CRLF);
218 _OUT(b64.s);
219 _ANSWER(2, 0);
220 break;
221 case AUTH_PLAIN:
222 _OUT(LINE("AUTH PLAIN"));
223 _ANSWER(3, 0);
225 snprintf(o, sizeof o, "%c%s%c%s", '\0', user, '\0', password);
226 b64_encode_buf(&b64, o, strlen(user) + strlen(password) + 2,
227 B64_SALLOC | B64_CRLF);
228 _OUT(b64.s);
229 _ANSWER(2, 0);
230 break;
231 #ifdef HAVE_MD5
232 case AUTH_CRAM_MD5:
233 _OUT(LINE("AUTH CRAM-MD5"));
234 _ANSWER(3, 0);
236 for (cp = buf; digitchar(*cp); ++cp)
238 while (blankchar(*cp))
239 ++cp;
240 cp = cram_md5_string(user, password, cp);
241 _OUT(cp);
242 _ANSWER(2, 0);
243 break;
244 #endif
246 } else {
247 snprintf(o, sizeof o, LINE("HELO %s"), nodename(1));
248 _OUT(o);
249 _ANSWER(2, 0);
252 snprintf(o, sizeof o, LINE("MAIL FROM:<%s>"), skinned);
253 _OUT(o);
254 _ANSWER(2, 0);
256 for (n = to; n != NULL; n = n->n_flink) {
257 if (!(n->n_type & GDEL)) {
258 snprintf(o, sizeof o, LINE("RCPT TO:<%s>"), skinned_name(n));
259 _OUT(o);
260 _ANSWER(2, 0);
264 _OUT(LINE("DATA"));
265 _ANSWER(3, 0);
266 fflush_rewind(fi);
268 cnt = fsize(fi);
269 while (fgetline(&buf, &bsize, &cnt, &blen, fi, 1) != NULL) {
270 if (inhdr) {
271 if (*buf == '\n') {
272 inhdr = 0;
273 inbcc = 0;
274 } else if (inbcc && blankchar(*buf))
275 continue;
276 /* We know what we have generated first, so do not look for whitespace
277 * before the ':' */
278 else if (!ascncasecmp(buf, "bcc: ", 5)) {
279 inbcc = 1;
280 continue;
281 } else
282 inbcc = 0;
285 if (*buf == '.') {
286 if (options & OPT_DEBUG)
287 putc('.', stderr);
288 else
289 swrite1(sp, ".", 1, 1);
291 if (options & OPT_DEBUG) {
292 fprintf(stderr, ">>> %s", buf);
293 continue;
295 buf[blen - 1] = NL[0];
296 buf[blen] = NL[1];
297 swrite1(sp, buf, blen + 1, 1);
299 _OUT(LINE("."));
300 _ANSWER(2, 0);
302 _OUT(LINE("QUIT"));
303 _ANSWER(2, 1);
304 rv = 0;
305 jleave:
306 if (buf != NULL)
307 free(buf);
308 NYD_LEAVE;
309 return rv;
310 #undef _OUT
311 #undef _ANSWER
312 #undef __ANSWER
315 FL char *
316 smtp_auth_var(char const *atype, char const *addr) /* FIXME GENERIC */
318 size_t tl, al, len;
319 char *var, *cp;
320 NYD_ENTER;
322 tl = strlen(atype);
323 al = strlen(addr);
324 len = tl + al + 10 +1;
325 var = ac_alloc(len);
327 /* Try a 'user@host', i.e., address specific version first */
328 (void)snprintf(var, len, "smtp-auth%s-%s", atype, addr);
329 if ((cp = vok_vlook(var)) == NULL) {
330 snprintf(var, len, "smtp-auth%s", atype);
331 cp = vok_vlook(var);
333 if (cp != NULL)
334 cp = savestr(cp);
336 ac_free(var);
337 NYD_LEAVE;
338 return cp;
341 FL int
342 smtp_mta(char *volatile server, struct name *volatile to, FILE *fi,
343 struct header *hp, char const *user, char const *password,
344 char const *skinned)
346 struct sock so;
347 sighandler_type volatile saveterm;
348 int use_ssl, rv = 1;
349 NYD_ENTER;
351 memset(&so, 0, sizeof so);
353 saveterm = safe_signal(SIGTERM, SIG_IGN);
354 if (sigsetjmp(_smtp_jmp, 1))
355 goto jleave;
356 if (saveterm != SIG_IGN)
357 safe_signal(SIGTERM, &_smtp_onterm);
359 if (!strncmp(server, "smtp://", 7)) {
360 use_ssl = 0;
361 server += 7;
362 #ifdef HAVE_SSL
363 } else if (!strncmp(server, "smtps://", 8)) {
364 use_ssl = 1;
365 server += 8;
366 #endif
367 } else
368 use_ssl = 0;
370 if (!(options & OPT_DEBUG) && sopen(server, &so, use_ssl, server,
371 (use_ssl ? "smtps" : "smtp")) != OKAY)
372 goto jleave;
374 so.s_desc = "SMTP";
375 rv = _smtp_talk(to, fi, &so, server, server, hp, user, password, skinned);
377 if (!(options & OPT_DEBUG))
378 sclose(&so);
379 jleave:
380 safe_signal(SIGTERM, saveterm);
381 NYD_LEAVE;
382 return rv;
385 #undef LINE
386 #undef NL
387 #endif /* HAVE_SMTP */
389 /* vim:set fenc=utf-8:s-it-mode */