add SSL manager
[claws.git] / src / ssl_certificate.c
index 1386c0fb84690bdb28b5aacd58d06fe8034037dc..328f6f7d36bb634fc061ec17b8d9b35634c8053b 100644 (file)
 #include "alertpanel.h"
 #include "utils.h"
 #include "intl.h"
+#include "prefs_common.h"
+#include "socket.h"
 
-static void ssl_certificate_destroy(SSLCertificate *cert);
+static char *ssl_certificate_check_signer (X509 *cert); 
+static SSLCertificate *ssl_certificate_new_lookup(X509 *x509_cert, gchar *host, gushort port, gboolean lookup);
 
-static char * convert_fingerprint(char *src) 
+static char * get_fqdn(char *host)
+{
+       struct hostent *hp;
+
+       if (host == NULL || strlen(host) == 0)
+               return g_strdup("");
+
+       hp = my_gethostbyname(host);
+       if (hp == NULL)
+               return g_strdup(host); /*caller should free*/
+       else 
+               return g_strdup(hp->h_name);
+}
+
+static char * readable_fingerprint(unsigned char *src, int len) 
 {
        int i=0;
        char * ret;
@@ -40,12 +57,13 @@ static char * convert_fingerprint(char *src)
        if (src == NULL)
                return NULL;
        ret = g_strdup("");
-       while (src[i] != '\0') {
+       while (i < len) {
                char *tmp2;
                if(i>0)
                        tmp2 = g_strdup_printf("%s:%02X", ret, src[i]);
                else
                        tmp2 = g_strdup_printf("%02X", src[i]);
+               g_free(ret);
                ret = g_strdup(tmp2);
                g_free(tmp2);
                i++;
@@ -53,26 +71,33 @@ static char * convert_fingerprint(char *src)
        return ret;
 }
 
-SSLCertificate *ssl_certificate_new(gchar *host, gchar *issuer, gchar *subject, gchar *md)
+SSLCertificate *ssl_certificate_new(X509 *x509_cert, gchar *host, gushort port)
+{
+       return ssl_certificate_new_lookup(x509_cert, host, port, TRUE);
+}
+
+static SSLCertificate *ssl_certificate_new_lookup(X509 *x509_cert, gchar *host, gushort port, gboolean lookup)
 {
        SSLCertificate *cert = g_new0(SSLCertificate, 1);
        
-       if (host == NULL || issuer == NULL || subject == NULL || md == NULL) {
+       if (host == NULL || x509_cert == NULL) {
                ssl_certificate_destroy(cert);
                return NULL;
        }
-
-       cert->host = g_strdup(host);
-       cert->issuer = g_strdup(issuer);
-       cert->subject = g_strdup(subject);
-       cert->fingerprint = g_strdup(md);
+       cert->x509_cert = X509_dup(x509_cert);
+       if (lookup)
+               cert->host = get_fqdn(host);
+       else
+               cert->host = g_strdup(host);
+       cert->port = port;
        return cert;
 }
 
 static void ssl_certificate_save (SSLCertificate *cert)
 {
-       gchar *file;
+       gchar *file, *port;
        FILE *fp;
+
        file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
                          "certs", G_DIR_SEPARATOR_S, NULL);
        
@@ -80,104 +105,188 @@ static void ssl_certificate_save (SSLCertificate *cert)
                make_dir_hier(file);
        g_free(file);
 
+       port = g_strdup_printf("%d", cert->port);
        file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
                          "certs", G_DIR_SEPARATOR_S,
-                         cert->host, ".cert", NULL);
+                         cert->host, ".", port, ".cert", NULL);
 
-       fp = fopen(file, "w");
+       g_free(port);
+       fp = fopen(file, "wb");
        if (fp == NULL) {
                g_free(file);
                alertpanel_error(_("Can't save certificate !"));
                return;
        }
-       fputs("issuer=", fp);
-       fputs(cert->issuer, fp);
-       fputs("\nsubject=", fp);
-       fputs(cert->subject, fp);
-       fputs("\nfingerprint=", fp);
-       fputs(cert->fingerprint, fp);
-       fputs("\n", fp);
+       i2d_X509_fp(fp, cert->x509_cert);
+       g_free(file);
        fclose(fp);
+
 }
 
