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