pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
stream_management.c was missing from jabber/Makefile.mingw
release-2.x.y
2019-11-11, Gary Kramlich
0857d099906e
stream_management.c was missing from jabber/Makefile.mingw
/**
* @file ssl-gnutls.c GNUTLS SSL plugin.
*
* purple
*
* Copyright (C) 2003 Christian Hammond <chipx86@gnupdate.org>
*
* 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
"debug.h"
#include
"certificate.h"
#include
"plugin.h"
#include
"sslconn.h"
#include
"version.h"
#include
"util.h"
#define SSL_GNUTLS_PLUGIN_ID "ssl-gnutls"
#include
<gnutls/gnutls.h>
#include
<gnutls/x509.h>
typedef
struct
{
gnutls_session_t
session
;
guint
handshake_handler
;
guint
handshake_timer
;
}
PurpleSslGnutlsData
;
#define PURPLE_SSL_GNUTLS_DATA(gsc) ((PurpleSslGnutlsData *)gsc->private_data)
static
gnutls_certificate_client_credentials
xcred
=
NULL
;
#ifdef HAVE_GNUTLS_PRIORITY_FUNCS
/* Priority strings. The default one is, well, the default (and is always
* set). The hash table is of the form hostname => priority (both
* char *).
*
* We only use a gnutls_priority_t for the default on the assumption that
* that's the more common case. Improvement patches (like matching on
* subdomains) welcome.
*/
static
gnutls_priority_t
default_priority
=
NULL
;
static
GHashTable
*
host_priorities
=
NULL
;
#endif
static
void
ssl_gnutls_log
(
int
level
,
const
char
*
str
)
{
/* GnuTLS log messages include the '\n' */
purple_debug_misc
(
"gnutls"
,
"lvl %d: %s"
,
level
,
str
);
}
static
void
ssl_gnutls_init_gnutls
(
void
)
{
const
char
*
debug_level
;
const
char
*
host_priorities_str
;
debug_level
=
g_getenv
(
"PURPLE_GNUTLS_DEBUG"
);
if
(
debug_level
)
{
int
level
=
atoi
(
debug_level
);
if
(
level
<
0
)
{
purple_debug_warning
(
"gnutls"
,
"Assuming log level 0 instead of %d
\n
"
,
level
);
level
=
0
;
}
/* "The level is an integer between 0 and 9. Higher values mean more verbosity." */
gnutls_global_set_log_level
(
level
);
gnutls_global_set_log_function
(
ssl_gnutls_log
);
}
/* Expected format: host=priority;host2=priority;*=priority
* where "*" is used to override the default priority string for
* libpurple.
*/
host_priorities_str
=
g_getenv
(
"PURPLE_GNUTLS_PRIORITIES"
);
if
(
host_priorities_str
)
{
#ifndef HAVE_GNUTLS_PRIORITY_FUNCS
purple_debug_warning
(
"gnutls"
,
"Warning, PURPLE_GNUTLS_PRIORITIES "
"environment variable set, but we were built "
"against an older GnuTLS that doesn't support "
"this. :-("
);
#else
/* HAVE_GNUTLS_PRIORITY_FUNCS */
char
**
entries
=
g_strsplit
(
host_priorities_str
,
";"
,
-1
);
char
*
default_priority_str
=
NULL
;
guint
i
;
host_priorities
=
g_hash_table_new_full
(
g_str_hash
,
g_str_equal
,
g_free
,
g_free
);
for
(
i
=
0
;
entries
[
i
];
++
i
)
{
char
*
host
=
entries
[
i
];
char
*
equals
=
strchr
(
host
,
'='
);
char
*
prio_str
;
if
(
equals
)
{
*
equals
=
'\0'
;
prio_str
=
equals
+
1
;
/* Empty? */
if
(
*
prio_str
==
'\0'
)
{
purple_debug_warning
(
"gnutls"
,
"Ignoring empty priority "
"string for %s
\n
"
,
host
);
}
else
{
/* TODO: Validate each of these and complain */
if
(
purple_strequal
(
host
,
"*"
))
{
/* Override the default priority */
g_free
(
default_priority_str
);
default_priority_str
=
g_strdup
(
prio_str
);
}
else
g_hash_table_insert
(
host_priorities
,
g_strdup
(
host
),
g_strdup
(
prio_str
));
}
}
}
if
(
default_priority_str
)
{
if
(
gnutls_priority_init
(
&
default_priority
,
default_priority_str
,
NULL
))
{
purple_debug_warning
(
"gnutls"
,
"Unable to set default priority to %s
\n
"
,
default_priority_str
);
/* Versions of GnuTLS as of 2.8.6 (2010-03-31) don't free/NULL
* this on error.
*/
gnutls_free
(
default_priority
);
default_priority
=
NULL
;
}
g_free
(
default_priority_str
);
}
g_strfreev
(
entries
);
#endif
/* HAVE_GNUTLS_PRIORITY_FUNCS */
}
#ifdef HAVE_GNUTLS_PRIORITY_FUNCS
/* Make sure we set have a default priority! */
if
(
!
default_priority
)
{
if
(
gnutls_priority_init
(
&
default_priority
,
"NORMAL:%SSL3_RECORD_VERSION"
,
NULL
))
{
/* See comment above about memory leak */
gnutls_free
(
default_priority
);
gnutls_priority_init
(
&
default_priority
,
"NORMAL"
,
NULL
);
}
}
#endif
/* HAVE_GNUTLS_PRIORITY_FUNCS */
gnutls_global_init
();
gnutls_certificate_allocate_credentials
(
&
xcred
);
/* TODO: I can likely remove this */
gnutls_certificate_set_x509_trust_file
(
xcred
,
"ca.pem"
,
GNUTLS_X509_FMT_PEM
);
}
static
gboolean
ssl_gnutls_init
(
void
)
{
return
TRUE
;
}
static
void
ssl_gnutls_uninit
(
void
)
{
gnutls_global_deinit
();
gnutls_certificate_free_credentials
(
xcred
);
xcred
=
NULL
;
#ifdef HAVE_GNUTLS_PRIORITY_FUNCS
if
(
host_priorities
)
{
g_hash_table_destroy
(
host_priorities
);
host_priorities
=
NULL
;
}
gnutls_priority_deinit
(
default_priority
);
default_priority
=
NULL
;
#endif
}
static
void
ssl_gnutls_verified_cb
(
PurpleCertificateVerificationStatus
st
,
gpointer
userdata
)
{
PurpleSslConnection
*
gsc
=
(
PurpleSslConnection
*
)
userdata
;
if
(
st
==
PURPLE_CERTIFICATE_VALID
)
{
/* Certificate valid? Good! Do the connection! */
gsc
->
connect_cb
(
gsc
->
connect_cb_data
,
gsc
,
PURPLE_INPUT_READ
);
}
else
{
/* Otherwise, signal an error */
if
(
gsc
->
error_cb
!=
NULL
)
gsc
->
error_cb
(
gsc
,
PURPLE_SSL_CERTIFICATE_INVALID
,
gsc
->
connect_cb_data
);
purple_ssl_close
(
gsc
);
}
}
static
void
ssl_gnutls_handshake_cb
(
gpointer
data
,
gint
source
,
PurpleInputCondition
cond
)
{
PurpleSslConnection
*
gsc
=
data
;
PurpleSslGnutlsData
*
gnutls_data
=
PURPLE_SSL_GNUTLS_DATA
(
gsc
);
ssize_t
ret
;
/*purple_debug_info("gnutls", "Handshaking with %s\n", gsc->host);*/
ret
=
gnutls_handshake
(
gnutls_data
->
session
);
if
(
ret
==
GNUTLS_E_AGAIN
||
ret
==
GNUTLS_E_INTERRUPTED
)
return
;
purple_input_remove
(
gnutls_data
->
handshake_handler
);
gnutls_data
->
handshake_handler
=
0
;
if
(
ret
!=
0
)
{
purple_debug_error
(
"gnutls"
,
"Handshake failed. Error %s
\n
"
,
gnutls_strerror
(
ret
));
if
(
gsc
->
error_cb
!=
NULL
)
gsc
->
error_cb
(
gsc
,
PURPLE_SSL_HANDSHAKE_FAILED
,
gsc
->
connect_cb_data
);
purple_ssl_close
(
gsc
);
}
else
{
/* Now we are cooking with gas! */
PurpleSslOps
*
ops
=
purple_ssl_get_ops
();
GList
*
peers
=
ops
->
get_peer_certificates
(
gsc
);
PurpleCertificateScheme
*
x509
=
purple_certificate_find_scheme
(
"x509"
);
GList
*
l
;
/* TODO: Remove all this debugging babble */
purple_debug_info
(
"gnutls"
,
"Handshake complete
\n
"
);
for
(
l
=
peers
;
l
;
l
=
l
->
next
)
{
PurpleCertificate
*
crt
=
l
->
data
;
GByteArray
*
z
=
x509
->
get_fingerprint_sha1
(
crt
);
gchar
*
fpr
=
purple_base16_encode_chunked
(
z
->
data
,
z
->
len
);
purple_debug_info
(
"gnutls/x509"
,
"Key print: %s
\n
"
,
fpr
);
/* Kill the cert! */
x509
->
destroy_certificate
(
crt
);
g_free
(
fpr
);
g_byte_array_free
(
z
,
TRUE
);
}
g_list_free
(
peers
);
{
const
gnutls_datum_t
*
cert_list
;
unsigned
int
cert_list_size
=
0
;
gnutls_session_t
session
=
gnutls_data
->
session
;
guint
i
;
cert_list
=
gnutls_certificate_get_peers
(
session
,
&
cert_list_size
);
purple_debug_info
(
"gnutls"
,
"Peer provided %d certs
\n
"
,
cert_list_size
);
for
(
i
=
0
;
i
<
cert_list_size
;
i
++
)
{
gchar
fpr_bin
[
256
];
gsize
fpr_bin_sz
=
sizeof
(
fpr_bin
);
gchar
*
fpr_asc
=
NULL
;
gchar
tbuf
[
256
];
gsize
tsz
=
sizeof
(
tbuf
);
gchar
*
tasc
=
NULL
;
gnutls_x509_crt_t
cert
;
gnutls_x509_crt_init
(
&
cert
);
gnutls_x509_crt_import
(
cert
,
&
cert_list
[
i
],
GNUTLS_X509_FMT_DER
);
gnutls_x509_crt_get_fingerprint
(
cert
,
GNUTLS_DIG_SHA
,
fpr_bin
,
&
fpr_bin_sz
);
fpr_asc
=
purple_base16_encode_chunked
((
const
guchar
*
)
fpr_bin
,
fpr_bin_sz
);
purple_debug_info
(
"gnutls"
,
"Lvl %d SHA1 fingerprint: %s
\n
"
,
i
,
fpr_asc
);
tsz
=
sizeof
(
tbuf
);
gnutls_x509_crt_get_serial
(
cert
,
tbuf
,
&
tsz
);
tasc
=
purple_base16_encode_chunked
((
const
guchar
*
)
tbuf
,
tsz
);
purple_debug_info
(
"gnutls"
,
"Serial: %s
\n
"
,
tasc
);
g_free
(
tasc
);
tsz
=
sizeof
(
tbuf
);
gnutls_x509_crt_get_dn
(
cert
,
tbuf
,
&
tsz
);
purple_debug_info
(
"gnutls"
,
"Cert DN: %s
\n
"
,
tbuf
);
tsz
=
sizeof
(
tbuf
);
gnutls_x509_crt_get_issuer_dn
(
cert
,
tbuf
,
&
tsz
);
purple_debug_info
(
"gnutls"
,
"Cert Issuer DN: %s
\n
"
,
tbuf
);
g_free
(
fpr_asc
);
fpr_asc
=
NULL
;
gnutls_x509_crt_deinit
(
cert
);
}
}
/* TODO: The following logic should really be in libpurple */
/* If a Verifier was given, hand control over to it */
if
(
gsc
->
verifier
)
{
GList
*
peers
;
/* First, get the peer cert chain */
peers
=
purple_ssl_get_peer_certificates
(
gsc
);
/* Now kick off the verification process */
purple_certificate_verify
(
gsc
->
verifier
,
gsc
->
host
,
peers
,
ssl_gnutls_verified_cb
,
gsc
);
purple_certificate_destroy_list
(
peers
);
}
else
{
/* Otherwise, just call the "connection complete"
callback */
gsc
->
connect_cb
(
gsc
->
connect_cb_data
,
gsc
,
cond
);
}
}
}
static
gboolean
start_handshake_cb
(
gpointer
data
)
{
PurpleSslConnection
*
gsc
=
data
;
PurpleSslGnutlsData
*
gnutls_data
=
PURPLE_SSL_GNUTLS_DATA
(
gsc
);
purple_debug_info
(
"gnutls"
,
"Starting handshake with %s
\n
"
,
gsc
->
host
);
gnutls_data
->
handshake_timer
=
0
;
ssl_gnutls_handshake_cb
(
gsc
,
gsc
->
fd
,
PURPLE_INPUT_READ
);
return
FALSE
;
}
static
void
ssl_gnutls_connect
(
PurpleSslConnection
*
gsc
)
{
PurpleSslGnutlsData
*
gnutls_data
;
gnutls_data
=
g_new0
(
PurpleSslGnutlsData
,
1
);
gsc
->
private_data
=
gnutls_data
;
gnutls_init
(
&
gnutls_data
->
session
,
GNUTLS_CLIENT
);
#ifdef HAVE_GNUTLS_PRIORITY_FUNCS
{
const
char
*
prio_str
=
NULL
;
gboolean
set
=
FALSE
;
/* Let's see if someone has specified a specific priority */
if
(
gsc
->
host
&&
host_priorities
)
prio_str
=
g_hash_table_lookup
(
host_priorities
,
gsc
->
host
);
if
(
prio_str
)
set
=
(
GNUTLS_E_SUCCESS
==
gnutls_priority_set_direct
(
gnutls_data
->
session
,
prio_str
,
NULL
));
if
(
!
set
)
gnutls_priority_set
(
gnutls_data
->
session
,
default_priority
);
}
#else
gnutls_set_default_priority
(
gnutls_data
->
session
);
#endif
gnutls_credentials_set
(
gnutls_data
->
session
,
GNUTLS_CRD_CERTIFICATE
,
xcred
);
gnutls_transport_set_ptr
(
gnutls_data
->
session
,
GINT_TO_POINTER
(
gsc
->
fd
));
gnutls_data
->
handshake_handler
=
purple_input_add
(
gsc
->
fd
,
PURPLE_INPUT_READ
,
ssl_gnutls_handshake_cb
,
gsc
);
/* Orborde asks: Why are we configuring a callback, then
(almost) immediately calling it?
Answer: gnutls_handshake (up in handshake_cb) needs to be called
once in order to get the ball rolling on the SSL connection.
Once it has done so, only then will the server reply, triggering
the callback.
Since the logic driving gnutls_handshake is the same with the first
and subsequent calls, we'll just fire the callback immediately to
accomplish this.
*/
gnutls_data
->
handshake_timer
=
purple_timeout_add
(
0
,
start_handshake_cb
,
gsc
);
}
static
void
ssl_gnutls_close
(
PurpleSslConnection
*
gsc
)
{
PurpleSslGnutlsData
*
gnutls_data
=
PURPLE_SSL_GNUTLS_DATA
(
gsc
);
if
(
!
gnutls_data
)
return
;
if
(
gnutls_data
->
handshake_handler
)
purple_input_remove
(
gnutls_data
->
handshake_handler
);
if
(
gnutls_data
->
handshake_timer
)
purple_timeout_remove
(
gnutls_data
->
handshake_timer
);
gnutls_bye
(
gnutls_data
->
session
,
GNUTLS_SHUT_RDWR
);
gnutls_deinit
(
gnutls_data
->
session
);
g_free
(
gnutls_data
);
gsc
->
private_data
=
NULL
;
}
static
size_t
ssl_gnutls_read
(
PurpleSslConnection
*
gsc
,
void
*
data
,
size_t
len
)
{
PurpleSslGnutlsData
*
gnutls_data
=
PURPLE_SSL_GNUTLS_DATA
(
gsc
);
ssize_t
s
;
s
=
gnutls_record_recv
(
gnutls_data
->
session
,
data
,
len
);
if
(
s
==
GNUTLS_E_AGAIN
||
s
==
GNUTLS_E_INTERRUPTED
)
{
s
=
-1
;
errno
=
EAGAIN
;
#ifdef GNUTLS_E_PREMATURE_TERMINATION
}
else
if
(
s
==
GNUTLS_E_PREMATURE_TERMINATION
)
{
purple_debug_warning
(
"gnutls"
,
"Received a FIN on the TCP socket "
"for %s. This either means that the remote server closed "
"the socket without sending us a Close Notify alert or a "
"man-in-the-middle injected a FIN into the TCP stream. "
"Assuming it's the former.
\n
"
,
gsc
->
host
);
#else
}
else
if
(
s
==
GNUTLS_E_UNEXPECTED_PACKET_LENGTH
)
{
purple_debug_warning
(
"gnutls"
,
"Received packet of unexpected "
"length on the TCP socket for %s. Among other "
"possibilities this might mean that the remote server "
"closed the socket without sending us a Close Notify alert. "
"Assuming that's the case for compatibility, however, note "
"that it's quite possible that we're incorrectly ignoing "
"a real error.
\n
"
,
gsc
->
host
);
#endif
/*
* Summary:
* Always treat a closed TCP connection as if the remote server cleanly
* terminated the SSL session.
*
* Background:
* Most TLS servers send a Close Notify alert before sending TCP FIN
* when closing a session. This informs us at the TLS layer that the
* connection is being cleanly closed. Without this it's more
* difficult for us to determine whether the session was closed
* cleanly (we would need to resort to having the application layer
* perform this check, e.g. by looking at the Content-Length HTTP
* header for HTTP connections).
*
* There ARE servers that don't send Close Notify and we want to be
* compatible with them. And so we don't require Close Notify. This
* seems to match the behavior of libnss. This is a slightly
* unfortunate situation. It means a malicious MITM can inject a FIN
* into our TCP stream and cause our encrypted session to termiate
* and we won't indicate any problem to the user.
*
* GnuTLS < 3.0.0 returned the UNEXPECTED_PACKET_LENGTH error on EOF.
* GnuTLS >= 3.0.0 added the PREMATURE_TERMINATION error to allow us
* to detect the problem more specifically.
*
* For historical discussion see:
* https://developer.pidgin.im/ticket/16172
* http://trac.adiumx.com/intertrac/ticket%3A16678
* https://bugzilla.mozilla.org/show_bug.cgi?id=508698#c4
* http://lists.gnu.org/archive/html/gnutls-devel/2008-03/msg00058.html
* Or search for GNUTLS_E_UNEXPECTED_PACKET_LENGTH or
* GNUTLS_E_PREMATURE_TERMINATION
*/
s
=
0
;
}
else
if
(
s
<
0
)
{
purple_debug_error
(
"gnutls"
,
"receive failed: %s
\n
"
,
gnutls_strerror
(
s
));
s
=
-1
;
/*
* TODO: Set errno to something more appropriate. Or even
* better: allow ssl plugins to keep track of their
* own error message, then add a new ssl_ops function
* that returns the error message.
*/
errno
=
EIO
;
}
return
s
;
}
static
size_t
ssl_gnutls_write
(
PurpleSslConnection
*
gsc
,
const
void
*
data
,
size_t
len
)
{
PurpleSslGnutlsData
*
gnutls_data
=
PURPLE_SSL_GNUTLS_DATA
(
gsc
);
ssize_t
s
=
0
;
/* XXX: when will gnutls_data be NULL? */
if
(
gnutls_data
)
s
=
gnutls_record_send
(
gnutls_data
->
session
,
data
,
len
);
if
(
s
==
GNUTLS_E_AGAIN
||
s
==
GNUTLS_E_INTERRUPTED
)
{
s
=
-1
;
errno
=
EAGAIN
;
}
else
if
(
s
<
0
)
{
purple_debug_error
(
"gnutls"
,
"send failed: %s
\n
"
,
gnutls_strerror
(
s
));
s
=
-1
;
/*
* TODO: Set errno to something more appropriate. Or even
* better: allow ssl plugins to keep track of their
* own error message, then add a new ssl_ops function
* that returns the error message.
*/
errno
=
EIO
;
}
return
s
;
}
/* Forward declarations are fun! */
static
PurpleCertificate
*
x509_import_from_datum
(
const
gnutls_datum_t
dt
,
gnutls_x509_crt_fmt_t
mode
);
/* indeed! */
static
gboolean
x509_certificate_signed_by
(
PurpleCertificate
*
crt
,
PurpleCertificate
*
issuer
);
static
void
x509_destroy_certificate
(
PurpleCertificate
*
crt
);
static
GList
*
ssl_gnutls_get_peer_certificates
(
PurpleSslConnection
*
gsc
)
{
PurpleSslGnutlsData
*
gnutls_data
=
PURPLE_SSL_GNUTLS_DATA
(
gsc
);
PurpleCertificate
*
prvcrt
=
NULL
;
/* List of Certificate instances to return */
GList
*
peer_certs
=
NULL
;
/* List of raw certificates as given by GnuTLS */
const
gnutls_datum_t
*
cert_list
;
unsigned
int
cert_list_size
=
0
;
unsigned
int
i
;
/* This should never, ever happen. */
g_return_val_if_fail
(
gnutls_certificate_type_get
(
gnutls_data
->
session
)
==
GNUTLS_CRT_X509
,
NULL
);
/* Get the certificate list from GnuTLS */
/* TODO: I am _pretty sure_ this doesn't block or do other exciting things */
cert_list
=
gnutls_certificate_get_peers
(
gnutls_data
->
session
,
&
cert_list_size
);
/* Convert each certificate to a Certificate and append it to the list */
for
(
i
=
0
;
i
<
cert_list_size
;
i
++
)
{
PurpleCertificate
*
newcrt
=
x509_import_from_datum
(
cert_list
[
i
],
GNUTLS_X509_FMT_DER
);
/* Append is somewhat inefficient on linked lists, but is easy
to read. If someone complains, I'll change it.
TODO: Is anyone complaining? (Maybe elb?) */
/* only append if previous cert was actually signed by this one.
* Thanks Microsoft. */
if
((
newcrt
!=
NULL
)
&&
((
prvcrt
==
NULL
)
||
x509_certificate_signed_by
(
prvcrt
,
newcrt
)))
{
peer_certs
=
g_list_append
(
peer_certs
,
newcrt
);
prvcrt
=
newcrt
;
}
else
{
x509_destroy_certificate
(
newcrt
);
purple_debug_error
(
"gnutls"
,
"Dropping further peer certificates "
"because the chain is broken!
\n
"
);
break
;
}
}
/* cert_list doesn't need free()-ing */
return
peer_certs
;
}
/************************************************************************/
/* X.509 functionality */
/************************************************************************/
const
gchar
*
SCHEME_NAME
=
"x509"
;
static
PurpleCertificateScheme
x509_gnutls
;
/** Refcounted GnuTLS certificate data instance */
typedef
struct
{
gint
refcount
;
gnutls_x509_crt_t
crt
;
}
x509_crtdata_t
;
/** Helper functions for reference counting */
static
x509_crtdata_t
*
x509_crtdata_addref
(
x509_crtdata_t
*
cd
)
{
(
cd
->
refcount
)
++
;
return
cd
;
}
static
void
x509_crtdata_delref
(
x509_crtdata_t
*
cd
)
{
(
cd
->
refcount
)
--
;
if
(
cd
->
refcount
<
0
)
g_critical
(
"Refcount of x509_crtdata_t is %d, which is less "
"than zero!
\n
"
,
cd
->
refcount
);
/* If the refcount reaches zero, kill the structure */
if
(
cd
->
refcount
<=
0
)
{
/* Kill the internal data */
gnutls_x509_crt_deinit
(
cd
->
crt
);
/* And kill the struct */
g_free
(
cd
);
}
}
/** Helper macro to retrieve the GnuTLS crt_t from a PurpleCertificate */
#define X509_GET_GNUTLS_DATA(pcrt) ( ((x509_crtdata_t *) (pcrt->data))->crt)
/** Transforms a gnutls_datum containing an X.509 certificate into a Certificate instance under the x509_gnutls scheme
*
* @param dt Datum to transform
* @param mode GnuTLS certificate format specifier (GNUTLS_X509_FMT_PEM for
* reading from files, and GNUTLS_X509_FMT_DER for converting
* "over the wire" certs for SSL)
*
* @return A newly allocated Certificate structure of the x509_gnutls scheme
*/
static
PurpleCertificate
*
x509_import_from_datum
(
const
gnutls_datum_t
dt
,
gnutls_x509_crt_fmt_t
mode
)
{
/* Internal certificate data structure */
x509_crtdata_t
*
certdat
;
/* New certificate to return */
PurpleCertificate
*
crt
;
/* Allocate and prepare the internal certificate data */
certdat
=
g_new0
(
x509_crtdata_t
,
1
);
if
(
gnutls_x509_crt_init
(
&
(
certdat
->
crt
))
!=
0
)
{
g_free
(
certdat
);
return
NULL
;
}
certdat
->
refcount
=
0
;
/* Perform the actual certificate parse */
/* Yes, certdat->crt should be passed as-is */
if
(
gnutls_x509_crt_import
(
certdat
->
crt
,
&
dt
,
mode
)
!=
0
)
{
g_free
(
certdat
);
return
NULL
;
}
/* Allocate the certificate and load it with data */
crt
=
g_new0
(
PurpleCertificate
,
1
);
crt
->
scheme
=
&
x509_gnutls
;
crt
->
data
=
x509_crtdata_addref
(
certdat
);
return
crt
;
}
/** Imports a PEM-formatted X.509 certificate from the specified file.
* @param filename Filename to import from. Format is PEM
*
* @return A newly allocated Certificate structure of the x509_gnutls scheme
*/
static
PurpleCertificate
*
x509_import_from_file
(
const
gchar
*
filename
)
{
PurpleCertificate
*
crt
;
/* Certificate being constructed */
gchar
*
buf
;
/* Used to load the raw file data */
gsize
buf_sz
;
/* Size of the above */
gnutls_datum_t
dt
;
/* Struct to pass down to GnuTLS */
purple_debug_info
(
"gnutls"
,
"Attempting to load X.509 certificate from %s
\n
"
,
filename
);
/* Next, we'll simply yank the entire contents of the file
into memory */
/* TODO: Should I worry about very large files here? */
if
(
!
g_file_get_contents
(
filename
,
&
buf
,
&
buf_sz
,
NULL
/* No error checking for now */
))
{
return
NULL
;
}
/* Load the datum struct */
dt
.
data
=
(
unsigned
char
*
)
buf
;
dt
.
size
=
buf_sz
;
/* Perform the conversion; files should be in PEM format */
crt
=
x509_import_from_datum
(
dt
,
GNUTLS_X509_FMT_PEM
);
/* Cleanup */
g_free
(
buf
);
return
crt
;
}
/** Imports a number of PEM-formatted X.509 certificates from the specified file.
* @param filename Filename to import from. Format is PEM
*
* @return A newly allocated GSList of Certificate structures of the x509_gnutls scheme
*/
static
GSList
*
x509_importcerts_from_file
(
const
gchar
*
filename
)
{
PurpleCertificate
*
crt
;
/* Certificate being constructed */
gchar
*
buf
;
/* Used to load the raw file data */
gchar
*
begin
,
*
end
;
GSList
*
crts
=
NULL
;
gsize
buf_sz
;
/* Size of the above */
gnutls_datum_t
dt
;
/* Struct to pass down to GnuTLS */
purple_debug_info
(
"gnutls"
,
"Attempting to load X.509 certificates from %s
\n
"
,
filename
);
/* Next, we'll simply yank the entire contents of the file
into memory */
/* TODO: Should I worry about very large files here? */
g_return_val_if_fail
(
g_file_get_contents
(
filename
,
&
buf
,
&
buf_sz
,
NULL
/* No error checking for now */
),
NULL
);
begin
=
buf
;
while
((
end
=
strstr
(
begin
,
"-----END CERTIFICATE-----"
))
!=
NULL
)
{
end
+=
sizeof
(
"-----END CERTIFICATE-----"
)
-1
;
/* Load the datum struct */
dt
.
data
=
(
unsigned
char
*
)
begin
;
dt
.
size
=
(
end
-
begin
);
/* Perform the conversion; files should be in PEM format */
crt
=
x509_import_from_datum
(
dt
,
GNUTLS_X509_FMT_PEM
);
if
(
crt
!=
NULL
)
{
crts
=
g_slist_prepend
(
crts
,
crt
);
}
begin
=
end
;
}
/* Cleanup */
g_free
(
buf
);
return
crts
;
}
/**
* Exports a PEM-formatted X.509 certificate to the specified file.
* @param filename Filename to export to. Format will be PEM
* @param crt Certificate to export
*
* @return TRUE if success, otherwise FALSE
*/
static
gboolean
x509_export_certificate
(
const
gchar
*
filename
,
PurpleCertificate
*
crt
)
{
gnutls_x509_crt_t
crt_dat
;
/* GnuTLS cert struct */
int
ret
;
gchar
*
out_buf
;
/* Data to output */
size_t
out_size
;
/* Output size */
gboolean
success
=
FALSE
;
/* Paranoia paranoia paranoia! */
g_return_val_if_fail
(
filename
,
FALSE
);
g_return_val_if_fail
(
crt
,
FALSE
);
g_return_val_if_fail
(
crt
->
scheme
==
&
x509_gnutls
,
FALSE
);
g_return_val_if_fail
(
crt
->
data
,
FALSE
);
crt_dat
=
X509_GET_GNUTLS_DATA
(
crt
);
/* Obtain the output size required */
out_size
=
0
;
ret
=
gnutls_x509_crt_export
(
crt_dat
,
GNUTLS_X509_FMT_PEM
,
NULL
,
/* Provide no buffer yet */
&
out_size
/* Put size here */
);
g_return_val_if_fail
(
ret
==
GNUTLS_E_SHORT_MEMORY_BUFFER
,
FALSE
);
/* Now allocate a buffer and *really* export it */
out_buf
=
g_new0
(
gchar
,
out_size
);
ret
=
gnutls_x509_crt_export
(
crt_dat
,
GNUTLS_X509_FMT_PEM
,
out_buf
,
/* Export to our new buffer */
&
out_size
/* Put size here */
);
if
(
ret
!=
0
)
{
purple_debug_error
(
"gnutls/x509"
,
"Failed to export cert to buffer with code %d
\n
"
,
ret
);
g_free
(
out_buf
);
return
FALSE
;
}
/* Write it out to an actual file */
success
=
purple_util_write_data_to_file_absolute
(
filename
,
out_buf
,
out_size
);
g_free
(
out_buf
);
return
success
;
}
static
PurpleCertificate
*
x509_copy_certificate
(
PurpleCertificate
*
crt
)
{
x509_crtdata_t
*
crtdat
;
PurpleCertificate
*
newcrt
;
g_return_val_if_fail
(
crt
,
NULL
);
g_return_val_if_fail
(
crt
->
scheme
==
&
x509_gnutls
,
NULL
);
crtdat
=
(
x509_crtdata_t
*
)
crt
->
data
;
newcrt
=
g_new0
(
PurpleCertificate
,
1
);
newcrt
->
scheme
=
&
x509_gnutls
;
newcrt
->
data
=
x509_crtdata_addref
(
crtdat
);
return
newcrt
;
}
/** Frees a Certificate
*
* Destroys a Certificate's internal data structures and frees the pointer
* given.
* @param crt Certificate instance to be destroyed. It WILL NOT be destroyed
* if it is not of the correct CertificateScheme. Can be NULL
*
*/
static
void
x509_destroy_certificate
(
PurpleCertificate
*
crt
)
{
if
(
NULL
==
crt
)
return
;
/* Check that the scheme is x509_gnutls */
if
(
crt
->
scheme
!=
&
x509_gnutls
)
{
purple_debug_error
(
"gnutls"
,
"destroy_certificate attempted on certificate of wrong scheme (scheme was %s, expected %s)
\n
"
,
crt
->
scheme
->
name
,
SCHEME_NAME
);
return
;
}
g_return_if_fail
(
crt
->
data
!=
NULL
);
g_return_if_fail
(
crt
->
scheme
!=
NULL
);
/* Use the reference counting system to free (or not) the
underlying data */
x509_crtdata_delref
((
x509_crtdata_t
*
)
crt
->
data
);
/* Kill the structure itself */
g_free
(
crt
);
}
/** Determines whether one certificate has been issued and signed by another
*
* @param crt Certificate to check the signature of
* @param issuer Issuer's certificate
*
* @return TRUE if crt was signed and issued by issuer, otherwise FALSE
* @TODO Modify this function to return a reason for invalidity?
*/
static
gboolean
x509_certificate_signed_by
(
PurpleCertificate
*
crt
,
PurpleCertificate
*
issuer
)
{
gnutls_x509_crt_t
crt_dat
;
gnutls_x509_crt_t
issuer_dat
;
unsigned
int
verify
;
/* used to store result from GnuTLS verifier */
int
ret
;
gchar
*
crt_id
=
NULL
;
gchar
*
issuer_id
=
NULL
;
g_return_val_if_fail
(
crt
,
FALSE
);
g_return_val_if_fail
(
issuer
,
FALSE
);
/* Verify that both certs are the correct scheme */
g_return_val_if_fail
(
crt
->
scheme
==
&
x509_gnutls
,
FALSE
);
g_return_val_if_fail
(
issuer
->
scheme
==
&
x509_gnutls
,
FALSE
);
/* TODO: check for more nullness? */
crt_dat
=
X509_GET_GNUTLS_DATA
(
crt
);
issuer_dat
=
X509_GET_GNUTLS_DATA
(
issuer
);
/* Ensure crt issuer matches the name on the issuer cert. */
ret
=
gnutls_x509_crt_check_issuer
(
crt_dat
,
issuer_dat
);
if
(
ret
<=
0
)
{
if
(
ret
<
0
)
{
purple_debug_error
(
"gnutls/x509"
,
"GnuTLS error %d while checking certificate issuer match."
,
ret
);
}
else
{
gchar
*
crt_id
,
*
issuer_id
,
*
crt_issuer_id
;
crt_id
=
purple_certificate_get_unique_id
(
crt
);
issuer_id
=
purple_certificate_get_unique_id
(
issuer
);
crt_issuer_id
=
purple_certificate_get_issuer_unique_id
(
crt
);
purple_debug_info
(
"gnutls/x509"
,
"Certificate %s is issued by "
"%s, which does not match %s.
\n
"
,
crt_id
?
crt_id
:
"(null)"
,
crt_issuer_id
?
crt_issuer_id
:
"(null)"
,
issuer_id
?
issuer_id
:
"(null)"
);
g_free
(
crt_id
);
g_free
(
issuer_id
);
g_free
(
crt_issuer_id
);
}
/* The issuer is not correct, or there were errors */
return
FALSE
;
}
/* Check basic constraints extension (if it exists then the CA flag must
be set to true, and it must exist for certs with version 3 or higher. */
ret
=
gnutls_x509_crt_get_basic_constraints
(
issuer_dat
,
NULL
,
NULL
,
NULL
);
if
(
ret
==
GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE
)
{
if
(
gnutls_x509_crt_get_version
(
issuer_dat
)
>=
3
)
{
/* Reject cert (no basic constraints and cert version is >= 3). */
gchar
*
issuer_id
=
purple_certificate_get_unique_id
(
issuer
);
purple_debug_info
(
"gnutls/x509"
,
"Rejecting cert because the "
"basic constraints extension is missing from issuer cert "
"for %s. The basic constraints extension is required on "
"all version 3 or higher certs (this cert is version %d)."
,
issuer_id
?
issuer_id
:
"(null)"
,
gnutls_x509_crt_get_version
(
issuer_dat
));
g_free
(
issuer_id
);
return
FALSE
;
}
else
{
/* Allow cert (no basic constraints and cert version is < 3). */
purple_debug_info
(
"gnutls/x509"
,
"Basic constraint extension is "
"missing from issuer cert for %s. Allowing this because "
"the cert is version %d and the basic constraints "
"extension is only required for version 3 or higher "
"certs."
,
issuer_id
?
issuer_id
:
"(null)"
,
gnutls_x509_crt_get_version
(
issuer_dat
));
}
}
else
if
(
ret
<=
0
)
{
/* Reject cert (CA flag is false in basic constraints). */
gchar
*
issuer_id
=
purple_certificate_get_unique_id
(
issuer
);
purple_debug_info
(
"gnutls/x509"
,
"Rejecting cert because the CA flag "
"is set to false in the basic constraints extension for "
"issuer cert %s. ret=%d
\n
"
,
issuer_id
?
issuer_id
:
"(null)"
,
ret
);
g_free
(
issuer_id
);
return
FALSE
;
}
/* Now, check the signature */
/* The second argument is a ptr to an array of "trusted" issuer certs,
but we're only using one trusted one */
ret
=
gnutls_x509_crt_verify
(
crt_dat
,
&
issuer_dat
,
1
,
/* Permit signings by X.509v1 certs
(Verisign and possibly others have
root certificates that predate the
current standard) */
GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT
,
&
verify
);
if
(
ret
!=
0
)
{
purple_debug_error
(
"gnutls/x509"
,
"Attempted certificate verification caused a GnuTLS error code %d. I will just say the signature is bad, but you should look into this.
\n
"
,
ret
);
return
FALSE
;
}
#ifdef HAVE_GNUTLS_CERT_INSECURE_ALGORITHM
if
(
verify
&
GNUTLS_CERT_INSECURE_ALGORITHM
)
{
/*
* A certificate in the chain is signed with an insecure
* algorithm. Put a warning into the log to make this error
* perfectly clear as soon as someone looks at the debug log is
* generated.
*/
crt_id
=
purple_certificate_get_unique_id
(
crt
);
issuer_id
=
purple_certificate_get_issuer_unique_id
(
crt
);
purple_debug_warning
(
"gnutls/x509"
,
"Insecure hash algorithm used by %s to sign %s
\n
"
,
issuer_id
,
crt_id
);
}
#endif
if
(
verify
&
GNUTLS_CERT_INVALID
)
{
/* Signature didn't check out, but at least
there were no errors*/
if
(
!
crt_id
)
crt_id
=
purple_certificate_get_unique_id
(
crt
);
if
(
!
issuer_id
)
issuer_id
=
purple_certificate_get_issuer_unique_id
(
crt
);
purple_debug_error
(
"gnutls/x509"
,
"Bad signature from %s on %s
\n
"
,
issuer_id
,
crt_id
);
g_free
(
crt_id
);
g_free
(
issuer_id
);
return
FALSE
;
}
/* if (ret, etc.) */
/* If we got here, the signature is good */
return
TRUE
;
}
static
GByteArray
*
x509_shasum
(
PurpleCertificate
*
crt
,
gnutls_digest_algorithm_t
algo
)
{
size_t
hashlen
=
(
algo
==
GNUTLS_DIG_SHA1
)
?
20
:
32
;
size_t
tmpsz
=
hashlen
;
/* Throw-away variable for GnuTLS to stomp on*/
gnutls_x509_crt_t
crt_dat
;
GByteArray
*
hash
;
/**< Final hash container */
guchar
hashbuf
[
hashlen
];
/**< Temporary buffer to contain hash */
g_return_val_if_fail
(
crt
,
NULL
);
crt_dat
=
X509_GET_GNUTLS_DATA
(
crt
);
/* Extract the fingerprint */
g_return_val_if_fail
(
0
==
gnutls_x509_crt_get_fingerprint
(
crt_dat
,
algo
,
hashbuf
,
&
tmpsz
),
NULL
);
/* This shouldn't happen */
g_return_val_if_fail
(
tmpsz
==
hashlen
,
NULL
);
/* Okay, now create and fill hash array */
hash
=
g_byte_array_new
();
g_byte_array_append
(
hash
,
hashbuf
,
hashlen
);
return
hash
;
}
static
GByteArray
*
x509_sha1sum
(
PurpleCertificate
*
crt
)
{
return
x509_shasum
(
crt
,
GNUTLS_DIG_SHA1
);
}
static
GByteArray
*
x509_sha256sum
(
PurpleCertificate
*
crt
)
{
return
x509_shasum
(
crt
,
GNUTLS_DIG_SHA256
);
}
static
gchar
*
x509_cert_dn
(
PurpleCertificate
*
crt
)
{
gnutls_x509_crt_t
cert_dat
;
gchar
*
dn
=
NULL
;
size_t
dn_size
;
g_return_val_if_fail
(
crt
,
NULL
);
g_return_val_if_fail
(
crt
->
scheme
==
&
x509_gnutls
,
NULL
);
cert_dat
=
X509_GET_GNUTLS_DATA
(
crt
);
/* Figure out the length of the Distinguished Name */
/* Claim that the buffer is size 0 so GnuTLS just tells us how much
space it needs */
dn_size
=
0
;
gnutls_x509_crt_get_dn
(
cert_dat
,
dn
,
&
dn_size
);
/* Now allocate and get the Distinguished Name */
/* Old versions of GnuTLS have an off-by-one error in reporting
the size of the needed buffer in some functions, so allocate
an extra byte */
dn
=
g_new0
(
gchar
,
++
dn_size
);
if
(
0
!=
gnutls_x509_crt_get_dn
(
cert_dat
,
dn
,
&
dn_size
))
{
purple_debug_error
(
"gnutls/x509"
,
"Failed to get Distinguished Name
\n
"
);
g_free
(
dn
);
return
NULL
;
}
return
dn
;
}
static
gchar
*
x509_issuer_dn
(
PurpleCertificate
*
crt
)
{
gnutls_x509_crt_t
cert_dat
;
gchar
*
dn
=
NULL
;
size_t
dn_size
;
g_return_val_if_fail
(
crt
,
NULL
);
g_return_val_if_fail
(
crt
->
scheme
==
&
x509_gnutls
,
NULL
);
cert_dat
=
X509_GET_GNUTLS_DATA
(
crt
);
/* Figure out the length of the Distinguished Name */
/* Claim that the buffer is size 0 so GnuTLS just tells us how much
space it needs */
dn_size
=
0
;
gnutls_x509_crt_get_issuer_dn
(
cert_dat
,
dn
,
&
dn_size
);
/* Now allocate and get the Distinguished Name */
/* Old versions of GnuTLS have an off-by-one error in reporting
the size of the needed buffer in some functions, so allocate
an extra byte */
dn
=
g_new0
(
gchar
,
++
dn_size
);
if
(
0
!=
gnutls_x509_crt_get_issuer_dn
(
cert_dat
,
dn
,
&
dn_size
))
{
purple_debug_error
(
"gnutls/x509"
,
"Failed to get issuer's Distinguished "
"Name
\n
"
);
g_free
(
dn
);
return
NULL
;
}
return
dn
;
}
static
gchar
*
x509_common_name
(
PurpleCertificate
*
crt
)
{
gnutls_x509_crt_t
cert_dat
;
gchar
*
cn
=
NULL
;
size_t
cn_size
;
int
ret
;
g_return_val_if_fail
(
crt
,
NULL
);
g_return_val_if_fail
(
crt
->
scheme
==
&
x509_gnutls
,
NULL
);
cert_dat
=
X509_GET_GNUTLS_DATA
(
crt
);
/* Figure out the length of the Common Name */
/* Claim that the buffer is size 0 so GnuTLS just tells us how much
space it needs */
cn_size
=
0
;
gnutls_x509_crt_get_dn_by_oid
(
cert_dat
,
GNUTLS_OID_X520_COMMON_NAME
,
0
,
/* First CN found, please */
0
,
/* Not in raw mode */
cn
,
&
cn_size
);
/* Now allocate and get the Common Name */
/* Old versions of GnuTLS have an off-by-one error in reporting
the size of the needed buffer in some functions, so allocate
an extra byte */
cn
=
g_new0
(
gchar
,
++
cn_size
);
ret
=
gnutls_x509_crt_get_dn_by_oid
(
cert_dat
,
GNUTLS_OID_X520_COMMON_NAME
,
0
,
/* First CN found, please */
0
,
/* Not in raw mode */
cn
,
&
cn_size
);
if
(
ret
!=
0
)
{
purple_debug_error
(
"gnutls/x509"
,
"Failed to get Common Name
\n
"
);
g_free
(
cn
);
return
NULL
;
}
return
cn
;
}
static
gboolean
x509_check_name
(
PurpleCertificate
*
crt
,
const
gchar
*
name
)
{
gnutls_x509_crt_t
crt_dat
;
g_return_val_if_fail
(
crt
,
FALSE
);
g_return_val_if_fail
(
crt
->
scheme
==
&
x509_gnutls
,
FALSE
);
g_return_val_if_fail
(
name
,
FALSE
);
crt_dat
=
X509_GET_GNUTLS_DATA
(
crt
);
if
(
gnutls_x509_crt_check_hostname
(
crt_dat
,
name
))
{
return
TRUE
;
}
else
{
return
FALSE
;
}
}
static
gboolean
x509_times
(
PurpleCertificate
*
crt
,
time_t
*
activation
,
time_t
*
expiration
)
{
gnutls_x509_crt_t
crt_dat
;
/* GnuTLS time functions return this on error */
const
time_t
errval
=
(
time_t
)
(
-1
);
gboolean
success
=
TRUE
;
g_return_val_if_fail
(
crt
,
FALSE
);
g_return_val_if_fail
(
crt
->
scheme
==
&
x509_gnutls
,
FALSE
);
crt_dat
=
X509_GET_GNUTLS_DATA
(
crt
);
if
(
activation
)
{
*
activation
=
gnutls_x509_crt_get_activation_time
(
crt_dat
);
if
(
*
activation
==
errval
)
success
=
FALSE
;
}
if
(
expiration
)
{
*
expiration
=
gnutls_x509_crt_get_expiration_time
(
crt_dat
);
if
(
*
expiration
==
errval
)
success
=
FALSE
;
}
return
success
;
}
/* GNUTLS_KEYID_USE_BEST_KNOWN was added in gnutls 3.4.1, but can't ifdef it
* because it's an enum member. Older versions will ignore it, which means
* using SHA1 instead of SHA256 to compare pubkeys. But hey, not my fault. */
#if GNUTLS_VERSION_NUMBER < 0x030401
#define KEYID_FLAG (1<<30)
#else
#define KEYID_FLAG GNUTLS_KEYID_USE_BEST_KNOWN
#endif
static
gboolean
x509_compare_pubkeys
(
PurpleCertificate
*
crt1
,
PurpleCertificate
*
crt2
)
{
gnutls_x509_crt_t
crt_dat1
,
crt_dat2
;
unsigned
char
buffer1
[
64
],
buffer2
[
64
];
size_t
size1
,
size2
;
size1
=
size2
=
sizeof
(
buffer1
);
g_return_val_if_fail
(
crt1
&&
crt2
,
FALSE
);
g_return_val_if_fail
(
crt1
->
scheme
==
&
x509_gnutls
,
FALSE
);
g_return_val_if_fail
(
crt2
->
scheme
==
&
x509_gnutls
,
FALSE
);
crt_dat1
=
X509_GET_GNUTLS_DATA
(
crt1
);
if
(
gnutls_x509_crt_get_key_id
(
crt_dat1
,
KEYID_FLAG
,
buffer1
,
&
size1
)
!=
0
)
{
return
FALSE
;
}
crt_dat2
=
X509_GET_GNUTLS_DATA
(
crt2
);
if
(
gnutls_x509_crt_get_key_id
(
crt_dat2
,
KEYID_FLAG
,
buffer2
,
&
size2
)
!=
0
)
{
return
FALSE
;
}
if
(
size1
!=
size2
)
{
return
FALSE
;
}
return
memcmp
(
buffer1
,
buffer2
,
size1
)
==
0
;
}
/* X.509 certificate operations provided by this plugin */
static
PurpleCertificateScheme
x509_gnutls
=
{
"x509"
,
/* Scheme name */
N_
(
"X.509 Certificates"
),
/* User-visible scheme name */
x509_import_from_file
,
/* Certificate import function */
x509_export_certificate
,
/* Certificate export function */
x509_copy_certificate
,
/* Copy */
x509_destroy_certificate
,
/* Destroy cert */
x509_certificate_signed_by
,
/* Signature checker */
x509_sha1sum
,
/* SHA1 fingerprint */
x509_cert_dn
,
/* Unique ID */
x509_issuer_dn
,
/* Issuer Unique ID */
x509_common_name
,
/* Subject name */
x509_check_name
,
/* Check subject name */
x509_times
,
/* Activation/Expiration time */
x509_importcerts_from_file
,
/* Multiple certificates import function */
NULL
,
NULL
,
sizeof
(
PurpleCertificateScheme
),
/* struct_size */
x509_sha256sum
,
/* SHA256 fingerprint */
x509_compare_pubkeys
,
/* Compare public keys */
};
static
PurpleSslOps
ssl_ops
=
{
ssl_gnutls_init
,
ssl_gnutls_uninit
,
ssl_gnutls_connect
,
ssl_gnutls_close
,
ssl_gnutls_read
,
ssl_gnutls_write
,
ssl_gnutls_get_peer_certificates
,
/* padding */
NULL
,
NULL
,
NULL
};
static
gboolean
plugin_load
(
PurplePlugin
*
plugin
)
{
if
(
!
purple_ssl_get_ops
())
{
purple_ssl_set_ops
(
&
ssl_ops
);
}
/* Init GNUTLS now so others can use it even if sslconn never does */
ssl_gnutls_init_gnutls
();
/* Register that we're providing an X.509 CertScheme */
purple_certificate_register_scheme
(
&
x509_gnutls
);
return
TRUE
;
}
static
gboolean
plugin_unload
(
PurplePlugin
*
plugin
)
{
if
(
purple_ssl_get_ops
()
==
&
ssl_ops
)
{
purple_ssl_set_ops
(
NULL
);
}
purple_certificate_unregister_scheme
(
&
x509_gnutls
);
return
TRUE
;
}
static
PurplePluginInfo
info
=
{
PURPLE_PLUGIN_MAGIC
,
PURPLE_MAJOR_VERSION
,
PURPLE_MINOR_VERSION
,
PURPLE_PLUGIN_STANDARD
,
/**< type */
NULL
,
/**< ui_requirement */
PURPLE_PLUGIN_FLAG_INVISIBLE
,
/**< flags */
NULL
,
/**< dependencies */
PURPLE_PRIORITY_DEFAULT
,
/**< priority */
SSL_GNUTLS_PLUGIN_ID
,
/**< id */
N_
(
"GNUTLS"
),
/**< name */
DISPLAY_VERSION
,
/**< version */
/** summary */
N_
(
"Provides SSL support through GNUTLS."
),
/** description */
N_
(
"Provides SSL support through GNUTLS."
),
"Christian Hammond <chipx86@gnupdate.org>"
,
PURPLE_WEBSITE
,
/**< homepage */
plugin_load
,
/**< load */
plugin_unload
,
/**< unload */
NULL
,
/**< destroy */
NULL
,
/**< ui_info */
NULL
,
/**< extra_info */
NULL
,
/**< prefs_info */
NULL
,
/**< actions */
/* padding */
NULL
,
NULL
,
NULL
,
NULL
};
static
void
init_plugin
(
PurplePlugin
*
plugin
)
{
}
PURPLE_INIT_PLUGIN
(
ssl_gnutls
,
init_plugin
,
info
)