562393807be30afaf0f8471e748d6ccf56993407
[apps/madmutt.git] / lib-sys / mutt_ssl.cpkg
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 2001 Marco d'Itri <md@linux.it>
4  * Copyright (C) 2001-2004 Andrew McDonald <andrew@mcdonald.org.uk>
5  *
6  * This file is part of mutt-ng, see http://www.muttng.org/.
7  * It's licensed under the GNU General Public License,
8  * please see the file GPL in the top level source directory.
9  */
10
11 #include <lib-lib/lib-lib.h>
12
13 #include <gnutls/gnutls.h>
14 #include <gnutls/x509.h>
15
16 #include <lib-ui/lib-ui.h>
17 #include <lib-ui/menu.h>
18
19 #include "mutt.h"
20 #include "mutt_socket.h"
21
22 @import  "../lib-lua/base.cpkg"
23
24 @package mod_ssl {
25     bool force_tls = 0;
26     /*
27      ** .pp
28      ** If this variable is \fIset\fP, Madmutt will require that all connections
29      ** to remote servers be encrypted. Furthermore it will attempt to
30      ** negotiate TLS even if the server does not advertise the capability,
31      ** since it would otherwise have to abort the connection anyway. This
32      ** option supersedes ``$$ssl_starttls''.
33      */
34     bool starttls = 1;
35     /*
36      ** .pp
37      ** If \fIset\fP (the default), Madmutt will attempt to use STARTTLS on servers
38      ** advertising the capability. When \fIunset\fP, Madmutt will not attempt to
39      ** use STARTTLS regardless of the server's capabilities.
40      */
41     bool use_sslv3 = 1;
42     /*
43      ** .pp
44      ** This variables specifies whether to attempt to use SSLv3 in the
45      ** SSL authentication process.
46      */
47
48     path_t cert_file = luaM_pathnew("~/.cache/madmutt/certificates");
49     /*
50      ** .pp
51      ** This variable specifies the file where the certificates you trust
52      ** are saved. When an unknown certificate is encountered, you are asked
53      ** if you accept it or not. If you accept it, the certificate can also
54      ** be saved in this file and further connections are automatically
55      ** accepted.
56      ** .pp
57      ** You can also manually add CA certificates in this file. Any server
58      ** certificate that is signed with one of these CA certificates are
59      ** also automatically accepted.
60      ** .pp
61      ** Example: \fTset certificate_file=~/.madmutt/certificates\fP
62      */
63
64     path_t ca_certificates_file = NULL;
65     /*
66      ** .pp
67      ** This variable specifies a file containing trusted CA certificates.
68      ** Any server certificate that is signed with one of these CA
69      ** certificates are also automatically accepted.
70      ** .pp
71      ** Example: \fTset ssl_ca_certificates_file=/etc/ssl/certs/ca-certificates.crt\fP
72      */
73 };
74
75 typedef struct _tlssockdata {
76   gnutls_session state;
77   gnutls_certificate_credentials xcred;
78 } tlssockdata;
79
80 /* local prototypes */
81 static int tls_socket_read (CONNECTION * conn, char *buf, ssize_t len);
82 static int tls_socket_write (CONNECTION * conn, const char *buf, ssize_t len);
83 static int tls_socket_open (CONNECTION * conn);
84 static int tls_socket_close (CONNECTION * conn);
85 static int tls_starttls_close (CONNECTION * conn);
86
87 static int tls_init (void);
88 static int tls_negotiate (CONNECTION * conn);
89 static int tls_check_certificate (CONNECTION * conn);
90
91
92 static int tls_init (void)
93 {
94   static unsigned char init_complete = 0;
95   int err;
96
97   if (init_complete)
98     return 0;
99
100   err = gnutls_global_init ();
101   if (err < 0) {
102     mutt_error (_("gnutls_global_init: %s"), gnutls_strerror (err));
103     mutt_sleep (2);
104     return -1;
105   }
106
107   init_complete = 1;
108   return 0;
109 }
110
111 int mutt_ssl_socket_setup (CONNECTION * conn)
112 {
113   if (tls_init () < 0)
114     return -1;
115
116   conn->conn_open = tls_socket_open;
117   conn->conn_read = tls_socket_read;
118   conn->conn_write = tls_socket_write;
119   conn->conn_close = tls_socket_close;
120
121   return 0;
122 }
123
124 static int tls_socket_read (CONNECTION * conn, char *buf, ssize_t len)
125 {
126   tlssockdata *data = conn->sockdata;
127   int ret;
128
129   if (!data) {
130     mutt_error (_("Error: no TLS socket open"));
131     mutt_sleep (2);
132     return -1;
133   }
134
135   ret = gnutls_record_recv (data->state, buf, len);
136   if (ret < 0 && gnutls_error_is_fatal (ret) == 1) {
137     mutt_error (_("tls_socket_read (%s)"), gnutls_strerror (ret));
138     mutt_sleep (4);
139     return -1;
140   }
141   return ret;
142 }
143
144 static int tls_socket_write (CONNECTION * conn, const char *buf, ssize_t len)
145 {
146   tlssockdata *data = conn->sockdata;
147   int ret;
148
149   if (!data) {
150     mutt_error (_("Error: no TLS socket open"));
151     mutt_sleep (2);
152     return -1;
153   }
154
155   ret = gnutls_record_send (data->state, buf, len);
156   if (ret < 0 && gnutls_error_is_fatal (ret) == 1) {
157     mutt_error (_("tls_socket_write (%s)"), gnutls_strerror (ret));
158     mutt_sleep (4);
159     return -1;
160   }
161   return ret;
162 }
163
164 static int tls_socket_open (CONNECTION * conn)
165 {
166   if (raw_socket_open (conn) < 0)
167     return -1;
168
169   if (tls_negotiate (conn) < 0) {
170     tls_socket_close (conn);
171     return -1;
172   }
173
174   return 0;
175 }
176
177 int mutt_ssl_starttls (CONNECTION * conn)
178 {
179   if (tls_init () < 0)
180     return -1;
181
182   if (tls_negotiate (conn) < 0)
183     return -1;
184
185   conn->conn_read = tls_socket_read;
186   conn->conn_write = tls_socket_write;
187   conn->conn_close = tls_starttls_close;
188
189   return 0;
190 }
191
192 static int protocol_priority[] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
193
194 /* tls_negotiate: After TLS state has been initialised, attempt to negotiate
195  *   TLS over the wire, including certificate checks. */
196 static int tls_negotiate (CONNECTION * conn)
197 {
198   tlssockdata *data;
199   int err;
200
201   data = p_new(tlssockdata, 1);
202   conn->sockdata = data;
203   err = gnutls_certificate_allocate_credentials (&data->xcred);
204   if (err < 0) {
205     p_delete(&conn->sockdata);
206     mutt_error (_("gnutls_certificate_allocate_credentials: %s"),
207                 gnutls_strerror (err));
208     mutt_sleep (2);
209     return -1;
210   }
211
212   gnutls_certificate_set_x509_trust_file (data->xcred, mod_ssl.cert_file,
213                                           GNUTLS_X509_FMT_PEM);
214   /* ignore errors, maybe file doesn't exist yet */
215
216   if (mod_ssl.ca_certificates_file) {
217     gnutls_certificate_set_x509_trust_file (data->xcred,
218                                             mod_ssl.ca_certificates_file,
219                                             GNUTLS_X509_FMT_PEM);
220   }
221
222 /*
223   gnutls_set_x509_client_key (data->xcred, "", "");
224   gnutls_set_x509_cert_callback (data->xcred, cert_callback);
225 */
226
227   gnutls_init (&data->state, GNUTLS_CLIENT);
228
229   /* set socket */
230   gnutls_transport_set_ptr (data->state, (gnutls_transport_ptr)(intptr_t)conn->fd);
231
232   /* disable TLS/SSL protocols as needed */
233   if (!mod_ssl.use_sslv3) {
234     protocol_priority[0] = GNUTLS_TLS1;
235     protocol_priority[1] = 0;
236   }
237   /*
238      else
239      use the list set above
240    */
241
242   /* We use default priorities (see gnutls documentation),
243      except for protocol version */
244   gnutls_set_default_priority (data->state);
245   gnutls_protocol_set_priority (data->state, protocol_priority);
246
247 /*
248   gnutls_set_cred (data->state, GNUTLS_ANON, NULL);
249 */
250
251   gnutls_credentials_set (data->state, GNUTLS_CRD_CERTIFICATE, data->xcred);
252
253   err = gnutls_handshake (data->state);
254
255   while (err == GNUTLS_E_AGAIN || err == GNUTLS_E_INTERRUPTED) {
256     err = gnutls_handshake (data->state);
257   }
258   if (err < 0) {
259     if (err == GNUTLS_E_FATAL_ALERT_RECEIVED) {
260       mutt_error (_("gnutls_handshake: %s(%s)"), gnutls_strerror (err),
261                   gnutls_alert_get_name (gnutls_alert_get (data->state)));
262     }
263     else {
264       mutt_error (_("gnutls_handshake: %s"), gnutls_strerror (err));
265     }
266     mutt_sleep (2);
267     goto fail;
268   }
269
270   if (!tls_check_certificate (conn))
271     goto fail;
272
273   /* set Security Strength Factor (SSF) for SASL */
274   /* NB: gnutls_cipher_get_key_size() returns key length in bytes */
275   conn->ssf =
276     gnutls_cipher_get_key_size (gnutls_cipher_get (data->state)) * 8;
277
278   mutt_message (_("SSL/TLS connection using %s (%s/%s/%s)"),
279                 gnutls_protocol_get_name (gnutls_protocol_get_version
280                                           (data->state)),
281                 gnutls_kx_get_name (gnutls_kx_get (data->state)),
282                 gnutls_cipher_get_name (gnutls_cipher_get (data->state)),
283                 gnutls_mac_get_name (gnutls_mac_get (data->state)));
284   mutt_sleep (0);
285
286   return 0;
287
288 fail:
289   gnutls_certificate_free_credentials (data->xcred);
290   gnutls_deinit (data->state);
291   p_delete(&conn->sockdata);
292   return -1;
293 }
294
295 static int tls_socket_close (CONNECTION * conn)
296 {
297   tlssockdata *data = conn->sockdata;
298
299   if (data) {
300     gnutls_bye (data->state, GNUTLS_SHUT_RDWR);
301
302     gnutls_certificate_free_credentials (data->xcred);
303     gnutls_deinit (data->state);
304     p_delete(&conn->sockdata);
305   }
306
307   return raw_socket_close (conn);
308 }
309
310 static int tls_starttls_close (CONNECTION * conn)
311 {
312   int rc;
313
314   rc = tls_socket_close (conn);
315   conn->conn_read = raw_socket_read;
316   conn->conn_write = raw_socket_write;
317   conn->conn_close = raw_socket_close;
318
319   return rc;
320 }
321
322 #define CERT_SEP "-----BEGIN"
323
324 /* this bit is based on read_ca_file() in gnutls */
325 static int tls_compare_certificates (const gnutls_datum * peercert)
326 {
327   gnutls_datum cert;
328   unsigned char *ptr;
329   FILE *fd1;
330   int ret;
331   gnutls_datum b64_data;
332   unsigned char *b64_data_data;
333   struct stat filestat;
334
335   if (stat(mod_ssl.cert_file, &filestat) == -1)
336     return 0;
337
338   b64_data.size = filestat.st_size + 1;
339   b64_data_data = p_new(unsigned char, b64_data.size);
340   b64_data_data[b64_data.size - 1] = '\0';
341   b64_data.data = b64_data_data;
342
343   fd1 = fopen(mod_ssl.cert_file, "r");
344   if (fd1 == NULL) {
345     return 0;
346   }
347
348   b64_data.size = fread (b64_data.data, 1, b64_data.size, fd1);
349   m_fclose(&fd1);
350
351   do {
352     ret = gnutls_pem_base64_decode_alloc (NULL, &b64_data, &cert);
353     if (ret != 0) {
354       p_delete(&b64_data_data);
355       return 0;
356     }
357
358     ptr = (unsigned char *) strstr ((char*) b64_data.data, CERT_SEP) + 1;
359     ptr = (unsigned char *) strstr ((char*) ptr, CERT_SEP);
360
361     b64_data.size = b64_data.size - (ptr - b64_data.data);
362     b64_data.data = ptr;
363
364     if (cert.size == peercert->size) {
365       if (memcmp (cert.data, peercert->data, cert.size) == 0) {
366         /* match found */
367         gnutls_free (cert.data);
368         p_delete(&b64_data_data);
369         return 1;
370       }
371     }
372
373     gnutls_free (cert.data);
374   } while (ptr != NULL);
375
376   /* no match found */
377   p_delete(&b64_data_data);
378   return 0;
379 }
380
381 static void tls_fingerprint (gnutls_digest_algorithm algo,
382                              char *s, int l, const gnutls_datum * data)
383 {
384   char md[36];
385   ssize_t n;
386   int j;
387
388   n = 36;
389
390   if (gnutls_fingerprint(algo, data, md, (size_t *)&n) < 0) {
391     snprintf (s, l, _("[unable to calculate]"));
392   }
393   else {
394     for (j = 0; j < (int) n; j++) {
395       char ch[8];
396
397       snprintf (ch, 8, "%02X%s", md[j], (j % 2 ? " " : ""));
398       strncat (s, ch, l);
399     }
400     s[2 * n + n / 2 - 1] = '\0';        /* don't want trailing space */
401   }
402 }
403
404 static char *tls_make_date (time_t t, char *s, ssize_t len)
405 {
406     struct tm *l = gmtime(&t);
407
408     if (l) {
409         const char *loc;
410         loc = setlocale(LC_TIME, "C");
411         strftime(s, len, "%a, %d %b %Y %T UTC", l);
412         setlocale(LC_TIME, loc);
413     } else {
414         m_strcpy(s, len, _("[invalid date]"));
415     }
416
417     return s;
418 }
419
420 static int tls_check_stored_hostname (const gnutls_datum * cert,
421                                       const char *hostname)
422 {
423   char buf[80];
424   FILE *fp;
425   char *linestr = NULL;
426   ssize_t linestrsize;
427   int linenum = 0;
428   regex_t preg;
429   regmatch_t pmatch[3];
430
431   /* try checking against names stored in stored certs file */
432   if ((fp = fopen(mod_ssl.cert_file, "r"))) {
433     if (regcomp
434         (&preg,
435          "^#H ([a-zA-Z0-9_\\.-]+) ([0-9A-F]{4}( [0-9A-F]{4}){7})[ \t]*$",
436          REG_ICASE | REG_EXTENDED) != 0) {
437       regfree (&preg);
438       return 0;
439     }
440
441     buf[0] = '\0';
442     tls_fingerprint (GNUTLS_DIG_MD5, buf, sizeof (buf), cert);
443     while ((linestr =
444             mutt_read_line (linestr, &linestrsize, fp, &linenum)) != NULL) {
445       if (linestr[0] == '#' && linestr[1] == 'H') {
446         if (regexec (&preg, linestr, 3, pmatch, 0) == 0) {
447           linestr[pmatch[1].rm_eo] = '\0';
448           linestr[pmatch[2].rm_eo] = '\0';
449           if (m_strcmp(linestr + pmatch[1].rm_so, hostname) == 0 &&
450               m_strcmp(linestr + pmatch[2].rm_so, buf) == 0) {
451             regfree (&preg);
452             p_delete(&linestr);
453             m_fclose(&fp);
454             return 1;
455           }
456         }
457       }
458     }
459
460     regfree (&preg);
461     m_fclose(&fp);
462   }
463
464   /* not found a matching name */
465   return 0;
466 }
467
468 static int tls_check_certificate (CONNECTION * conn)
469 {
470   tlssockdata *data = conn->sockdata;
471   gnutls_session state = data->state;
472   char fpbuf[STRING];
473   ssize_t buflen;
474   char dn_common_name[STRING];
475   char dn_email[STRING];
476   char dn_organization[STRING];
477   char dn_organizational_unit[STRING];
478   char dn_locality[STRING];
479   char dn_province[STRING];
480   char dn_country[STRING];
481   MUTTMENU *menu;
482   int done, row, i, ret;
483   FILE *fp;
484   time_t t;
485   const gnutls_datum *cert_list;
486   unsigned int cert_list_size = 0;
487   gnutls_certificate_status_t certstat;
488   char datestr[30];
489   gnutls_x509_crt cert;
490   gnutls_datum pemdata;
491   int certerr_expired = 0;
492   int certerr_notyetvalid = 0;
493   int certerr_hostname = 0;
494   int certerr_nottrusted = 0;
495   int certerr_revoked = 0;
496   int certerr_signernotca = 0;
497
498   if (gnutls_auth_get_type (state) != GNUTLS_CRD_CERTIFICATE) {
499     mutt_error (_("Unable to get certificate from peer"));
500     mutt_sleep (2);
501     return 0;
502   }
503
504   if (gnutls_certificate_verify_peers2(state, &certstat) < 0) {
505       mutt_error (_("Certificate verification error (%s)"),
506                   gnutls_strerror(certstat));
507       mutt_sleep (2);
508       return 0;
509   }
510
511   /* We only support X.509 certificates (not OpenPGP) at the moment */
512   if (gnutls_certificate_type_get (state) != GNUTLS_CRT_X509) {
513     mutt_error (_("Certificate is not X.509"));
514     mutt_sleep (2);
515     return 0;
516   }
517
518   if (gnutls_x509_crt_init (&cert) < 0) {
519     mutt_error (_("Error initialising gnutls certificate data"));
520     mutt_sleep (2);
521     return 0;
522   }
523
524   cert_list = gnutls_certificate_get_peers (state, &cert_list_size);
525   if (!cert_list) {
526     mutt_error (_("Unable to get certificate from peer"));
527     mutt_sleep (2);
528     return 0;
529   }
530
531   /* FIXME: Currently only check first certificate in chain. */
532   if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) {
533     mutt_error (_("Error processing certificate data"));
534     mutt_sleep (2);
535     return 0;
536   }
537
538   if (gnutls_x509_crt_get_expiration_time (cert) < time (NULL)) {
539     certerr_expired = 1;
540   }
541
542   if (gnutls_x509_crt_get_activation_time (cert) > time (NULL)) {
543     certerr_notyetvalid = 1;
544   }
545
546   if (!gnutls_x509_crt_check_hostname (cert, conn->account.host) &&
547       !tls_check_stored_hostname (&cert_list[0], conn->account.host)) {
548     certerr_hostname = 1;
549   }
550
551   /* see whether certificate is in our cache (certificates file) */
552   if (tls_compare_certificates (&cert_list[0])) {
553     if (certstat & GNUTLS_CERT_INVALID) {
554       /* doesn't matter - have decided is valid because server
555          certificate is in our trusted cache */
556       certstat ^= GNUTLS_CERT_INVALID;
557     }
558
559     if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) {
560       /* doesn't matter that we haven't found the signer, since
561          certificate is in our trusted cache */
562       certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
563     }
564
565     if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) {
566       /* Hmm. Not really sure how to handle this, but let's say
567          that we don't care if the CA certificate hasn't got the
568          correct X.509 basic constraints if server certificate is
569          in our cache. */
570       certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
571     }
572
573   }
574
575   if (certstat & GNUTLS_CERT_REVOKED) {
576     certerr_revoked = 1;
577     certstat ^= GNUTLS_CERT_REVOKED;
578   }
579
580   if (certstat & GNUTLS_CERT_INVALID) {
581     certerr_nottrusted = 1;
582     certstat ^= GNUTLS_CERT_INVALID;
583   }
584
585   if (certstat & GNUTLS_CERT_SIGNER_NOT_FOUND) {
586     /* NB: already cleared if cert in cache */
587     certerr_nottrusted = 1;
588     certstat ^= GNUTLS_CERT_SIGNER_NOT_FOUND;
589   }
590
591   if (certstat & GNUTLS_CERT_SIGNER_NOT_CA) {
592     /* NB: already cleared if cert in cache */
593     certerr_signernotca = 1;
594     certstat ^= GNUTLS_CERT_SIGNER_NOT_CA;
595   }
596
597   /* OK if signed by (or is) a trusted certificate */
598   /* we've been zeroing the interesting bits in certstat - 
599      don't return OK if there are any unhandled bits we don't
600      understand */
601   if (!(certerr_expired || certerr_notyetvalid ||
602         certerr_hostname || certerr_nottrusted) && certstat == 0) {
603     gnutls_x509_crt_deinit (cert);
604     return 1;
605   }
606
607
608   /* interactive check from user */
609   menu = mutt_new_menu ();
610   menu->max = 25;
611   menu->dialog = p_new(char*, menu->max);
612   for (i = 0; i < menu->max; i++)
613     menu->dialog[i] = p_new(char, STRING);
614
615   row = 0;
616   m_strcpy(menu->dialog[row], STRING,
617            _("This certificate belongs to:"));
618   row++;
619
620   buflen = sizeof (dn_common_name);
621   if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0,
622                                      dn_common_name, (size_t *)&buflen) != 0)
623     dn_common_name[0] = '\0';
624   buflen = sizeof (dn_email);
625   if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0,
626                                      dn_email, (size_t *)&buflen) != 0)
627     dn_email[0] = '\0';
628   buflen = sizeof (dn_organization);
629   if (gnutls_x509_crt_get_dn_by_oid
630       (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization,
631        (size_t *)&buflen) != 0)
632     dn_organization[0] = '\0';
633   buflen = sizeof (dn_organizational_unit);
634   if (gnutls_x509_crt_get_dn_by_oid
635       (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0,
636        dn_organizational_unit, (size_t *)&buflen) != 0)
637     dn_organizational_unit[0] = '\0';
638   buflen = sizeof (dn_locality);
639   if (gnutls_x509_crt_get_dn_by_oid
640       (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, (size_t *)&buflen) != 0)
641     dn_locality[0] = '\0';
642   buflen = sizeof (dn_province);
643   if (gnutls_x509_crt_get_dn_by_oid
644       (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province,
645        (size_t *)&buflen) != 0)
646     dn_province[0] = '\0';
647   buflen = sizeof (dn_country);
648   if (gnutls_x509_crt_get_dn_by_oid (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0,
649                                      dn_country, (size_t *)&buflen) != 0)
650     dn_country[0] = '\0';
651
652   snprintf (menu->dialog[row++], STRING, "   %s  %s", dn_common_name,
653             dn_email);
654   snprintf (menu->dialog[row++], STRING, "   %s", dn_organization);
655   snprintf (menu->dialog[row++], STRING, "   %s",
656             dn_organizational_unit);
657   snprintf (menu->dialog[row++], STRING, "   %s  %s  %s", dn_locality,
658             dn_province, dn_country);
659   row++;
660
661   m_strcpy(menu->dialog[row], STRING,
662            _("This certificate was issued by:"));
663   row++;
664
665   buflen = sizeof (dn_common_name);
666   if (gnutls_x509_crt_get_issuer_dn_by_oid
667       (cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, dn_common_name, (size_t *)&buflen) != 0)
668     dn_common_name[0] = '\0';
669   buflen = sizeof (dn_email);
670   if (gnutls_x509_crt_get_issuer_dn_by_oid
671       (cert, GNUTLS_OID_PKCS9_EMAIL, 0, 0, dn_email, (size_t *)&buflen) != 0)
672     dn_email[0] = '\0';
673   buflen = sizeof (dn_organization);
674   if (gnutls_x509_crt_get_issuer_dn_by_oid
675       (cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, dn_organization,
676        (size_t *)&buflen) != 0)
677     dn_organization[0] = '\0';
678   buflen = sizeof (dn_organizational_unit);
679   if (gnutls_x509_crt_get_issuer_dn_by_oid
680       (cert, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0,
681        dn_organizational_unit, (size_t *)&buflen) != 0)
682     dn_organizational_unit[0] = '\0';
683   buflen = sizeof (dn_locality);
684   if (gnutls_x509_crt_get_issuer_dn_by_oid
685       (cert, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, dn_locality, (size_t *)&buflen) != 0)
686     dn_locality[0] = '\0';
687   buflen = sizeof (dn_province);
688   if (gnutls_x509_crt_get_issuer_dn_by_oid
689       (cert, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, dn_province,
690        (size_t *)&buflen) != 0)
691     dn_province[0] = '\0';
692   buflen = sizeof (dn_country);
693   if (gnutls_x509_crt_get_issuer_dn_by_oid
694       (cert, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0, dn_country, (size_t *)&buflen) != 0)
695     dn_country[0] = '\0';
696
697   snprintf (menu->dialog[row++], STRING, "   %s  %s", dn_common_name,
698             dn_email);
699   snprintf (menu->dialog[row++], STRING, "   %s", dn_organization);
700   snprintf (menu->dialog[row++], STRING, "   %s",
701             dn_organizational_unit);
702   snprintf (menu->dialog[row++], STRING, "   %s  %s  %s", dn_locality,
703             dn_province, dn_country);
704   row++;
705
706   snprintf (menu->dialog[row++], STRING,
707             _("This certificate is valid"));
708
709   t = gnutls_x509_crt_get_activation_time (cert);
710   snprintf (menu->dialog[row++], STRING, _("   from %s"),
711             tls_make_date (t, datestr, 30));
712
713   t = gnutls_x509_crt_get_expiration_time (cert);
714   snprintf (menu->dialog[row++], STRING, _("     to %s"),
715             tls_make_date (t, datestr, 30));
716
717   fpbuf[0] = '\0';
718   tls_fingerprint (GNUTLS_DIG_SHA, fpbuf, sizeof (fpbuf), &cert_list[0]);
719   snprintf (menu->dialog[row++], STRING, _("SHA1 Fingerprint: %s"),
720             fpbuf);
721   fpbuf[0] = '\0';
722   tls_fingerprint (GNUTLS_DIG_MD5, fpbuf, sizeof (fpbuf), &cert_list[0]);
723   snprintf (menu->dialog[row++], STRING, _("MD5 Fingerprint: %s"),
724             fpbuf);
725
726   if (certerr_notyetvalid) {
727     row++;
728     m_strcpy(menu->dialog[row], STRING,
729              _("WARNING: Server certificate is not yet valid"));
730   }
731   if (certerr_expired) {
732     row++;
733     m_strcpy(menu->dialog[row], STRING,
734              _("WARNING: Server certificate has expired"));
735   }
736   if (certerr_revoked) {
737     row++;
738     m_strcpy(menu->dialog[row], STRING,
739              _("WARNING: Server certificate has been revoked"));
740   }
741   if (certerr_hostname) {
742     row++;
743     m_strcpy(menu->dialog[row], STRING,
744              _("WARNING: Server hostname does not match certificate"));
745   }
746   if (certerr_signernotca) {
747     row++;
748     m_strcpy(menu->dialog[row], STRING,
749              _("WARNING: Signer of server certificate is not a CA"));
750   }
751
752   menu->title = _("TLS/SSL Certificate check");
753   /* certificates with bad dates, or that are revoked, must be
754      accepted manually each and every time */
755   if (mod_ssl.cert_file && !certerr_expired && !certerr_notyetvalid
756       && !certerr_revoked) {
757     menu->prompt = _("(r)eject, accept (o)nce, (a)ccept always");
758     menu->keys = _("roa");
759   }
760   else {
761     menu->prompt = _("(r)eject, accept (o)nce");
762     menu->keys = _("ro");
763   }
764
765   done = 0;
766   set_option (OPTUNBUFFEREDINPUT);
767   while (!done) {
768     switch (mutt_menuLoop (menu)) {
769     case -1:                   /* abort */
770     case OP_MAX + 1:           /* reject */
771     case OP_EXIT:
772       done = 1;
773       break;
774     case OP_MAX + 3:           /* accept always */
775       done = 0;
776       if ((fp = fopen(mod_ssl.cert_file, "a"))) {
777         /* save hostname if necessary */
778         if (certerr_hostname) {
779           fprintf (fp, "#H %s %s\n", conn->account.host, fpbuf);
780           done = 1;
781         }
782         if (certerr_nottrusted) {
783           done = 0;
784           ret = gnutls_pem_base64_encode_alloc ("CERTIFICATE", &cert_list[0],
785                                                 &pemdata);
786           if (ret == 0) {
787             if (fwrite (pemdata.data, pemdata.size, 1, fp) == 1) {
788               done = 1;
789             }
790             gnutls_free (pemdata.data);
791           }
792         }
793         m_fclose(&fp);
794       }
795       if (!done) {
796         mutt_error (_("Warning: Couldn't save certificate"));
797         mutt_sleep (2);
798       }
799       else {
800         mutt_message (_("Certificate saved"));
801         mutt_sleep (0);
802       }
803       /* fall through */
804     case OP_MAX + 2:           /* accept once */
805       done = 2;
806       break;
807     }
808   }
809   unset_option (OPTUNBUFFEREDINPUT);
810   mutt_menuDestroy (&menu);
811   gnutls_x509_crt_deinit (cert);
812   return done == 2;
813 }
814
815 /* vim:set ft=c: */