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