-static char* ssl_certificate_to_string(SSLCertificate *cert)
+char* ssl_certificate_to_string(SSLCertificate *cert)
 {
-       char *ret;
-       ret = g_strdup_printf("  Issuer: %s\n  Subject: %s\n  Fingerprint: %s",
-                               cert->issuer,
-                               cert->subject,
-                               cert->fingerprint);
+       char *ret, buf[100];
+       char *issuer_commonname, *issuer_location, *issuer_organization;
+       char *subject_commonname, *subject_location, *subject_organization;
+       char *fingerprint, *sig_status;
+       unsigned int n;
+       unsigned char md[EVP_MAX_MD_SIZE];      
+       
+       /* issuer */    
+       if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
+                                      NID_commonName, buf, 100) >= 0)
+               issuer_commonname = g_strdup(buf);
+       else
+               issuer_commonname = g_strdup(_("<not in certificate>"));
+       if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
+                                      NID_localityName, buf, 100) >= 0) {
+               issuer_location = g_strdup(buf);
+               if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
+                                      NID_countryName, buf, 100) >= 0)
+                       issuer_location = g_strconcat(issuer_location,", ",buf, NULL);
+       } else if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
+                                      NID_countryName, buf, 100) >= 0)
+               issuer_location = g_strdup(buf);
+       else
+               issuer_location = g_strdup(_("<not in certificate>"));
+
+       if (X509_NAME_get_text_by_NID(X509_get_issuer_name(cert->x509_cert), 
+                                      NID_organizationName, buf, 100) >= 0)
+               issuer_organization = g_strdup(buf);
+       else 
+               issuer_organization = g_strdup(_("<not in certificate>"));
+        
+       /* subject */   
+       if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
+                                      NID_commonName, buf, 100) >= 0)
+               subject_commonname = g_strdup(buf);
+       else
+               subject_commonname = g_strdup(_("<not in certificate>"));
+       if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
+                                      NID_localityName, buf, 100) >= 0) {
+               subject_location = g_strdup(buf);
+               if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
+                                      NID_countryName, buf, 100) >= 0)
+                       subject_location = g_strconcat(subject_location,", ",buf, NULL);
+       } else if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
+                                      NID_countryName, buf, 100) >= 0)
+               subject_location = g_strdup(buf);
+       else
+               subject_location = g_strdup(_("<not in certificate>"));
+
+       if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert->x509_cert), 
+                                      NID_organizationName, buf, 100) >= 0)
+               subject_organization = g_strdup(buf);
+       else 
+               subject_organization = g_strdup(_("<not in certificate>"));
+        
+       /* fingerprint */
+       X509_digest(cert->x509_cert, EVP_md5(), md, &n);
+       fingerprint = readable_fingerprint(md, (int)n);
+
+       /* signature */
+       sig_status = ssl_certificate_check_signer(cert->x509_cert);
+
+       ret = g_strdup_printf(_("  Owner: %s (%s) in %s\n  Signed by: %s (%s) in %s\n  Fingerprint: %s\n  Signature status: %s"),
+                               subject_commonname, subject_organization, subject_location, 
+                               issuer_commonname, issuer_organization, issuer_location, 
+                               fingerprint,
+                               (sig_status==NULL ? "correct":sig_status));
+
+       if (issuer_commonname)
+               g_free(issuer_commonname);
+       if (issuer_location)
+               g_free(issuer_location);
+       if (issuer_organization)
+               g_free(issuer_organization);
+       if (subject_commonname)
+               g_free(subject_commonname);
+       if (subject_location)
+               g_free(subject_location);
+       if (subject_organization)
+               g_free(subject_organization);
+       if (fingerprint)
+               g_free(fingerprint);
+       if (sig_status)
+               g_free(sig_status);
        return ret;
 }
        
 void ssl_certificate_destroy(SSLCertificate *cert) 
 {
-       g_return_if_fail(cert != NULL);
+       if (cert == NULL)
+               return;
+
+       if (cert->x509_cert)
+               X509_free(cert->x509_cert);
        if (cert->host) 
                g_free(cert->host);
-       if (cert->issuer)
-               g_free(cert->issuer);
-       if (cert->subject)
-               g_free(cert->subject);
-       if (cert->fingerprint)
-               g_free(cert->fingerprint);
        g_free(cert);
        cert = NULL;
 }
 
