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