grim/pidgin
Clone
Summary
Browse
Changes
Graph
Migrate pounces.xml to XDG data dir
xdg-dirs
2016-10-02, qarkai
dfe50a38fc52
Migrate pounces.xml to XDG data dir
/*
*
* purple
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include
"internal.h"
#include
"tls-certificate.h"
#include
"debug.h"
#include
"util.h"
/* Makes a filename path for a certificate. If id is NULL,
* just return the directory
*/
static
gchar
*
make_certificate_path
(
const
gchar
*
id
)
{
return
g_build_filename
(
purple_data_dir
(),
"certificates"
,
"tls"
,
id
!=
NULL
?
purple_escape_filename
(
id
)
:
NULL
,
NULL
);
}
/* Creates the certificate directory if it doesn't exist,
* returns TRUE if it's successful or it already exists,
* returns FALSE if there was an error.
*/
static
gboolean
ensure_certificate_dir
(
GError
**
error
)
{
gchar
*
dir
=
make_certificate_path
(
NULL
);
gboolean
ret
=
TRUE
;
if
(
purple_build_dir
(
dir
,
0700
)
!=
0
)
{
g_set_error_literal
(
error
,
G_FILE_ERROR
,
g_file_error_from_errno
(
errno
),
g_strerror
(
errno
));
ret
=
FALSE
;
}
g_free
(
dir
);
return
ret
;
}
GList
*
purple_tls_certificate_list_ids
()
{
gchar
*
dir_path
;
GDir
*
dir
;
const
gchar
*
entry
;
GList
*
idlist
=
NULL
;
GError
*
error
=
NULL
;
/* Ensure certificate directory exists */
if
(
!
ensure_certificate_dir
(
&
error
))
{
purple_debug_error
(
"tls-certificate"
,
"Error creating certificate directory: %s"
,
error
->
message
);
g_clear_error
(
&
error
);
return
NULL
;
}
/* Open certificate directory */
dir_path
=
make_certificate_path
(
NULL
);
dir
=
g_dir_open
(
dir_path
,
0
,
&
error
);
if
(
dir
==
NULL
)
{
purple_debug_error
(
"tls-certificate"
,
"Error opening certificate directory (%s): %s"
,
dir_path
,
error
->
message
);
g_free
(
dir_path
);
g_clear_error
(
&
error
);
return
NULL
;
}
g_free
(
dir_path
);
/* Traverse the directory listing and create an idlist */
while
((
entry
=
g_dir_read_name
(
dir
))
!=
NULL
)
{
/* Unescape the filename
* (GLib owns original string)
*/
const
char
*
unescaped
=
purple_unescape_filename
(
entry
);
/* Copy the entry name into our list
* (Purple own the escaped string)
*/
idlist
=
g_list_prepend
(
idlist
,
g_strdup
(
unescaped
));
}
g_dir_close
(
dir
);
return
idlist
;
}
void
purple_tls_certificate_free_ids
(
GList
*
ids
)
{
g_list_free_full
(
ids
,
g_free
);
}
GTlsCertificate
*
purple_tls_certificate_new_from_id
(
const
gchar
*
id
,
GError
**
error
)
{
GTlsCertificate
*
cert
;
gchar
*
path
;
g_return_val_if_fail
(
id
!=
NULL
&&
id
[
0
]
!=
'\0'
,
NULL
);
/* Load certificate from file if it exists */
path
=
make_certificate_path
(
id
);
cert
=
g_tls_certificate_new_from_file
(
path
,
error
);
g_free
(
path
);
return
cert
;
}
gboolean
purple_tls_certificate_trust
(
const
gchar
*
id
,
GTlsCertificate
*
certificate
,
GError
**
error
)
{
gchar
*
path
;
gchar
*
pem
=
NULL
;
gboolean
ret
;
g_return_val_if_fail
(
id
!=
NULL
&&
id
[
0
]
!=
'\0'
,
FALSE
);
g_return_val_if_fail
(
G_IS_TLS_CERTIFICATE
(
certificate
),
FALSE
);
/* Ensure certificate directory exists */
if
(
!
ensure_certificate_dir
(
error
))
{
return
FALSE
;
}
/* Get the text representation of the certificate */
g_object_get
(
certificate
,
"certificate-pem"
,
&
pem
,
NULL
);
g_return_val_if_fail
(
pem
!=
NULL
,
FALSE
);
/* Save certificate text to a fail */
path
=
make_certificate_path
(
id
);
ret
=
g_file_set_contents
(
path
,
pem
,
-1
,
error
);
g_free
(
path
);
g_free
(
pem
);
return
ret
;
}
gboolean
purple_tls_certificate_distrust
(
const
gchar
*
id
,
GError
**
error
)
{
gchar
*
path
;
gboolean
ret
=
TRUE
;
g_return_val_if_fail
(
id
!=
NULL
&&
id
[
0
]
!=
'\0'
,
FALSE
);
/* Delete certificate file if it exists */
path
=
make_certificate_path
(
id
);
if
(
g_unlink
(
path
)
!=
0
)
{
g_set_error_literal
(
error
,
G_FILE_ERROR
,
g_file_error_from_errno
(
errno
),
g_strerror
(
errno
));
ret
=
FALSE
;
}
g_free
(
path
);
return
ret
;
}
/* Converts GTlsCertificateFlags to a translated string representation
* of the first set error flag in the order checked
*/
static
const
gchar
*
tls_certificate_flags_to_reason
(
GTlsCertificateFlags
flags
)
{
if
(
flags
&
G_TLS_CERTIFICATE_UNKNOWN_CA
)
{
return
_
(
"The certificate is not trusted because no "
"certificate that can verify it is "
"currently trusted."
);
}
else
if
(
flags
&
G_TLS_CERTIFICATE_BAD_IDENTITY
)
{
/* Translators: "domain" refers to a DNS domain
* (e.g. talk.google.com)
*/
return
_
(
"The certificate presented is not issued to "
"this domain."
);
}
else
if
(
flags
&
G_TLS_CERTIFICATE_NOT_ACTIVATED
)
{
return
_
(
"The certificate is not valid yet. Check that your "
"computer's date and time are accurate."
);
}
else
if
(
flags
&
G_TLS_CERTIFICATE_EXPIRED
)
{
return
_
(
"The certificate has expired and should not be "
"considered valid. Check that your "
"computer's date and time are accurate."
);
}
else
if
(
flags
&
G_TLS_CERTIFICATE_REVOKED
)
{
return
_
(
"The certificate has been revoked."
);
}
else
if
(
flags
&
G_TLS_CERTIFICATE_INSECURE
)
{
return
_
(
"The certificate's algorithm is considered insecure."
);
}
else
{
/* Also catches G_TLS_CERTIFICATE_GENERIC_ERROR here */
return
_
(
"An unknown certificate error occurred."
);
}
}
/* Holds data for requesting the user to accept a given certificate */
typedef
struct
{
gchar
*
identity
;
GTlsCertificate
*
cert
;
}
UserCertRequestData
;
static
void
user_cert_request_data_free
(
UserCertRequestData
*
data
)
{
g_return_if_fail
(
data
!=
NULL
);
g_free
(
data
->
identity
);
g_object_unref
(
data
->
cert
);
g_free
(
data
);
}
static
void
user_cert_request_accept_cb
(
UserCertRequestData
*
data
)
{
GError
*
error
=
NULL
;
g_return_if_fail
(
data
!=
NULL
);
/* User accepted. Trust this certificate */
if
(
!
purple_tls_certificate_trust
(
data
->
identity
,
data
->
cert
,
&
error
))
{
purple_debug_error
(
"tls-certificate"
,
"Error trusting certificate '%s': %s"
,
data
->
identity
,
error
->
message
);
g_clear_error
(
&
error
);
}
user_cert_request_data_free
(
data
);
}
static
void
user_cert_request_deny_cb
(
UserCertRequestData
*
data
)
{
/* User denied. Free data related to the requst */
user_cert_request_data_free
(
data
);
}
/* Prompts the user to accept the certificate as it failed due to the
* passed errors.
*/
static
void
request_accept_certificate
(
const
gchar
*
identity
,
GTlsCertificate
*
peer_cert
,
GTlsCertificateFlags
errors
)
{
UserCertRequestData
*
data
;
gchar
*
primary
;
g_return_if_fail
(
identity
!=
NULL
&&
identity
[
0
]
!=
'\0'
);
g_return_if_fail
(
G_IS_TLS_CERTIFICATE
(
peer_cert
));
g_return_if_fail
(
errors
!=
0
);
data
=
g_new
(
UserCertRequestData
,
1
);
data
->
identity
=
g_strdup
(
identity
);
data
->
cert
=
g_object_ref
(
peer_cert
);
primary
=
g_strdup_printf
(
_
(
"Accept certificate for %s?"
),
identity
);
purple_request_certificate
(
data
,
_
(
"TLS Certificate Verification"
),
primary
,
tls_certificate_flags_to_reason
(
errors
),
data
->
cert
,
_
(
"Accept"
),
G_CALLBACK
(
user_cert_request_accept_cb
),
_
(
"Reject"
),
G_CALLBACK
(
user_cert_request_deny_cb
),
data
);
g_free
(
primary
);
}
/* Called when a GTlsConnection (which this handler has been connected to)
* has an error validating its certificate.
* Returns TRUE if the certificate is already trusted, so the connection
* can continue.
* Returns FALSE if the certificate is not trusted, causing the
* connection's handshake to fail, and then prompts the user to accept
* the certificate.
*/
static
gboolean
accept_certificate_cb
(
GTlsConnection
*
conn
,
GTlsCertificate
*
peer_cert
,
GTlsCertificateFlags
errors
,
gpointer
user_data
)
{
GTlsCertificate
*
trusted_cert
;
GSocketConnectable
*
connectable
;
const
gchar
*
identity
;
g_return_val_if_fail
(
G_IS_TLS_CLIENT_CONNECTION
(
conn
),
FALSE
);
g_return_val_if_fail
(
G_IS_TLS_CERTIFICATE
(
peer_cert
),
FALSE
);
/* Get the certificate identity from the GTlsClientConnection */
connectable
=
g_tls_client_connection_get_server_identity
(
G_TLS_CLIENT_CONNECTION
(
conn
));
g_return_val_if_fail
(
G_IS_SOCKET_CONNECTABLE
(
connectable
),
FALSE
);
/* identity is owned by the connectable */
if
(
G_IS_NETWORK_ADDRESS
(
connectable
))
{
identity
=
g_network_address_get_hostname
(
G_NETWORK_ADDRESS
(
connectable
));
}
else
if
(
G_IS_NETWORK_SERVICE
(
connectable
))
{
identity
=
g_network_service_get_domain
(
G_NETWORK_SERVICE
(
connectable
));
}
else
{
g_return_val_if_reached
(
FALSE
);
}
/* See if a trusted certificate matching the peer certificate exists */
trusted_cert
=
purple_tls_certificate_new_from_id
(
identity
,
NULL
);
if
(
trusted_cert
!=
NULL
&&
g_tls_certificate_is_same
(
peer_cert
,
trusted_cert
))
{
/* It's manually trusted. Accept certificate */
g_object_unref
(
trusted_cert
);
return
TRUE
;
}
g_clear_object
(
&
trusted_cert
);
/* Certificate failed and isn't trusted.
* Fail certificate and prompt user.
*/
request_accept_certificate
(
identity
,
peer_cert
,
errors
);
return
FALSE
;
}
gpointer
purple_tls_certificate_attach_to_tls_connection
(
GTlsConnection
*
conn
)
{
return
g_object_connect
(
conn
,
"signal::accept-certificate"
,
accept_certificate_cb
,
NULL
,
NULL
);
}
/* Called when GSocketClient signals an event.
* Calls purple_tls_certificate_attach_to_tls_connection() on the client's
* connection when it's about to handshake.
*/
static
void
socket_client_event_cb
(
GSocketClient
*
client
,
GSocketClientEvent
event
,
GSocketConnectable
*
connectable
,
GIOStream
*
connection
,
gpointer
user_data
)
{
if
(
event
==
G_SOCKET_CLIENT_TLS_HANDSHAKING
)
{
/* Attach libpurple's certificate subsystem to the
* GTlsConnection right before it starts the handshake
*/
purple_tls_certificate_attach_to_tls_connection
(
G_TLS_CONNECTION
(
connection
));
}
}
gpointer
purple_tls_certificate_attach_to_socket_client
(
GSocketClient
*
client
)
{
return
g_object_connect
(
client
,
"signal::event"
,
socket_client_event_cb
,
NULL
,
NULL
);
}