-static SSLCertificate *ssl_certificate_find (gchar *host)
+void ssl_certificate_delete_from_disk(SSLCertificate *cert)
 {
+       gchar *buf;
        gchar *file;
-       gchar buf[1024], *subject, *issuer, *fingerprint;
-       SSLCertificate *cert;
+       buf = g_strdup_printf("%d", cert->port);
+       file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
+                         "certs", G_DIR_SEPARATOR_S,
+                         cert->host, ".", buf, ".cert", NULL);
+       unlink (file);
+       g_free(buf);
+       g_free(file);
+}
+
+SSLCertificate *ssl_certificate_find (gchar *host, gushort port)
+{
+       return ssl_certificate_find_lookup (host, port, TRUE);
+}
+
+SSLCertificate *ssl_certificate_find_lookup (gchar *host, gushort port, gboolean lookup)
+{
+       gchar *file;
+       gchar *buf;
+       gchar *fqdn_host;
+       SSLCertificate *cert = NULL;
+       X509 *tmp_x509;
        FILE *fp;
-       
+
+       if (lookup)
+               fqdn_host = get_fqdn(host);
+       else
+               fqdn_host = g_strdup(host);
+
+       buf = g_strdup_printf("%d", port);
        file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 
                          "certs", G_DIR_SEPARATOR_S,
-                         host, ".cert", NULL);
+                         fqdn_host, ".", buf, ".cert", NULL);
 
-       fp = fopen(file, "r");
+       g_free(buf);
+       fp = fopen(file, "rb");
        if (fp == NULL) {
                g_free(file);
+               g_free(fqdn_host);
                return NULL;
        }
        
-       while( fgets( buf, sizeof( buf ), fp ) != NULL ) {
-               if (!strncmp(buf, "subject=", 8)) {
-                       subject = g_strdup((char *)buf +8);
-                       g_strdelimit(subject, "\r\n", '\0');
-               }
-               else if (!strncmp(buf, "issuer=", 7)) {
-                       issuer = g_strdup((char *)buf +7);
-                       g_strdelimit(issuer, "\r\n", '\0');
-               }
-               else if (!strncmp(buf, "fingerprint=", 12)) {
-                       fingerprint = g_strdup((char *)buf +12);
-                       g_strdelimit(fingerprint, "\r\n", '\0');
-               }
-       }
-       fclose (fp);
-       if (subject && issuer && fingerprint) {
-               cert = ssl_certificate_new(host, issuer, subject, fingerprint);
-       }
        
+       if ((tmp_x509 = d2i_X509_fp(fp, 0)) != NULL) {
+               cert = ssl_certificate_new_lookup(tmp_x509, fqdn_host, port, lookup);
+               X509_free(tmp_x509);
+       }
+       fclose(fp);
        g_free(file);
+       g_free(fqdn_host);
        
-       if (subject)
-               g_free(subject);
-       if (issuer)
-               g_free(issuer);
-       if (fingerprint)
-               g_free(fingerprint);
-
        return cert;
 }
 
 static gboolean ssl_certificate_compare (SSLCertificate *cert_a, SSLCertificate *cert_b)
 {
-       if (!strcmp(cert_a->issuer, cert_b->issuer)
-       &&  !strcmp(cert_a->subject, cert_b->subject)
-       &&  !strcmp(cert_a->fingerprint, cert_b->fingerprint))
+       if (cert_a == NULL || cert_b == NULL)
+               return FALSE;
+       else if (!X509_cmp(cert_a->x509_cert, cert_b->x509_cert))
                return TRUE;    
        else
                return FALSE;
@@ -194,7 +303,7 @@ static char *ssl_certificate_check_signer (X509 *cert)
        store = X509_STORE_new();
        if (store == NULL) {
                printf("Can't create X509_STORE\n");
-               return FALSE;
+               return NULL;
        }
        if (X509_STORE_set_default_paths(store)) 
                ok++;
@@ -223,46 +332,54 @@ static char *ssl_certificate_check_signer (X509 *cert)
        return NULL;
 }
 
