make-config.in: complete path (leftover of [807f64e2], 2015-12-26!)
[s-mailx.git] / tls.c
blob88702c94de6e27b4df04d11cb3349be1c07e7ccc
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Generic TLS / S/MIME commands.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 * SPDX-License-Identifier: BSD-4-Clause TODO ISC (is taken from book!)
7 */
8 /*
9 * Copyright (c) 2002
10 * Gunnar Ritter. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. All advertising materials mentioning features or use of this software
21 * must display the following acknowledgement:
22 * This product includes software developed by Gunnar Ritter
23 * and his contributors.
24 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
25 * may be used to endorse or promote products derived from this software
26 * without specific prior written permission.
28 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
29 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
32 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 * SUCH DAMAGE.
40 #undef n_FILE
41 #define n_FILE tls
43 #ifndef HAVE_AMALGAMATION
44 # include "nail.h"
45 #endif
47 EMPTY_FILE()
48 #ifdef HAVE_TLS
49 struct a_tls_verify_levels{
50 char const tv_name[8];
51 enum n_tls_verify_level tv_level;
54 /* Supported SSL/TLS verification methods: update manual on change! */
55 static struct a_tls_verify_levels const a_tls_verify_levels[] = {
56 {"strict", n_TLS_VERIFY_STRICT},
57 {"ask", n_TLS_VERIFY_ASK},
58 {"warn", n_TLS_VERIFY_WARN},
59 {"ignore", n_TLS_VERIFY_IGNORE}
62 FL void
63 n_tls_set_verify_level(struct url const *urlp){
64 size_t i;
65 char const *cp;
66 NYD2_ENTER;
68 n_tls_verify_level = n_TLS_VERIFY_ASK;
70 if((cp = xok_vlook(tls_verify, urlp, OXM_ALL)) != NULL ||
71 (cp = xok_vlook(ssl_verify, urlp, OXM_ALL)) != NULL){
72 for(i = 0;;)
73 if(!asccasecmp(a_tls_verify_levels[i].tv_name, cp)){
74 n_tls_verify_level = a_tls_verify_levels[i].tv_level;
75 break;
76 }else if(++i >= n_NELEM(a_tls_verify_levels)){
77 n_err(_("Invalid value of *tls-verify*: %s\n"), cp);
78 break;
81 NYD2_LEAVE;
84 FL bool_t
85 n_tls_verify_decide(void){
86 bool_t rv;
87 NYD2_ENTER;
89 switch(n_tls_verify_level){
90 default:
91 case n_TLS_VERIFY_STRICT:
92 rv = FAL0;
93 break;
94 case n_TLS_VERIFY_ASK:
95 rv = getapproval(NULL, FAL0);
96 break;
97 case n_TLS_VERIFY_WARN:
98 case n_TLS_VERIFY_IGNORE:
99 rv = TRU1;
100 break;
102 NYD2_LEAVE;
103 return rv;
106 FL enum okay
107 smime_split(FILE *ip, FILE **hp, FILE **bp, long xcount, int keep)
109 struct myline {
110 struct myline *ml_next;
111 size_t ml_len;
112 char ml_buf[n_VFIELD_SIZE(0)];
113 } *head, *tail;
114 char *buf;
115 size_t bufsize, buflen, cnt;
116 int c;
117 enum okay rv = STOP;
118 NYD_ENTER;
120 if ((*hp = Ftmp(NULL, "smimeh", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
121 goto jetmp;
122 if ((*bp = Ftmp(NULL, "smimeb", OF_RDWR | OF_UNLINK | OF_REGISTER)
123 ) == NULL) {
124 Fclose(*hp);
125 jetmp:
126 n_perr(_("tempfile"), 0);
127 goto jleave;
130 head = tail = NULL;
131 buf = n_alloc(bufsize = LINESIZE);
132 cnt = (xcount < 0) ? fsize(ip) : xcount;
134 while (fgetline(&buf, &bufsize, &cnt, &buflen, ip, 0) != NULL &&
135 *buf != '\n') {
136 if (!ascncasecmp(buf, "content-", 8)) {
137 if (keep)
138 fputs("X-Encoded-", *hp);
139 for (;;) {
140 struct myline *ml = n_alloc(n_VSTRUCT_SIZEOF(struct myline, ml_buf
141 ) + buflen +1);
142 if (tail != NULL)
143 tail->ml_next = ml;
144 else
145 head = ml;
146 tail = ml;
147 ml->ml_next = NULL;
148 ml->ml_len = buflen;
149 memcpy(ml->ml_buf, buf, buflen +1);
150 if (keep)
151 fwrite(buf, sizeof *buf, buflen, *hp);
152 c = getc(ip);
153 ungetc(c, ip);
154 if (!blankchar(c))
155 break;
156 fgetline(&buf, &bufsize, &cnt, &buflen, ip, 0);
158 continue;
160 fwrite(buf, sizeof *buf, buflen, *hp);
162 fflush_rewind(*hp);
164 while (head != NULL) {
165 fwrite(head->ml_buf, sizeof *head->ml_buf, head->ml_len, *bp);
166 tail = head;
167 head = head->ml_next;
168 n_free(tail);
170 putc('\n', *bp);
171 while (fgetline(&buf, &bufsize, &cnt, &buflen, ip, 0) != NULL)
172 fwrite(buf, sizeof *buf, buflen, *bp);
173 fflush_rewind(*bp);
175 n_free(buf);
176 rv = OKAY;
177 jleave:
178 NYD_LEAVE;
179 return rv;
182 FL FILE *
183 smime_sign_assemble(FILE *hp, FILE *bp, FILE *sp, char const *message_digest)
185 char *boundary;
186 int c, lastc = EOF;
187 FILE *op;
188 NYD_ENTER;
190 if ((op = Ftmp(NULL, "smimea", OF_RDWR | OF_UNLINK | OF_REGISTER)
191 ) == NULL) {
192 n_perr(_("tempfile"), 0);
193 goto jleave;
196 while ((c = getc(hp)) != EOF) {
197 if (c == '\n' && lastc == '\n')
198 break;
199 putc(c, op);
200 lastc = c;
203 boundary = mime_param_boundary_create();
204 fprintf(op, "Content-Type: multipart/signed;\n"
205 " protocol=\"application/pkcs7-signature\"; micalg=%s;\n"
206 " boundary=\"%s\"\n\n", message_digest, boundary);
207 fprintf(op, "This is a S/MIME signed message.\n\n--%s\n", boundary);
208 while ((c = getc(bp)) != EOF)
209 putc(c, op);
211 fprintf(op, "\n--%s\n", boundary);
212 fputs("Content-Type: application/pkcs7-signature; name=\"smime.p7s\"\n"
213 "Content-Transfer-Encoding: base64\n"
214 "Content-Disposition: attachment; filename=\"smime.p7s\"\n"
215 "Content-Description: S/MIME digital signature\n\n", op);
216 while ((c = getc(sp)) != EOF) {
217 if (c == '-') {
218 while ((c = getc(sp)) != EOF && c != '\n');
219 continue;
221 putc(c, op);
224 fprintf(op, "\n--%s--\n", boundary);
226 Fclose(hp);
227 Fclose(bp);
228 Fclose(sp);
230 fflush(op);
231 if (ferror(op)) {
232 n_perr(_("signed output data"), 0);
233 Fclose(op);
234 op = NULL;
235 goto jleave;
237 rewind(op);
238 jleave:
239 NYD_LEAVE;
240 return op;
243 FL FILE *
244 smime_encrypt_assemble(FILE *hp, FILE *yp)
246 FILE *op;
247 int c, lastc = EOF;
248 NYD_ENTER;
250 if ((op = Ftmp(NULL, "smimee", OF_RDWR | OF_UNLINK | OF_REGISTER)
251 ) == NULL) {
252 n_perr(_("tempfile"), 0);
253 goto jleave;
256 while ((c = getc(hp)) != EOF) {
257 if (c == '\n' && lastc == '\n')
258 break;
259 putc(c, op);
260 lastc = c;
263 fputs("Content-Type: application/pkcs7-mime; name=\"smime.p7m\"\n"
264 "Content-Transfer-Encoding: base64\n"
265 "Content-Disposition: attachment; filename=\"smime.p7m\"\n"
266 "Content-Description: S/MIME encrypted message\n\n", op);
267 while ((c = getc(yp)) != EOF) {
268 if (c == '-') {
269 while ((c = getc(yp)) != EOF && c != '\n');
270 continue;
272 putc(c, op);
275 Fclose(hp);
276 Fclose(yp);
278 fflush(op);
279 if (ferror(op)) {
280 n_perr(_("encrypted output data"), 0);
281 Fclose(op);
282 op = NULL;
283 goto jleave;
285 rewind(op);
286 jleave:
287 NYD_LEAVE;
288 return op;
291 FL struct message *
292 smime_decrypt_assemble(struct message *m, FILE *hp, FILE *bp)
294 ui32_t lastnl = 0;
295 int binary = 0;
296 char *buf = NULL;
297 size_t bufsize = 0, buflen, cnt;
298 long lns = 0, octets = 0;
299 struct message *x;
300 off_t offset;
301 NYD_ENTER;
303 x = n_autorec_alloc(sizeof *x);
304 *x = *m;
305 fflush(mb.mb_otf);
306 fseek(mb.mb_otf, 0L, SEEK_END);
307 offset = ftell(mb.mb_otf);
309 cnt = fsize(hp);
310 while (fgetline(&buf, &bufsize, &cnt, &buflen, hp, 0) != NULL) {
311 char const *cp;
312 if (buf[0] == '\n')
313 break;
314 if ((cp = thisfield(buf, "content-transfer-encoding")) != NULL)
315 if (!ascncasecmp(cp, "binary", 7))
316 binary = 1;
317 fwrite(buf, sizeof *buf, buflen, mb.mb_otf);
318 octets += buflen;
319 ++lns;
322 { struct time_current save = time_current;
323 time_current_update(&time_current, TRU1);
324 octets += mkdate(mb.mb_otf, "X-Decoding-Date");
325 time_current = save;
327 ++lns;
329 cnt = fsize(bp);
330 while (fgetline(&buf, &bufsize, &cnt, &buflen, bp, 0) != NULL) {
331 lns++;
332 if (!binary && buf[buflen - 1] == '\n' && buf[buflen - 2] == '\r')
333 buf[--buflen - 1] = '\n';
334 fwrite(buf, sizeof *buf, buflen, mb.mb_otf);
335 octets += buflen;
336 if (buf[0] == '\n')
337 ++lastnl;
338 else if (buf[buflen - 1] == '\n')
339 lastnl = 1;
340 else
341 lastnl = 0;
344 while (!binary && lastnl < 2) {
345 putc('\n', mb.mb_otf);
346 ++lns;
347 ++octets;
348 ++lastnl;
351 Fclose(hp);
352 Fclose(bp);
353 n_free(buf);
355 fflush(mb.mb_otf);
356 if (ferror(mb.mb_otf)) {
357 n_perr(_("decrypted output data"), 0);
358 x = NULL;
359 }else{
360 x->m_size = x->m_xsize = octets;
361 x->m_lines = x->m_xlines = lns;
362 x->m_block = mailx_blockof(offset);
363 x->m_offset = mailx_offsetof(offset);
365 NYD_LEAVE;
366 return x;
369 FL int
370 c_certsave(void *vp){
371 FILE *fp;
372 int *msgvec, *ip;
373 struct n_cmd_arg_ctx *cacp;
374 NYD_ENTER;
376 cacp = vp;
377 assert(cacp->cac_no == 2);
379 msgvec = cacp->cac_arg->ca_arg.ca_msglist;
380 /* C99 */{
381 char *file, *cp;
383 file = cacp->cac_arg->ca_next->ca_arg.ca_str.s;
384 if((cp = fexpand(file, FEXP_LOCAL_FILE | FEXP_NOPROTO)) == NULL ||
385 *cp == '\0'){
386 n_err(_("`certsave': file expansion failed: %s\n"),
387 n_shexp_quote_cp(file, FAL0));
388 vp = NULL;
389 goto jleave;
391 file = cp;
393 if((fp = Fopen(file, "a")) == NULL){
394 n_perr(file, 0);
395 vp = NULL;
396 goto jleave;
400 for(ip = msgvec; *ip != 0; ++ip)
401 if(smime_certsave(&message[*ip - 1], *ip, fp) != OKAY)
402 vp = NULL;
404 Fclose(fp);
406 if(vp != NULL)
407 fprintf(n_stdout, "Certificate(s) saved\n");
408 jleave:
409 NYD_LEAVE;
410 return (vp != NULL);
413 FL bool_t
414 n_tls_rfc2595_hostname_match(char const *host, char const *pattern){
415 bool_t rv;
416 NYD_ENTER;
418 if(pattern[0] == '*' && pattern[1] == '.'){
419 ++pattern;
420 while(*host && *host != '.')
421 ++host;
423 rv = (asccasecmp(host, pattern) == 0);
424 NYD_LEAVE;
425 return rv;
428 FL int
429 c_tls(void *vp){
430 size_t i;
431 char const **argv, *varname, *varres, *cp;
432 NYD_ENTER;
434 argv = vp;
435 vp = NULL; /* -> return value (boolean) */
436 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
437 varres = n_empty;
439 if((cp = argv[0])[0] == '\0')
440 goto jesubcmd;
441 else if(is_asccaseprefix(cp, "fingerprint")){
442 #ifndef HAVE_SOCKETS
443 n_err(_("`tls': fingerprint: no +sockets in *features*\n"));
444 n_pstate_err_no = n_ERR_OPNOTSUPP;
445 goto jleave;
446 #else
447 struct sock so;
448 struct url url;
450 if(argv[1] == NULL || argv[2] != NULL)
451 goto jesynopsis;
452 if((i = strlen(*++argv)) >= UI32_MAX)
453 goto jeoverflow; /* TODO generic for ALL commands!! */
454 if(!url_parse(&url, CPROTO_CERTINFO, *argv))
455 goto jeinval;
456 if(!sopen(&so, &url)){ /* auto-closes for CPROTO_CERTINFO on success */
457 n_pstate_err_no = n_err_no;
458 goto jleave;
460 if(so.s_tls_finger == NULL)
461 goto jeinval;
462 varres = so.s_tls_finger;
463 #endif /* HAVE_SOCKETS */
464 }else
465 goto jesubcmd;
467 n_pstate_err_no = n_ERR_NONE;
468 vp = (char*)-1;
469 jleave:
470 if(varname == NULL){
471 if(fprintf(n_stdout, "%s\n", varres) < 0){
472 n_pstate_err_no = n_err_no;
473 vp = NULL;
475 }else if(!n_var_vset(varname, (uintptr_t)varres)){
476 n_pstate_err_no = n_ERR_NOTSUP;
477 vp = NULL;
479 NYD_LEAVE;
480 return (vp == NULL);
482 jeoverflow:
483 n_err(_("`tls': string length or offset overflows datatype\n"));
484 n_pstate_err_no = n_ERR_OVERFLOW;
485 goto jleave;
487 jesubcmd:
488 n_err(_("`tls': invalid subcommand: %s\n"),
489 n_shexp_quote_cp(*argv, FAL0));
490 jesynopsis:
491 n_err(_("Synopsis: tls: <command> [<:argument:>]\n"));
492 jeinval:
493 n_pstate_err_no = n_ERR_INVAL;
494 goto jleave;
496 #endif /* HAVE_TLS */
498 /* s-it-mode */