1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Generic TLS / S/MIME commands.
3 *
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.
11 *
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.
27 *
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.
39 */
40 #undef su_FILE
41 #define su_FILE tls
42 #define mx_SOURCE
43
44 #ifndef mx_HAVE_AMALGAMATION
45 # include "mx/nail.h"
46 #endif
47
48 su_EMPTY_FILE()
49 #ifdef mx_HAVE_TLS
50 #include <su/cs.h>
51 #include <su/mem.h>
52
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"
58
59 /* TODO fake */
60 #include "su/code-in.h"
61
62 struct a_tls_verify_levels{
63 char const tv_name[8];
64 enum n_tls_verify_level tv_level;
65 };
66
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}
73 };
74
75 FL void
n_tls_set_verify_level(struct mx_url const * urlp)76 n_tls_set_verify_level(struct mx_url const *urlp){
77 uz i;
78 char const *cp;
79 NYD2_IN;
80
81 n_tls_verify_level = n_TLS_VERIFY_ASK;
82
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;
92 }
93 }
94 NYD2_OU;
95 }
96
97 FL boole
n_tls_verify_decide(void)98 n_tls_verify_decide(void){
99 boole rv;
100 NYD2_IN;
101
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;
114 }
115 NYD2_OU;
116 return rv;
117 }
118
119 FL boole
mx_smime_split(FILE * ip,FILE ** hp,FILE ** bp,long xcount,boole keep)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;
131
132 rv = FAL0;
133 mx_fs_linepool_aquire(&buf, &bufsize);
134 *hp = *bp = NIL;
135
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;
142
143 head = tail = NIL;
144 cnt = (xcount < 0) ? fsize(ip) : xcount;
145
146 for(;;){
147 if(fgetline(&buf, &bufsize, &cnt, &buflen, ip, FAL0) == NIL){
148 if(ferror(ip))
149 goto jleave;
150 break;
151 }
152 if(*buf == '\n')
153 break;
154
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;
160
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;
180 }
181 continue;
182 }
183
184 if(fwrite(buf, sizeof *buf, buflen, *hp) != buflen)
185 goto jleave;
186 }
187 fflush_rewind(*hp);
188
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);
196 }
197
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;
203 }
204 fflush_rewind(*bp);
205 if(ferror(ip))
206 goto jleave;
207
208 rv = OKAY;
209 jleave:
210 if(!rv){
211 if(*bp != NIL)
212 mx_fs_close(*bp);
213 if(*hp != NIL)
214 mx_fs_close(*hp);
215 }
216
217 mx_fs_linepool_release(buf, bufsize);
218
219 NYD_OU;
220 return rv;
221 }
222
223 FL FILE *
smime_sign_assemble(FILE * hp,FILE * bp,FILE * tsp,char const * message_digest)224 smime_sign_assemble(FILE *hp, FILE *bp, FILE *tsp, char const *message_digest)
225 {
226 char *boundary;
227 int c, lastc = EOF;
228 FILE *op;
229 NYD_IN;
230
231 if((op = mx_fs_tmp_open("smimea", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
232 mx_FS_O_REGISTER), NIL)) == NIL){
233 n_perr(_("tempfile"), 0);
234 goto jleave;
235 }
236
237 while ((c = getc(hp)) != EOF) {
238 if (c == '\n' && lastc == '\n')
239 break;
240 putc(c, op);
241 lastc = c;
242 }
243
244 boundary = mime_param_boundary_create();
245 fprintf(op, "Content-Type: multipart/signed;\n"
246 " protocol=\"application/pkcs7-signature\"; micalg=%s;\n"
247 " boundary=\"%s\"\n\n", message_digest, boundary);
248 fprintf(op, "This is a S/MIME signed message.\n\n--%s\n", boundary);
249 while ((c = getc(bp)) != EOF)
250 putc(c, op);
251
252 fprintf(op, "\n--%s\n", boundary);
253 fputs("Content-Type: application/pkcs7-signature; name=\"smime.p7s\"\n"
254 "Content-Transfer-Encoding: base64\n"
255 "Content-Disposition: attachment; filename=\"smime.p7s\"\n", op);
256 /* C99 */{
257 char const *cp;
258
259 if(*(cp = ok_vlook(content_description_smime_signature)) != '\0')
260 fprintf(op, "Content-Description: %s\n", cp);
261 }
262 putc('\n', op);
263
264 while ((c = getc(tsp)) != EOF) {
265 if (c == '-') {
266 while ((c = getc(tsp)) != EOF && c != '\n');
267 continue;
268 }
269 putc(c, op);
270 }
271
272 fprintf(op, "\n--%s--\n", boundary);
273
274 mx_fs_close(hp);
275 mx_fs_close(bp);
276 mx_fs_close(tsp);
277
278 fflush(op);
279 if (ferror(op)) {
280 n_perr(_("signed output data"), 0);
281 mx_fs_close(op);
282 op = NULL;
283 goto jleave;
284 }
285 rewind(op);
286 jleave:
287 NYD_OU;
288 return op;
289 }
290
291 FL FILE *
smime_encrypt_assemble(FILE * hp,FILE * yp)292 smime_encrypt_assemble(FILE *hp, FILE *yp)
293 {
294 FILE *op;
295 int c, lastc = EOF;
296 NYD_IN;
297
298 if((op = mx_fs_tmp_open("smimee", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
299 mx_FS_O_REGISTER), NIL)) == NIL){
300 n_perr(_("tempfile"), 0);
301 goto jleave;
302 }
303
304 while ((c = getc(hp)) != EOF) {
305 if (c == '\n' && lastc == '\n')
306 break;
307 putc(c, op);
308 lastc = c;
309 }
310
311 fputs("Content-Type: application/pkcs7-mime; name=\"smime.p7m\"\n"
312 "Content-Transfer-Encoding: base64\n"
313 "Content-Disposition: attachment; filename=\"smime.p7m\"\n", op);
314 /* C99 */{
315 char const *cp;
316
317 if(*(cp = ok_vlook(content_description_smime_message)) != '\0')
318 fprintf(op, "Content-Description: %s\n", cp);
319 }
320 putc('\n', op);
321
322 while ((c = getc(yp)) != EOF) {
323 if (c == '-') {
324 while ((c = getc(yp)) != EOF && c != '\n');
325 continue;
326 }
327 putc(c, op);
328 }
329
330 mx_fs_close(hp);
331 mx_fs_close(yp);
332
333 fflush(op);
334 if (ferror(op)) {
335 n_perr(_("encrypted output data"), 0);
336 mx_fs_close(op);
337 op = NULL;
338 goto jleave;
339 }
340 rewind(op);
341 jleave:
342 NYD_OU;
343 return op;
344 }
345
346 FL struct message *
mx_smime_decrypt_assemble(struct message * mp,FILE * hp,FILE * bp)347 mx_smime_decrypt_assemble(struct message *mp, FILE *hp, FILE *bp){
348 boole binary, lastnl;
349 long lns, octets;
350 off_t offset;
351 uz bufsize, buflen, cnt;
352 char *buf;
353 struct message *xmp;
354 NYD_IN;
355
356 xmp = NIL;
357 mx_fs_linepool_aquire(&buf, &bufsize);
358
359 fflush(mb.mb_otf);
360 fseek(mb.mb_otf, 0L, SEEK_END);
361 offset = ftell(mb.mb_otf);
362 lns = octets = 0;
363 binary = FAL0;
364
365 cnt = fsize(hp);
366 while(fgetline(&buf, &bufsize, &cnt, &buflen, hp, FAL0) != NIL){
367 char const *cp;
368
369 if(buf[0] == '\n')
370 break;
371 if((cp = n_header_get_field(buf, "content-transfer-encoding", NIL)
372 ) != NIL)
373 if(!su_cs_cmp_case_n(cp, "binary", 7))
374 binary = TRU1;
375 if(fwrite(buf, sizeof *buf, buflen, mb.mb_otf) != buflen)
376 goto jleave;
377 octets += buflen;
378 ++lns;
379 }
380 if(ferror(hp))
381 goto jleave;
382
383 /* C99 */{
384 struct time_current save;
385 int w;
386
387 save = time_current;
388 time_current_update(&time_current, TRU1);
389 if((w = mkdate(mb.mb_otf, "X-Decoding-Date")) == -1)
390 goto jleave;
391 octets += w;
392 time_current = save;
393 }
394 ++lns;
395
396 cnt = fsize(bp);
397 lastnl = FAL0;
398 while(fgetline(&buf, &bufsize, &cnt, &buflen, bp, FAL0) != NIL){
399 lns++;
400 if(!binary && buf[buflen - 1] == '\n' && buf[buflen - 2] == '\r')
401 buf[--buflen - 1] = '\n';
402 fwrite(buf, sizeof *buf, buflen, mb.mb_otf);
403 octets += buflen;
404 if(buf[0] == '\n')
405 lastnl = lastnl ? TRUM1 : TRU1;
406 else
407 lastnl = (buf[buflen - 1] == '\n');
408 }
409 if(ferror(bp))
410 goto jleave;
411
412 if(!binary && lastnl != TRUM1){
413 for(;;){
414 if(putc('\n', mb.mb_otf) == EOF)
415 goto jleave;
416 ++lns;
417 ++octets;
418 if(lastnl)
419 break;
420 lastnl = TRU1;
421 }
422 }
423
424 fflush(mb.mb_otf);
425 if(!ferror(mb.mb_otf)){
426 xmp = n_autorec_alloc(sizeof *xmp);
427 *xmp = *mp;
428 xmp->m_size = xmp->m_xsize = octets;
429 xmp->m_lines = xmp->m_xlines = lns;
430 xmp->m_block = mailx_blockof(offset);
431 xmp->m_offset = mailx_offsetof(offset);
432 }
433
434 jleave:
435 mx_fs_linepool_release(buf, bufsize);
436
437 NYD_OU;
438 return xmp;
439 }
440
441 FL int
c_certsave(void * vp)442 c_certsave(void *vp){
443 FILE *fp;
444 int *msgvec, *ip;
445 struct mx_cmd_arg_ctx *cacp;
446 NYD_IN;
447
448 cacp = vp;
449 ASSERT(cacp->cac_no == 2);
450
451 msgvec = cacp->cac_arg->ca_arg.ca_msglist;
452 /* C99 */{
453 char *file, *cp;
454
455 file = cacp->cac_arg->ca_next->ca_arg.ca_str.s;
456 if((cp = fexpand(file, (FEXP_NOPROTO | FEXP_LOCAL_FILE | FEXP_NSHELL))
457 ) == NIL || *cp == '\0'){
458 n_err(_("certsave: file expansion failed: %s\n"),
459 n_shexp_quote_cp(file, FAL0));
460 vp = NULL;
461 goto jleave;
462 }
463 file = cp;
464
465 if((fp = mx_fs_open(file, "a")) == NIL){
466 n_perr(file, 0);
467 vp = NULL;
468 goto jleave;
469 }
470 }
471
472 for(ip = msgvec; *ip != 0; ++ip)
473 if(smime_certsave(&message[*ip - 1], *ip, fp) != OKAY)
474 vp = NULL;
475
476 mx_fs_close(fp);
477
478 if(vp != NULL)
479 fprintf(n_stdout, "Certificate(s) saved\n");
480 jleave:
481 NYD_OU;
482 return (vp != NULL);
483 }
484
485 FL boole
n_tls_rfc2595_hostname_match(char const * host,char const * pattern)486 n_tls_rfc2595_hostname_match(char const *host, char const *pattern){
487 boole rv;
488 NYD_IN;
489
490 if(pattern[0] == '*' && pattern[1] == '.'){
491 ++pattern;
492 while(*host && *host != '.')
493 ++host;
494 }
495 rv = (su_cs_cmp_case(host, pattern) == 0);
496 NYD_OU;
497 return rv;
498 }
499
500 FL int
c_tls(void * vp)501 c_tls(void *vp){
502 #ifdef mx_HAVE_NET
503 struct mx_socket so;
504 struct mx_url url;
505 #endif
506 uz i;
507 enum {a_FPRINT, a_CERTIFICATE, a_CERTCHAIN} mode;
508 char const **argv, *varname, *varres, *cp;
509 NYD_IN;
510
511 argv = vp;
512 vp = NULL; /* -> return value (boolean) */
513 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
514 varres = n_empty;
515
516 if((cp = argv[0])[0] == '\0')
517 goto jesubcmd;
518 else if(su_cs_starts_with_case("fingerprint", cp))
519 mode = a_FPRINT;
520 else if(su_cs_starts_with_case("certificate", cp))
521 mode = a_CERTIFICATE;
522 else if(su_cs_starts_with_case("certchain", cp))
523 mode = a_CERTCHAIN;
524 else
525 goto jesubcmd;
526
527 #ifndef mx_HAVE_NET
528 n_err(_("tls: fingerprint: no +sockets in *features*\n"));
529 n_pstate_err_no = su_ERR_OPNOTSUPP;
530 goto jleave;
531 #else
532 if(argv[1] == NULL || argv[2] != NULL)
533 goto jesynopsis;
534 if((i = su_cs_len(*++argv)) >= U32_MAX)
535 goto jeoverflow; /* TODO generic for ALL commands!! */
536 if(!mx_url_parse(&url, CPROTO_CERTINFO, *argv))
537 goto jeinval;
538
539 if(!mx_socket_open(&so, &url)){
540 n_pstate_err_no = su_err_no();
541 goto jleave;
542 }
543 mx_socket_close(&so);
544
545 switch(mode){
546 case a_FPRINT:
547 if(so.s_tls_finger == NIL)
548 goto jeinval;
549 varres = so.s_tls_finger;
550 break;
551 case a_CERTIFICATE:
552 if(so.s_tls_certificate == NIL)
553 goto jeinval;
554 varres = so.s_tls_certificate;
555 break;
556 case a_CERTCHAIN:
557 if(so.s_tls_certchain == NIL)
558 goto jeinval;
559 varres = so.s_tls_certchain;
560 break;
561 }
562 #endif /* mx_HAVE_NET */
563
564 n_pstate_err_no = su_ERR_NONE;
565 vp = (char*)-1;
566 jleave:
567 if(varname == NULL){
568 if(fprintf(n_stdout, "%s\n", varres) < 0){
569 n_pstate_err_no = su_err_no();
570 vp = NULL;
571 }
572 }else if(!n_var_vset(varname, (up)varres)){
573 n_pstate_err_no = su_ERR_NOTSUP;
574 vp = NULL;
575 }
576 NYD_OU;
577 return (vp == NULL);
578
579 jeoverflow:
580 n_err(_("tls: string length or offset overflows datatype\n"));
581 n_pstate_err_no = su_ERR_OVERFLOW;
582 goto jleave;
583
584 jesubcmd:
585 n_err(_("tls: invalid subcommand: %s\n"),
586 n_shexp_quote_cp(*argv, FAL0));
587 jesynopsis:
588 mx_cmd_print_synopsis(mx_cmd_firstfit("tls"), NIL);
589 jeinval:
590 n_pstate_err_no = su_ERR_INVAL;
591 goto jleave;
592 }
593
594 #include "su/code-ou.h"
595 #endif /* mx_HAVE_TLS */
596 /* s-it-mode */
597