5e8ae32426
* src/account.c * src/addressadd.c * src/compose.c * src/gtkaspell.c * src/gtkxtext.h ** REMOVE ** * src/imap.c * src/matcher.[ch] * src/mh.c * src/news.c * src/pine.c * src/prefs_common.c * src/prefs_matcher.c * src/procheader.[ch] * src/procmsg.[ch] * src/toolbar.c * src/common/ssl_certificate.c * src/gtk/colorlabel.c * src/gtk/menu.c o fix warnings o remove code obsoleted by new cache and folder system rework
439 lines
12 KiB
C
439 lines
12 KiB
C
/*
|
|
* Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
|
|
* Copyright (C) 1999-2001 Hiroyuki Yamamoto
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#if USE_OPENSSL
|
|
|
|
#include <openssl/ssl.h>
|
|
#include <glib.h>
|
|
#include "ssl_certificate.h"
|
|
#include "utils.h"
|
|
#include "intl.h"
|
|
#include "log.h"
|
|
#include "socket.h"
|
|
#include "hooks.h"
|
|
|
|
static SSLCertificate *ssl_certificate_new_lookup(X509 *x509_cert, gchar *host, gushort port, gboolean lookup);
|
|
|
|
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);
|
|
}
|
|
|
|
char * readable_fingerprint(unsigned char *src, int len)
|
|
{
|
|
int i=0;
|
|
char * ret;
|
|
|
|
if (src == NULL)
|
|
return NULL;
|
|
ret = g_strdup("");
|
|
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++;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
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 || x509_cert == NULL) {
|
|
ssl_certificate_destroy(cert);
|
|
return NULL;
|
|
}
|
|
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, *port;
|
|
FILE *fp;
|
|
|
|
file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
|
|
"certs", G_DIR_SEPARATOR_S, NULL);
|
|
|
|
if (!is_dir_exist(file))
|
|
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, ".", port, ".cert", NULL);
|
|
|
|
g_free(port);
|
|
fp = fopen(file, "wb");
|
|
if (fp == NULL) {
|
|
g_free(file);
|
|
debug_print("Can't save certificate !\n");
|
|
return;
|
|
}
|
|
i2d_X509_fp(fp, cert->x509_cert);
|
|
g_free(file);
|
|
fclose(fp);
|
|
|
|
}
|
|
|
|
char* ssl_certificate_to_string(SSLCertificate *cert)
|
|
{
|
|
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)
|
|
{
|
|
if (cert == NULL)
|
|
return;
|
|
|
|
if (cert->x509_cert)
|
|
X509_free(cert->x509_cert);
|
|
if (cert->host)
|
|
g_free(cert->host);
|
|
g_free(cert);
|
|
cert = NULL;
|
|
}
|
|
|
|
void ssl_certificate_delete_from_disk(SSLCertificate *cert)
|
|
{
|
|
gchar *buf;
|
|
gchar *file;
|
|
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,
|
|
fqdn_host, ".", buf, ".cert", NULL);
|
|
|
|
g_free(buf);
|
|
fp = fopen(file, "rb");
|
|
if (fp == NULL) {
|
|
g_free(file);
|
|
g_free(fqdn_host);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
return cert;
|
|
}
|
|
|
|
static gboolean ssl_certificate_compare (SSLCertificate *cert_a, SSLCertificate *cert_b)
|
|
{
|
|
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;
|
|
}
|
|
|
|
char *ssl_certificate_check_signer (X509 *cert)
|
|
{
|
|
X509_STORE_CTX store_ctx;
|
|
X509_STORE *store;
|
|
char *err_msg = NULL;
|
|
|
|
store = X509_STORE_new();
|
|
if (store == NULL) {
|
|
printf("Can't create X509_STORE\n");
|
|
return NULL;
|
|
}
|
|
if (!X509_STORE_set_default_paths(store)) {
|
|
X509_STORE_free (store);
|
|
return g_strdup(_("Can't load X509 default paths"));
|
|
}
|
|
|
|
X509_STORE_CTX_init (&store_ctx, store, cert, NULL);
|
|
|
|
if(!X509_verify_cert (&store_ctx)) {
|
|
err_msg = g_strdup(X509_verify_cert_error_string(
|
|
X509_STORE_CTX_get_error(&store_ctx)));
|
|
debug_print("Can't check signer: %s\n", err_msg);
|
|
X509_STORE_CTX_cleanup (&store_ctx);
|
|
X509_STORE_free (store);
|
|
return err_msg;
|
|
|
|
}
|
|
X509_STORE_CTX_cleanup (&store_ctx);
|
|
X509_STORE_free (store);
|
|
return NULL;
|
|
}
|
|
|
|
gboolean ssl_certificate_check (X509 *x509_cert, gchar *host, gushort port)
|
|
{
|
|
SSLCertificate *current_cert = ssl_certificate_new(x509_cert, host, port);
|
|
SSLCertificate *known_cert;
|
|
SSLCertHookData cert_hook_data;
|
|
|
|
if (current_cert == NULL) {
|
|
debug_print("Buggy certificate !\n");
|
|
return FALSE;
|
|
}
|
|
|
|
known_cert = ssl_certificate_find (host, port);
|
|
|
|
if (known_cert == NULL) {
|
|
gchar *err_msg, *cur_cert_str, *sig_status;
|
|
|
|
sig_status = ssl_certificate_check_signer(x509_cert);
|
|
|
|
#if 0 /* disabled pref for now */
|
|
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;
|
|
}
|
|
}
|
|
#endif
|
|
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);
|
|
|
|
#if 0 /* disabled for now */
|
|
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;
|
|
}
|
|
#endif
|
|
cert_hook_data.cert = current_cert;
|
|
cert_hook_data.old_cert = NULL;
|
|
cert_hook_data.accept = FALSE;
|
|
|
|
hooks_invoke(SSLCERT_ASK_HOOKLIST, &cert_hook_data);
|
|
|
|
g_free(err_msg);
|
|
|
|
if (!cert_hook_data.accept) {
|
|
ssl_certificate_destroy(current_cert);
|
|
return FALSE;
|
|
} else {
|
|
ssl_certificate_save(current_cert);
|
|
ssl_certificate_destroy(current_cert);
|
|
return TRUE;
|
|
}
|
|
}
|
|
else if (!ssl_certificate_compare (current_cert, known_cert)) {
|
|
gchar *err_msg, *known_cert_str, *cur_cert_str;
|
|
|
|
known_cert_str = ssl_certificate_to_string(known_cert);
|
|
cur_cert_str = ssl_certificate_to_string(current_cert);
|
|
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);
|
|
g_free (cur_cert_str);
|
|
g_free (known_cert_str);
|
|
|
|
#if 0
|
|
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;
|
|
}
|
|
#endif
|
|
cert_hook_data.cert = current_cert;
|
|
cert_hook_data.old_cert = known_cert;
|
|
cert_hook_data.accept = FALSE;
|
|
|
|
hooks_invoke(SSLCERT_ASK_HOOKLIST, &cert_hook_data);
|
|
|
|
g_free(err_msg);
|
|
|
|
if (!cert_hook_data.accept) {
|
|
ssl_certificate_destroy(current_cert);
|
|
ssl_certificate_destroy(known_cert);
|
|
return FALSE;
|
|
} else {
|
|
ssl_certificate_save(current_cert);
|
|
ssl_certificate_destroy(current_cert);
|
|
ssl_certificate_destroy(known_cert);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
ssl_certificate_destroy(current_cert);
|
|
ssl_certificate_destroy(known_cert);
|
|
return TRUE;
|
|
}
|
|
|
|
#endif /* USE_OPENSSL */
|