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