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