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