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