Bump S-nail v14.9.25 ("Lubimy Gorod"), 2024-06-27
[s-mailx.git] / src / mx / tls.c
blob8d0a97359de40062967a7076b054f7b6359f2b12
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 - 2020 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 su_FILE
41 #define su_FILE tls
42 #define mx_SOURCE
44 #ifndef mx_HAVE_AMALGAMATION
45 # include "mx/nail.h"
46 #endif
48 su_EMPTY_FILE()
49 #ifdef mx_HAVE_TLS
50 #include <su/cs.h>
51 #include <su/mem.h>
53 #include "mx/cmd.h"
54 #include "mx/file-streams.h"
55 #include "mx/net-socket.h"
56 #include "mx/tty.h"
57 #include "mx/url.h"
59 /* TODO fake */
60 #include "su/code-in.h"
62 struct a_tls_verify_levels{
63 char const tv_name[8];
64 enum n_tls_verify_level tv_level;
67 /* Supported SSL/TLS verification methods: update manual on change! */
68 static struct a_tls_verify_levels const a_tls_verify_levels[] = {
69 {"strict", n_TLS_VERIFY_STRICT},
70 {"ask", n_TLS_VERIFY_ASK},
71 {"warn", n_TLS_VERIFY_WARN},
72 {"ignore", n_TLS_VERIFY_IGNORE}
75 FL void
76 n_tls_set_verify_level(struct mx_url const *urlp){
77 uz i;
78 char const *cp;
79 NYD2_IN;
81 n_tls_verify_level = n_TLS_VERIFY_ASK;
83 if((cp = xok_vlook(tls_verify, urlp, OXM_ALL)) != NULL ||
84 (cp = xok_vlook(ssl_verify, urlp, OXM_ALL)) != NULL){
85 for(i = 0;;)
86 if(!su_cs_cmp_case(a_tls_verify_levels[i].tv_name, cp)){
87 n_tls_verify_level = a_tls_verify_levels[i].tv_level;
88 break;
89 }else if(++i >= NELEM(a_tls_verify_levels)){
90 n_err(_("Invalid value of *tls-verify*: %s\n"), cp);
91 break;
94 NYD2_OU;
97 FL boole
98 n_tls_verify_decide(void){
99 boole rv;
100 NYD2_IN;
102 switch(n_tls_verify_level){
103 default:
104 case n_TLS_VERIFY_STRICT:
105 rv = FAL0;
106 break;
107 case n_TLS_VERIFY_ASK:
108 rv = mx_tty_yesorno(NIL, FAL0);
109 break;
110 case n_TLS_VERIFY_WARN:
111 case n_TLS_VERIFY_IGNORE:
112 rv = TRU1;
113 break;
115 NYD2_OU;
116 return rv;
119 FL boole
120 mx_smime_split(FILE *ip, FILE **hp, FILE **bp, long xcount, boole keep){
121 struct myline{
122 struct myline *ml_next;
123 uz ml_len;
124 char ml_buf[VFIELD_SIZE(0)];
125 } *head, *tail;
126 int c;
127 uz bufsize, buflen, cnt;
128 char *buf;
129 boole rv;
130 NYD_IN;
132 rv = FAL0;
133 mx_fs_linepool_aquire(&buf, &bufsize);
134 *hp = *bp = NIL;
136 if((*hp = mx_fs_tmp_open("smimeh", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
137 mx_FS_O_REGISTER), NIL)) == NIL)
138 goto jleave;
139 if((*bp = mx_fs_tmp_open("smimeb", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
140 mx_FS_O_REGISTER), NIL)) == NIL)
141 goto jleave;
143 head = tail = NIL;
144 cnt = (xcount < 0) ? fsize(ip) : xcount;
146 for(;;){
147 if(fgetline(&buf, &bufsize, &cnt, &buflen, ip, FAL0) == NIL){
148 if(ferror(ip))
149 goto jleave;
150 break;
152 if(*buf == '\n')
153 break;
155 if(!su_cs_cmp_case_n(buf, "content-", 8)){
156 if(keep && fputs("X-Encoded-", *hp) == EOF)
157 goto jleave;
158 for(;;){
159 struct myline *ml;
161 ml = n_lofi_alloc(VSTRUCT_SIZEOF(struct myline, ml_buf) +
162 buflen +1);
163 if(tail != NIL)
164 tail->ml_next = ml;
165 else
166 head = ml;
167 tail = ml;
168 ml->ml_next = NIL;
169 ml->ml_len = buflen;
170 su_mem_copy(ml->ml_buf, buf, buflen +1);
171 if(keep && fwrite(buf, sizeof *buf, buflen, *hp) != buflen)
172 goto jleave;
173 if((c = getc(ip)) == EOF || ungetc(c, ip) == EOF)
174 goto jleave;
175 if(!su_cs_is_blank(c))
176 break;
177 if(fgetline(&buf, &bufsize, &cnt, &buflen, ip, FAL0) == NIL &&
178 ferror(ip))
179 goto jleave;
181 continue;
184 if(fwrite(buf, sizeof *buf, buflen, *hp) != buflen)
185 goto jleave;
187 fflush_rewind(*hp);
189 while(head != NIL){
190 if(fwrite(head->ml_buf, sizeof *head->ml_buf, head->ml_len, *bp
191 ) != head->ml_len)
192 goto jleave;
193 tail = head;
194 head = head->ml_next;
195 n_lofi_free(tail);
198 if(putc('\n', *bp) == EOF)
199 goto jleave;
200 while(fgetline(&buf, &bufsize, &cnt, &buflen, ip, FAL0) != NIL){
201 if(fwrite(buf, sizeof *buf, buflen, *bp) != buflen)
202 goto jleave;
204 fflush_rewind(*bp);
205 if(ferror(ip))
206 goto jleave;
208 rv = OKAY;
209 jleave:
210 if(rv != OKAY){
211 if(*bp != NIL){
212 mx_fs_close(*bp);
213 *bp = NIL;
215 if(*hp != NIL){
216 mx_fs_close(*hp);
217 *hp = NIL;
221 mx_fs_linepool_release(buf, bufsize);
223 NYD_OU;
224 return rv;
227 FL FILE *
228 smime_sign_assemble(FILE *hp, FILE *bp, FILE *tsp, char const *message_digest)
230 char *boundary;
231 int c, lastc = EOF;
232 FILE *op;
233 NYD_IN;
235 if((op = mx_fs_tmp_open("smimea", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
236 mx_FS_O_REGISTER), NIL)) == NIL){
237 n_perr(_("tempfile"), 0);
238 goto jleave;
241 while ((c = getc(hp)) != EOF) {
242 if (c == '\n' && lastc == '\n')
243 break;
244 putc(c, op);
245 lastc = c;
248 boundary = mime_param_boundary_create();
249 fprintf(op, "Content-Type: multipart/signed;\n"
250 " protocol=\"application/pkcs7-signature\"; micalg=%s;\n"
251 " boundary=\"%s\"\n\n", message_digest, boundary);
252 fprintf(op, "This is a S/MIME signed message.\n\n--%s\n", boundary);
253 while ((c = getc(bp)) != EOF)
254 putc(c, op);
256 fprintf(op, "\n--%s\n", boundary);
257 fputs("Content-Type: application/pkcs7-signature; name=\"smime.p7s\"\n"
258 "Content-Transfer-Encoding: base64\n"
259 "Content-Disposition: attachment; filename=\"smime.p7s\"\n", op);
260 /* C99 */{
261 char const *cp;
263 if(*(cp = ok_vlook(content_description_smime_signature)) != '\0')
264 fprintf(op, "Content-Description: %s\n", cp);
266 putc('\n', op);
268 while ((c = getc(tsp)) != EOF) {
269 if (c == '-') {
270 while ((c = getc(tsp)) != EOF && c != '\n');
271 continue;
273 putc(c, op);
276 fprintf(op, "\n--%s--\n", boundary);
278 mx_fs_close(hp);
279 mx_fs_close(bp);
280 mx_fs_close(tsp);
282 fflush(op);
283 if (ferror(op)) {
284 n_perr(_("signed output data"), 0);
285 mx_fs_close(op);
286 op = NULL;
287 goto jleave;
289 rewind(op);
290 jleave:
291 NYD_OU;
292 return op;
295 FL FILE *
296 smime_encrypt_assemble(FILE *hp, FILE *yp)
298 FILE *op;
299 int c, lastc = EOF;
300 NYD_IN;
302 if((op = mx_fs_tmp_open("smimee", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
303 mx_FS_O_REGISTER), NIL)) == NIL){
304 n_perr(_("tempfile"), 0);
305 goto jleave;
308 while ((c = getc(hp)) != EOF) {
309 if (c == '\n' && lastc == '\n')
310 break;
311 putc(c, op);
312 lastc = c;
315 fputs("Content-Type: application/pkcs7-mime; name=\"smime.p7m\"\n"
316 "Content-Transfer-Encoding: base64\n"
317 "Content-Disposition: attachment; filename=\"smime.p7m\"\n", op);
318 /* C99 */{
319 char const *cp;
321 if(*(cp = ok_vlook(content_description_smime_message)) != '\0')
322 fprintf(op, "Content-Description: %s\n", cp);
324 putc('\n', op);
326 while ((c = getc(yp)) != EOF) {
327 if (c == '-') {
328 while ((c = getc(yp)) != EOF && c != '\n');
329 continue;
331 putc(c, op);
334 mx_fs_close(hp);
335 mx_fs_close(yp);
337 fflush(op);
338 if (ferror(op)) {
339 n_perr(_("encrypted output data"), 0);
340 mx_fs_close(op);
341 op = NULL;
342 goto jleave;
344 rewind(op);
345 jleave:
346 NYD_OU;
347 return op;
350 FL struct message *
351 mx_smime_decrypt_assemble(struct message *mp, FILE *hp, FILE *bp){
352 boole binary, lastnl;
353 long lns, octets;
354 off_t offset;
355 uz bufsize, buflen, cnt;
356 char *buf;
357 struct message *xmp;
358 NYD_IN;
360 xmp = NIL;
361 mx_fs_linepool_aquire(&buf, &bufsize);
363 fflush(mb.mb_otf);
364 fseek(mb.mb_otf, 0L, SEEK_END);
365 offset = ftell(mb.mb_otf);
366 lns = octets = 0;
367 binary = FAL0;
369 cnt = fsize(hp);
370 while(fgetline(&buf, &bufsize, &cnt, &buflen, hp, FAL0) != NIL){
371 char const *cp;
373 if(buf[0] == '\n')
374 break;
375 if((cp = n_header_get_field(buf, "content-transfer-encoding", NIL)
376 ) != NIL)
377 if(!su_cs_cmp_case_n(cp, "binary", 7))
378 binary = TRU1;
379 if(fwrite(buf, sizeof *buf, buflen, mb.mb_otf) != buflen)
380 goto jleave;
381 octets += buflen;
382 ++lns;
384 if(ferror(hp))
385 goto jleave;
387 /* C99 */{
388 struct time_current save;
389 int w;
391 save = time_current;
392 time_current_update(&time_current, TRU1);
393 if((w = mkdate(mb.mb_otf, "X-Decoding-Date")) == -1)
394 goto jleave;
395 octets += w;
396 time_current = save;
398 ++lns;
400 cnt = fsize(bp);
401 lastnl = FAL0;
402 while(fgetline(&buf, &bufsize, &cnt, &buflen, bp, FAL0) != NIL){
403 lns++;
404 if(!binary && buf[buflen - 1] == '\n' && buf[buflen - 2] == '\r')
405 buf[--buflen - 1] = '\n';
406 fwrite(buf, sizeof *buf, buflen, mb.mb_otf);
407 octets += buflen;
408 if(buf[0] == '\n')
409 lastnl = lastnl ? TRUM1 : TRU1;
410 else
411 lastnl = (buf[buflen - 1] == '\n');
413 if(ferror(bp))
414 goto jleave;
416 if(!binary && lastnl != TRUM1){
417 for(;;){
418 if(putc('\n', mb.mb_otf) == EOF)
419 goto jleave;
420 ++lns;
421 ++octets;
422 if(lastnl)
423 break;
424 lastnl = TRU1;
428 fflush(mb.mb_otf);
429 if(!ferror(mb.mb_otf)){
430 xmp = n_autorec_alloc(sizeof *xmp);
431 *xmp = *mp;
432 xmp->m_size = xmp->m_xsize = octets;
433 xmp->m_lines = xmp->m_xlines = lns;
434 xmp->m_block = mailx_blockof(offset);
435 xmp->m_offset = mailx_offsetof(offset);
438 jleave:
439 mx_fs_linepool_release(buf, bufsize);
441 NYD_OU;
442 return xmp;
445 FL int
446 c_certsave(void *vp){
447 FILE *fp;
448 int *msgvec, *ip;
449 struct mx_cmd_arg_ctx *cacp;
450 NYD_IN;
452 cacp = vp;
453 ASSERT(cacp->cac_no == 2);
455 msgvec = cacp->cac_arg->ca_arg.ca_msglist;
456 /* C99 */{
457 char *file, *cp;
459 file = cacp->cac_arg->ca_next->ca_arg.ca_str.s;
460 if((cp = fexpand(file, (FEXP_NOPROTO | FEXP_LOCAL_FILE | FEXP_NSHELL))
461 ) == NIL || *cp == '\0'){
462 n_err(_("certsave: file expansion failed: %s\n"),
463 n_shexp_quote_cp(file, FAL0));
464 vp = NULL;
465 goto jleave;
467 file = cp;
469 if((fp = mx_fs_open(file, "a")) == NIL){
470 n_perr(file, 0);
471 vp = NULL;
472 goto jleave;
476 for(ip = msgvec; *ip != 0; ++ip)
477 if(smime_certsave(&message[*ip - 1], *ip, fp) != OKAY)
478 vp = NULL;
480 mx_fs_close(fp);
482 if(vp != NULL)
483 fprintf(n_stdout, "Certificate(s) saved\n");
484 jleave:
485 NYD_OU;
486 return (vp != NULL);
489 FL boole
490 n_tls_rfc2595_hostname_match(char const *host, char const *pattern){
491 boole rv;
492 NYD_IN;
494 if(pattern[0] == '*' && pattern[1] == '.'){
495 ++pattern;
496 while(*host && *host != '.')
497 ++host;
499 rv = (su_cs_cmp_case(host, pattern) == 0);
500 NYD_OU;
501 return rv;
504 FL int
505 c_tls(void *vp){
506 #ifdef mx_HAVE_NET
507 struct mx_socket so;
508 struct mx_url url;
509 #endif
510 uz i;
511 enum {a_FPRINT, a_CERTIFICATE, a_CERTCHAIN} mode;
512 char const **argv, *varname, *varres, *cp;
513 NYD_IN;
515 argv = vp;
516 vp = NULL; /* -> return value (boolean) */
517 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
518 varres = n_empty;
520 if((cp = argv[0])[0] == '\0')
521 goto jesubcmd;
522 else if(su_cs_starts_with_case("fingerprint", cp))
523 mode = a_FPRINT;
524 else if(su_cs_starts_with_case("certificate", cp))
525 mode = a_CERTIFICATE;
526 else if(su_cs_starts_with_case("certchain", cp))
527 mode = a_CERTCHAIN;
528 else
529 goto jesubcmd;
531 #ifndef mx_HAVE_NET
532 n_err(_("tls: fingerprint: no +sockets in *features*\n"));
533 n_pstate_err_no = su_ERR_OPNOTSUPP;
534 goto jleave;
535 #else
536 if(argv[1] == NULL || argv[2] != NULL)
537 goto jesynopsis;
538 if((i = su_cs_len(*++argv)) >= U32_MAX)
539 goto jeoverflow; /* TODO generic for ALL commands!! */
540 if(!mx_url_parse(&url, CPROTO_CERTINFO, *argv))
541 goto jeinval;
543 if(!mx_socket_open(&so, &url)){
544 n_pstate_err_no = su_err_no();
545 goto jleave;
547 mx_socket_close(&so);
549 switch(mode){
550 case a_FPRINT:
551 if(so.s_tls_finger == NIL)
552 goto jeinval;
553 varres = so.s_tls_finger;
554 break;
555 case a_CERTIFICATE:
556 if(so.s_tls_certificate == NIL)
557 goto jeinval;
558 varres = so.s_tls_certificate;
559 break;
560 case a_CERTCHAIN:
561 if(so.s_tls_certchain == NIL)
562 goto jeinval;
563 varres = so.s_tls_certchain;
564 break;
566 #endif /* mx_HAVE_NET */
568 n_pstate_err_no = su_ERR_NONE;
569 vp = (char*)-1;
570 jleave:
571 if(varname == NULL){
572 if(fprintf(n_stdout, "%s\n", varres) < 0){
573 n_pstate_err_no = su_err_no();
574 vp = NULL;
576 }else if(!n_var_vset(varname, (up)varres)){
577 n_pstate_err_no = su_ERR_NOTSUP;
578 vp = NULL;
580 NYD_OU;
581 return (vp == NULL);
583 jeoverflow:
584 n_err(_("tls: string length or offset overflows datatype\n"));
585 n_pstate_err_no = su_ERR_OVERFLOW;
586 goto jleave;
588 jesubcmd:
589 n_err(_("tls: invalid subcommand: %s\n"),
590 n_shexp_quote_cp(*argv, FAL0));
591 jesynopsis:
592 mx_cmd_print_synopsis(mx_cmd_firstfit("tls"), NIL);
593 jeinval:
594 n_pstate_err_no = su_ERR_INVAL;
595 goto jleave;
598 #include "su/code-ou.h"
599 #endif /* mx_HAVE_TLS */
600 /* s-it-mode */