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