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!)
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
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
44 #ifndef mx_HAVE_AMALGAMATION
54 #include "mx/file-streams.h"
55 #include "mx/net-socket.h"
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
}
76 n_tls_set_verify_level(struct mx_url
const *urlp
){
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
){
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
;
89 }else if(++i
>= NELEM(a_tls_verify_levels
)){
90 n_err(_("Invalid value of *tls-verify*: %s\n"), cp
);
98 n_tls_verify_decide(void){
102 switch(n_tls_verify_level
){
104 case n_TLS_VERIFY_STRICT
:
107 case n_TLS_VERIFY_ASK
:
108 rv
= mx_tty_yesorno(NIL
, FAL0
);
110 case n_TLS_VERIFY_WARN
:
111 case n_TLS_VERIFY_IGNORE
:
120 mx_smime_split(FILE *ip
, FILE **hp
, FILE **bp
, long xcount
, boole keep
){
122 struct myline
*ml_next
;
124 char ml_buf
[VFIELD_SIZE(0)];
127 uz bufsize
, buflen
, cnt
;
133 mx_fs_linepool_aquire(&buf
, &bufsize
);
136 if((*hp
= mx_fs_tmp_open("smimeh", (mx_FS_O_RDWR
| mx_FS_O_UNLINK
|
137 mx_FS_O_REGISTER
), NIL
)) == NIL
)
139 if((*bp
= mx_fs_tmp_open("smimeb", (mx_FS_O_RDWR
| mx_FS_O_UNLINK
|
140 mx_FS_O_REGISTER
), NIL
)) == NIL
)
144 cnt
= (xcount
< 0) ? fsize(ip
) : xcount
;
147 if(fgetline(&buf
, &bufsize
, &cnt
, &buflen
, ip
, FAL0
) == NIL
){
155 if(!su_cs_cmp_case_n(buf
, "content-", 8)){
156 if(keep
&& fputs("X-Encoded-", *hp
) == EOF
)
161 ml
= n_lofi_alloc(VSTRUCT_SIZEOF(struct myline
, ml_buf
) +
170 su_mem_copy(ml
->ml_buf
, buf
, buflen
+1);
171 if(keep
&& fwrite(buf
, sizeof *buf
, buflen
, *hp
) != buflen
)
173 if((c
= getc(ip
)) == EOF
|| ungetc(c
, ip
) == EOF
)
175 if(!su_cs_is_blank(c
))
177 if(fgetline(&buf
, &bufsize
, &cnt
, &buflen
, ip
, FAL0
) == NIL
&&
184 if(fwrite(buf
, sizeof *buf
, buflen
, *hp
) != buflen
)
190 if(fwrite(head
->ml_buf
, sizeof *head
->ml_buf
, head
->ml_len
, *bp
194 head
= head
->ml_next
;
198 if(putc('\n', *bp
) == EOF
)
200 while(fgetline(&buf
, &bufsize
, &cnt
, &buflen
, ip
, FAL0
) != NIL
){
201 if(fwrite(buf
, sizeof *buf
, buflen
, *bp
) != buflen
)
221 mx_fs_linepool_release(buf
, bufsize
);
228 smime_sign_assemble(FILE *hp
, FILE *bp
, FILE *tsp
, char const *message_digest
)
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);
241 while ((c
= getc(hp
)) != EOF
) {
242 if (c
== '\n' && lastc
== '\n')
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
)
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
);
263 if(*(cp
= ok_vlook(content_description_smime_signature
)) != '\0')
264 fprintf(op
, "Content-Description: %s\n", cp
);
268 while ((c
= getc(tsp
)) != EOF
) {
270 while ((c
= getc(tsp
)) != EOF
&& c
!= '\n');
276 fprintf(op
, "\n--%s--\n", boundary
);
284 n_perr(_("signed output data"), 0);
296 smime_encrypt_assemble(FILE *hp
, FILE *yp
)
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);
308 while ((c
= getc(hp
)) != EOF
) {
309 if (c
== '\n' && lastc
== '\n')
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
);
321 if(*(cp
= ok_vlook(content_description_smime_message
)) != '\0')
322 fprintf(op
, "Content-Description: %s\n", cp
);
326 while ((c
= getc(yp
)) != EOF
) {
328 while ((c
= getc(yp
)) != EOF
&& c
!= '\n');
339 n_perr(_("encrypted output data"), 0);
351 mx_smime_decrypt_assemble(struct message
*mp
, FILE *hp
, FILE *bp
){
352 boole binary
, lastnl
;
355 uz bufsize
, buflen
, cnt
;
361 mx_fs_linepool_aquire(&buf
, &bufsize
);
364 fseek(mb
.mb_otf
, 0L, SEEK_END
);
365 offset
= ftell(mb
.mb_otf
);
370 while(fgetline(&buf
, &bufsize
, &cnt
, &buflen
, hp
, FAL0
) != NIL
){
375 if((cp
= n_header_get_field(buf
, "content-transfer-encoding", NIL
)
377 if(!su_cs_cmp_case_n(cp
, "binary", 7))
379 if(fwrite(buf
, sizeof *buf
, buflen
, mb
.mb_otf
) != buflen
)
388 struct time_current save
;
392 time_current_update(&time_current
, TRU1
);
393 if((w
= mkdate(mb
.mb_otf
, "X-Decoding-Date")) == -1)
402 while(fgetline(&buf
, &bufsize
, &cnt
, &buflen
, bp
, FAL0
) != NIL
){
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
);
409 lastnl
= lastnl
? TRUM1
: TRU1
;
411 lastnl
= (buf
[buflen
- 1] == '\n');
416 if(!binary
&& lastnl
!= TRUM1
){
418 if(putc('\n', mb
.mb_otf
) == EOF
)
429 if(!ferror(mb
.mb_otf
)){
430 xmp
= n_autorec_alloc(sizeof *xmp
);
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
);
439 mx_fs_linepool_release(buf
, bufsize
);
446 c_certsave(void *vp
){
449 struct mx_cmd_arg_ctx
*cacp
;
453 ASSERT(cacp
->cac_no
== 2);
455 msgvec
= cacp
->cac_arg
->ca_arg
.ca_msglist
;
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
));
469 if((fp
= mx_fs_open(file
, "a")) == NIL
){
476 for(ip
= msgvec
; *ip
!= 0; ++ip
)
477 if(smime_certsave(&message
[*ip
- 1], *ip
, fp
) != OKAY
)
483 fprintf(n_stdout
, "Certificate(s) saved\n");
490 n_tls_rfc2595_hostname_match(char const *host
, char const *pattern
){
494 if(pattern
[0] == '*' && pattern
[1] == '.'){
496 while(*host
&& *host
!= '.')
499 rv
= (su_cs_cmp_case(host
, pattern
) == 0);
511 enum {a_FPRINT
, a_CERTIFICATE
, a_CERTCHAIN
} mode
;
512 char const **argv
, *varname
, *varres
, *cp
;
516 vp
= NULL
; /* -> return value (boolean) */
517 varname
= (n_pstate
& n_PS_ARGMOD_VPUT
) ? *argv
++ : NULL
;
520 if((cp
= argv
[0])[0] == '\0')
522 else if(su_cs_starts_with_case("fingerprint", cp
))
524 else if(su_cs_starts_with_case("certificate", cp
))
525 mode
= a_CERTIFICATE
;
526 else if(su_cs_starts_with_case("certchain", cp
))
532 n_err(_("tls: fingerprint: no +sockets in *features*\n"));
533 n_pstate_err_no
= su_ERR_OPNOTSUPP
;
536 if(argv
[1] == NULL
|| argv
[2] != NULL
)
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
))
543 if(!mx_socket_open(&so
, &url
)){
544 n_pstate_err_no
= su_err_no();
547 mx_socket_close(&so
);
551 if(so
.s_tls_finger
== NIL
)
553 varres
= so
.s_tls_finger
;
556 if(so
.s_tls_certificate
== NIL
)
558 varres
= so
.s_tls_certificate
;
561 if(so
.s_tls_certchain
== NIL
)
563 varres
= so
.s_tls_certchain
;
566 #endif /* mx_HAVE_NET */
568 n_pstate_err_no
= su_ERR_NONE
;
572 if(fprintf(n_stdout
, "%s\n", varres
) < 0){
573 n_pstate_err_no
= su_err_no();
576 }else if(!n_var_vset(varname
, (up
)varres
)){
577 n_pstate_err_no
= su_ERR_NOTSUP
;
584 n_err(_("tls: string length or offset overflows datatype\n"));
585 n_pstate_err_no
= su_ERR_OVERFLOW
;
589 n_err(_("tls: invalid subcommand: %s\n"),
590 n_shexp_quote_cp(*argv
, FAL0
));
592 mx_cmd_print_synopsis(mx_cmd_firstfit("tls"), NIL
);
594 n_pstate_err_no
= su_ERR_INVAL
;
598 #include "su/code-ou.h"
599 #endif /* mx_HAVE_TLS */