-gboolean ssl_certificate_check (X509 *x509_cert, gchar *host, gchar *issuer, 
-                               gchar *subject, gchar *md) 
+gboolean ssl_certificate_check (X509 *x509_cert, gchar *host, gushort port)
 {
-       char *readable_md = convert_fingerprint(md);
-       SSLCertificate *current_cert = ssl_certificate_new(host, issuer, subject, readable_md);
+       SSLCertificate *current_cert = ssl_certificate_new(x509_cert, host, port);
        SSLCertificate *known_cert;
 
        if (current_cert == NULL) {
                debug_print("Buggy certificate !\n");
-               debug_print("host: %s\n", host);
-               debug_print("issuer: %s\n", issuer);
-               debug_print("subject: %s\n", subject);
-               debug_print("md: %s\n", readable_md);
-               if (readable_md)
-                       g_free(readable_md);
                return FALSE;
        }
 
-       if (readable_md)
-               g_free(readable_md);
-
-       known_cert = ssl_certificate_find (host);
+       known_cert = ssl_certificate_find (host, port);
 
        if (known_cert == NULL) {
                gint val;
-               gchar *err_msg, *cur_cert_str;
-               gchar *sig_status = NULL;
-               
-               cur_cert_str = ssl_certificate_to_string(current_cert);
+               gchar *err_msg, *cur_cert_str, *sig_status;
                
                sig_status = ssl_certificate_check_signer(x509_cert);
 
-               err_msg = g_strdup_printf(_("The SSL certificate presented by %s is unknown.\nPresented certificate is:\n%s\n\n%s%s"),
-                                         host,
-                                         cur_cert_str,
-                                         (sig_status == NULL)?"The presented certificate signature is correct.":"The presented certificate signature is not correct: ",
-                                         (sig_status == NULL)?"":sig_status);
+               if (sig_status == NULL && !prefs_common.ssl_ask_unknown_valid) {
+                       /* trust and accept silently if hostnames match */
+                       char *buf; /* don't free buf ! */
+                       if (X509_NAME_get_text_by_NID(X509_get_subject_name(x509_cert), 
+                                      NID_commonName, buf, 100) >= 0)
+                               if (!strcmp(buf, current_cert->host)) {
+                                       g_free(sig_status);
+                                       ssl_certificate_save(current_cert);
+                                       ssl_certificate_destroy(current_cert);
+                                       return TRUE;            
+                               }
+               }
+
+               g_free(sig_status);
+
+               cur_cert_str = ssl_certificate_to_string(current_cert);
+               
+               err_msg = g_strdup_printf(_("%s presented an unknown SSL certificate:\n%s"),
+                                         current_cert->host,
+                                         cur_cert_str);
                g_free (cur_cert_str);
-               g_free (sig_status);
+
+               if (prefs_common.no_recv_err_panel) {
+                       log_error(_("%s\n\nMail won't be retrieved on this account until you save the certificate.\n(Uncheck the \"%s\" preference).\n"),
+                                       err_msg,
+                                       _("Don't popup error dialog on receive error"));
+                       g_free(err_msg);
+                       return FALSE;
+               }
+                
                val = alertpanel(_("Warning"),
                               err_msg,
                               _("Accept and save"), _("Cancel connection"), NULL);
@@ -280,21 +397,24 @@ gboolean ssl_certificate_check (X509 *x509_cert, gchar *host, gchar *issuer,
        }
        else if (!ssl_certificate_compare (current_cert, known_cert)) {
                gint val;
-               gchar *err_msg, *known_cert_str, *cur_cert_str, *sig_status;
+               gchar *err_msg, *known_cert_str, *cur_cert_str;
                
-               sig_status = ssl_certificate_check_signer(x509_cert);
-
                known_cert_str = ssl_certificate_to_string(known_cert);
                cur_cert_str = ssl_certificate_to_string(current_cert);
-               err_msg = g_strdup_printf(_("The SSL certificate presented by %s differs from the known one.\nKnown certificate is:\n%s\nPresented certificate is:\n%s\n\n%s%s"),
-                                         host,
+               err_msg = g_strdup_printf(_("%s's SSL certificate changed !\nWe have saved this one:\n%s\n\nIt is now:\n%s\n\nThis could mean the server answering is not the known one."),
+                                         current_cert->host,
                                          known_cert_str,
-                                         cur_cert_str,
-                                         (sig_status == NULL)?"The presented certificate signature is correct.":"The presented certificate signature is not correct: ",
-                                         (sig_status == NULL)?"":sig_status);
+                                         cur_cert_str);
                g_free (cur_cert_str);
                g_free (known_cert_str);
-               g_free (sig_status);
+
+               if (prefs_common.no_recv_err_panel) {
+                       log_error(_("%s\n\nMail won't be retrieved on this account until you save the certificate.\n(Uncheck the \"%s\" preference).\n"),
+                                       err_msg,
+                                       _("Don't popup error dialog on receive error"));
+                       g_free(err_msg);
+                       return FALSE;
+               }
 
                val = alertpanel(_("Warning"),
                               err_msg,