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