You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdelibs/tdeio/tdeio/tcpslavebase.cpp

1356 lines
40 KiB

/*
* $Id$
*
* Copyright (C) 2000 Alex Zepeda <zipzippy@sonic.net
* Copyright (C) 2001-2003 George Staikos <staikos@kde.org>
* Copyright (C) 2001 Dawit Alemayehu <adawit@kde.org>
*
* This file is part of the KDE project
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <ksocks.h>
#include <kdebug.h>
#include <ksslall.h>
#include <ksslcertdlg.h>
#include <tdemessagebox.h>
#ifndef TQ_WS_WIN //temporary
#include <kresolver.h>
#endif
#include <tdelocale.h>
#include <dcopclient.h>
#include <tqcstring.h>
#include <tqdatastream.h>
#include <tdeapplication.h>
#include <tdeprotocolmanager.h>
#include <kde_file.h>
#include "tdeio/tcpslavebase.h"
using namespace TDEIO;
class TCPSlaveBase::TcpSlaveBasePrivate
{
public:
TcpSlaveBasePrivate() : rblockSz(256), militantSSL(false), userAborted(false) {}
~TcpSlaveBasePrivate() {}
KSSL *kssl;
bool usingTLS;
KSSLCertificateCache *cc;
TQString host;
TQString realHost;
TQString ip;
DCOPClient *dcc;
KSSLPKCS12 *pkcs;
int status;
int timeout;
int rblockSz; // Size for reading blocks in readLine()
bool block;
bool useSSLTunneling;
bool needSSLHandShake;
bool militantSSL; // If true, we just drop a connection silently
// if SSL certificate check fails in any way.
bool userAborted;
MetaData savedMetaData;
};
TCPSlaveBase::TCPSlaveBase(unsigned short int defaultPort,
const TQCString &protocol,
const TQCString &poolSocket,
const TQCString &appSocket)
:SlaveBase (protocol, poolSocket, appSocket),
m_iSock(-1),
m_iDefaultPort(defaultPort),
m_sServiceName(protocol),
fp(0)
{
// We have to have two constructors, so don't add anything
// else in here. Put it in doConstructorStuff() instead.
doConstructorStuff();
m_bIsSSL = false;
}
TCPSlaveBase::TCPSlaveBase(unsigned short int defaultPort,
const TQCString &protocol,
const TQCString &poolSocket,
const TQCString &appSocket,
bool useSSL)
:SlaveBase (protocol, poolSocket, appSocket),
m_iSock(-1),
m_bIsSSL(useSSL),
m_iDefaultPort(defaultPort),
m_sServiceName(protocol),
fp(0)
{
doConstructorStuff();
if (useSSL)
m_bIsSSL = initializeSSL();
}
// The constructor procedures go here now.
void TCPSlaveBase::doConstructorStuff()
{
d = new TcpSlaveBasePrivate;
d->kssl = 0L;
d->ip = "";
d->cc = 0L;
d->usingTLS = false;
d->dcc = 0L;
d->pkcs = 0L;
d->status = -1;
d->timeout = KProtocolManager::connectTimeout();
d->block = false;
d->useSSLTunneling = false;
}
TCPSlaveBase::~TCPSlaveBase()
{
cleanSSL();
if (d->usingTLS) delete d->kssl;
if (d->dcc) delete d->dcc;
if (d->pkcs) delete d->pkcs;
delete d;
}
ssize_t TCPSlaveBase::write(const void *data, ssize_t len)
{
#ifdef Q_OS_UNIX
if ( (m_bIsSSL || d->usingTLS) && !d->useSSLTunneling )
{
if ( d->needSSLHandShake )
(void) doSSLHandShake( true );
return d->kssl->write(data, len);
}
return KSocks::self()->write(m_iSock, data, len);
#else
return 0;
#endif
}
ssize_t TCPSlaveBase::read(void *data, ssize_t len)
{
#ifdef Q_OS_UNIX
if ( (m_bIsSSL || d->usingTLS) && !d->useSSLTunneling )
{
if ( d->needSSLHandShake )
(void) doSSLHandShake( true );
return d->kssl->read(data, len);
}
return KSocks::self()->read(m_iSock, data, len);
#else
return 0;
#endif
}
void TCPSlaveBase::setBlockSize(int sz)
{
if (sz <= 0)
sz = 1;
d->rblockSz = sz;
}
ssize_t TCPSlaveBase::readLine(char *data, ssize_t len)
{
// Optimization:
// It's small, but it probably results in a gain on very high
// speed connections. I moved 3 if statements out of the while loop
// so that the while loop is as small as possible. (GS)
// let's not segfault!
if (!data)
return -1;
char tmpbuf[1024]; // 1kb temporary buffer for peeking
*data = 0;
ssize_t clen = 0;
char *buf = data;
int rc = 0;
if ((m_bIsSSL || d->usingTLS) && !d->useSSLTunneling) { // SSL CASE
if ( d->needSSLHandShake )
(void) doSSLHandShake( true );
while (clen < len-1) {
rc = d->kssl->pending();
if (rc > 0) { // Read a chunk
int bytes = rc;
if (bytes > d->rblockSz)
bytes = d->rblockSz;
rc = d->kssl->peek(tmpbuf, bytes);
if (rc <= 0) {
// FIXME: this doesn't cover rc == 0 case
return -1;
}
bytes = rc; // in case it contains no \n
for (int i = 0; i < rc; i++) {
if (tmpbuf[i] == '\n') {
bytes = i+1;
break;
}
}
if (bytes+clen >= len) // don't read too much!
bytes = len - clen - 1;
rc = d->kssl->read(buf, bytes);
if (rc > 0) {
clen += rc;
buf += (rc-1);
if (*buf++ == '\n')
break;
} else {
// FIXME: different case if rc == 0;
return -1;
}
} else { // Read a byte
rc = d->kssl->read(buf, 1);
if (rc <= 0) {
return -1;
// hm rc = 0 then
// SSL_read says to call SSL_get_error to see if
// this was an error. FIXME
} else {
clen++;
if (*buf++ == '\n')
break;
}
}
}
} else { // NON SSL CASE
while (clen < len-1) {
#ifdef Q_OS_UNIX
rc = KSocks::self()->read(m_iSock, buf, 1);
#else
rc = 0;
#endif
if (rc <= 0) {
// FIXME: this doesn't cover rc == 0 case
return -1;
} else {
clen++;
if (*buf++ == '\n')
break;
}
}
}
// Both cases fall through to here
*buf = 0;
return clen;
}
unsigned short int TCPSlaveBase::port(unsigned short int _p)
{
unsigned short int p = _p;
if (_p <= 0)
{
p = m_iDefaultPort;
}
return p;
}
// This function is simply a wrapper to establish the connection
// to the server. It's a bit more complicated than ::connect
// because we first have to check to see if the user specified
// a port, and if so use it, otherwise we check to see if there
// is a port specified in /etc/services, and if so use that
// otherwise as a last resort use the supplied default port.
bool TCPSlaveBase::connectToHost( const TQString &host,
unsigned int _port,
bool sendError )
{
#ifdef Q_OS_UNIX
unsigned short int p;
KExtendedSocket ks;
d->userAborted = false;
// - leaving SSL - warn before we even connect
if (metaData("main_frame_request") == "TRUE" &&
metaData("ssl_activate_warnings") == "TRUE" &&
metaData("ssl_was_in_use") == "TRUE" &&
!m_bIsSSL) {
KSSLSettings kss;
if (kss.warnOnLeave()) {
int result = messageBox( i18n("You are about to leave secure "
"mode. Transmissions will no "
"longer be encrypted.\nThis "
"means that a third party could "
"observe your data in transit."),
WarningContinueCancel,
i18n("Security Information"),
i18n("C&ontinue Loading"), TQString::null,
"WarnOnLeaveSSLMode" );
// Move this setting into KSSL instead
TDEConfig *config = new TDEConfig("tdeioslaverc");
config->setGroup("Notification Messages");
if (!config->readBoolEntry("WarnOnLeaveSSLMode", true)) {
config->deleteEntry("WarnOnLeaveSSLMode");
config->sync();
kss.setWarnOnLeave(false);
kss.save();
}
delete config;
if ( result == KMessageBox::Cancel ) {
d->userAborted = true;
return false;
}
}
}
d->status = -1;
d->host = host;
d->needSSLHandShake = m_bIsSSL;
p = port(_port);
ks.setAddress(host, p);
if ( d->timeout > -1 )
ks.setTimeout( d->timeout );
if (ks.connect() < 0)
{
d->status = ks.status();
if ( sendError )
{
if (d->status == IO_LookupError)
error( ERR_UNKNOWN_HOST, host);
else if ( d->status != -1 )
error( ERR_COULD_NOT_CONNECT, host);
}
return false;
}
m_iSock = ks.fd();
// store the IP for later
const TDESocketAddress *sa = ks.peerAddress();
if (sa)
d->ip = sa->nodeName();
else
d->ip = "";
ks.release(); // KExtendedSocket no longer applicable
if ( d->block != ks.blockingMode() )
ks.setBlockingMode( d->block );
m_iPort=p;
if (m_bIsSSL && !d->useSSLTunneling) {
if ( !doSSLHandShake( sendError ) )
return false;
}
else
setMetaData("ssl_in_use", "FALSE");
// Since we want to use stdio on the socket,
// we must fdopen it to get a file pointer,
// if it fails, close everything up
if ((fp = KDE_fdopen(m_iSock, "w+")) == 0) {
closeDescriptor();
return false;
}
return true;
#else //!Q_OS_UNIX
return false;
#endif //Q_OS_UNIX
}
void TCPSlaveBase::closeDescriptor()
{
stopTLS();
if (fp) {
fclose(fp);
fp=0;
m_iSock=-1;
if (m_bIsSSL)
d->kssl->close();
}
if (m_iSock != -1) {
close(m_iSock);
m_iSock=-1;
}
d->ip = "";
d->host = "";
}
bool TCPSlaveBase::initializeSSL()
{
if (m_bIsSSL) {
if (KSSL::doesSSLWork()) {
d->kssl = new KSSL;
return true;
}
}
return false;
}
void TCPSlaveBase::cleanSSL()
{
delete d->cc;
if (m_bIsSSL) {
delete d->kssl;
d->kssl = 0;
}
d->militantSSL = false;
}
bool TCPSlaveBase::atEnd()
{
return feof(fp);
}
int TCPSlaveBase::startTLS()
{
if (d->usingTLS || d->useSSLTunneling || m_bIsSSL || !KSSL::doesSSLWork())
return false;
d->kssl = new KSSL(false);
if (!d->kssl->TLSInit()) {
delete d->kssl;
return -1;
}
if ( !d->realHost.isEmpty() )
{
kdDebug(7029) << "Setting real hostname: " << d->realHost << endl;
d->kssl->setPeerHost(d->realHost);
} else {
kdDebug(7029) << "Setting real hostname: " << d->host << endl;
d->kssl->setPeerHost(d->host);
}
if (hasMetaData("ssl_session_id")) {
KSSLSession *s = KSSLSession::fromString(metaData("ssl_session_id"));
if (s) {
Added support for OpenSSL 1.1 Some KOpenSSLProxy methods have been renamed to be consistent with OpenSSL 1.1 API names and to prevent hidden API changes. To ensure API / ABI compatibility, the original methods are still included but have been marked as deprecated. + SSLv23_client_method => TLS_client_method + X509_STORE_CTX_set_chain => X509_STORE_CTX_set0_untrusted + sk_dup => OPENSSL_sk_dup + sk_free => OPENSSL_sk_free + sk_new => OPENSSL_sk_new + sk_num => OPENSSL_sk_num + sk_pop => OPENSSL_sk_pop + sk_push => OPENSSL_sk_push + sk_value => OPENSSL_sk_value Additional methods have been added to KOpenSSLProxy to support the new OpenSSL 1.1 API functions that provide access to the (now) opaque SSL structures. Compatibility with OpenSSL < 1.1 is handled internally in KOpenSSLProxy. + BIO_get_data + DSA_get0_key + DSA_get0_pqg + EVP_PKEY_base_id + EVP_PKEY_get0_DSA + EVP_PKEY_get0_RSA + RSA_get0_key + X509_CRL_get0_lastUpdate + X509_CRL_get0_nextUpdate + X509_OBJECT_get0_X509 + X509_OBJECT_get_type + X509_STORE_CTX_get_current_cert + X509_STORE_CTX_get_error + X509_STORE_CTX_get_error_depth + X509_STORE_CTX_set_error + X509_STORE_get0_objects + X509_STORE_set_verify_cb + X509_get0_signature + X509_getm_notAfter + X509_getm_notBefore + X509_subject_name_cmp + _SSL_session_reused + _SSL_set_options Method "KSSL::setSession" has been renamed to "KSSL::takeSession" and its functionality has changed: the session is now transferred from the argument object to the invoked object. Since it is only used internally in TDE and the functionality is different, the method with the previous name has not been preserved. Signed-off-by: Slávek Banko <slavek.banko@axis.cz> Signed-off-by: Michele Calgaro <michele.calgaro@yahoo.it>
8 years ago
d->kssl->takeSession(s);
delete s;
}
}
certificatePrompt();
int rc = d->kssl->connect(m_iSock);
if (rc < 0) {
delete d->kssl;
return -2;
}
setMetaData("ssl_session_id", d->kssl->session()->toString());
d->usingTLS = true;
setMetaData("ssl_in_use", "TRUE");
if (!d->kssl->reusingSession()) {
rc = verifyCertificate();
if (rc != 1) {
setMetaData("ssl_in_use", "FALSE");
d->usingTLS = false;
delete d->kssl;
return -3;
}
}
d->savedMetaData = mOutgoingMetaData;
return (d->usingTLS ? 1 : 0);
}
void TCPSlaveBase::stopTLS()
{
if (d->usingTLS) {
delete d->kssl;
d->usingTLS = false;
setMetaData("ssl_in_use", "FALSE");
}
}
void TCPSlaveBase::setSSLMetaData() {
if (!(d->usingTLS || d->useSSLTunneling || m_bIsSSL))
return;
mOutgoingMetaData = d->savedMetaData;
}
bool TCPSlaveBase::canUseTLS()
{
if (m_bIsSSL || d->needSSLHandShake || !KSSL::doesSSLWork())
return false;
KSSLSettings kss;
return kss.tlsv1();
}
void TCPSlaveBase::certificatePrompt()
{
TQString certname; // the cert to use this session
bool send = false, prompt = false, save = false, forcePrompt = false;
KSSLCertificateHome::KSSLAuthAction aa;
setMetaData("ssl_using_client_cert", "FALSE"); // we change this if needed
if (metaData("ssl_no_client_cert") == "TRUE") return;
forcePrompt = (metaData("ssl_force_cert_prompt") == "TRUE");
// Delete the old cert since we're certainly done with it now
if (d->pkcs) {
delete d->pkcs;
d->pkcs = NULL;
}
if (!d->kssl) return;
// Look for a general certificate
if (!forcePrompt) {
certname = KSSLCertificateHome::getDefaultCertificateName(&aa);
switch(aa) {
case KSSLCertificateHome::AuthSend:
send = true; prompt = false;
break;
case KSSLCertificateHome::AuthDont:
send = false; prompt = false;
certname = TQString::null;
break;
case KSSLCertificateHome::AuthPrompt:
send = false; prompt = true;
break;
default:
break;
}
}
TQString ourHost;
if (!d->realHost.isEmpty()) {
ourHost = d->realHost;
} else {
ourHost = d->host;
}
// Look for a certificate on a per-host basis as an override
TQString tmpcn = KSSLCertificateHome::getDefaultCertificateName(ourHost, &aa);
if (aa != KSSLCertificateHome::AuthNone) { // we must override
switch (aa) {
case KSSLCertificateHome::AuthSend:
send = true;
prompt = false;
certname = tmpcn;
break;
case KSSLCertificateHome::AuthDont:
send = false;
prompt = false;
certname = TQString::null;
break;
case KSSLCertificateHome::AuthPrompt:
send = false;
prompt = true;
certname = tmpcn;
break;
default:
break;
}
}
// Finally, we allow the application to override anything.
if (hasMetaData("ssl_demand_certificate")) {
certname = metaData("ssl_demand_certificate");
if (!certname.isEmpty()) {
forcePrompt = false;
prompt = false;
send = true;
}
}
if (certname.isEmpty() && !prompt && !forcePrompt) return;
// Ok, we're supposed to prompt the user....
if (prompt || forcePrompt) {
TQStringList certs = KSSLCertificateHome::getCertificateList();
for (TQStringList::Iterator it = certs.begin(); it != certs.end(); ++it) {
KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(*it);
if (pkcs && (!pkcs->getCertificate() ||
!pkcs->getCertificate()->x509V3Extensions().certTypeSSLClient())) {
certs.remove(*it);
}
delete pkcs;
}
if (certs.isEmpty()) return; // we had nothing else, and prompt failed
if (!d->dcc) {
d->dcc = new DCOPClient;
d->dcc->attach();
if (!d->dcc->isApplicationRegistered("tdeio_uiserver")) {
TDEApplication::startServiceByDesktopPath("tdeio_uiserver.desktop",
TQStringList() );
}
}
TQByteArray data, retval;
TQCString rettype;
TQDataStream arg(data, IO_WriteOnly);
arg << ourHost;
arg << certs;
arg << metaData("window-id").toInt();
bool rc = d->dcc->call("tdeio_uiserver", "UIServer",
"showSSLCertDialog(TQString, TQStringList,int)",
data, rettype, retval);
if (rc && rettype == "KSSLCertDlgRet") {
TQDataStream retStream(retval, IO_ReadOnly);
KSSLCertDlgRet drc;
retStream >> drc;
if (drc.ok) {
send = drc.send;
save = drc.save;
certname = drc.choice;
}
}
}
// The user may have said to not send the certificate,
// but to save the choice
if (!send) {
if (save) {
KSSLCertificateHome::setDefaultCertificate(certname, ourHost,
false, false);
}
return;
}
// We're almost committed. If we can read the cert, we'll send it now.
KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(certname);
if (!pkcs && KSSLCertificateHome::hasCertificateByName(certname)) { // We need the password
TDEIO::AuthInfo ai;
bool first = true;
do {
ai.prompt = i18n("Enter the certificate password:");
ai.caption = i18n("SSL Certificate Password");
ai.url.setProtocol("kssl");
ai.url.setHost(certname);
ai.username = certname;
ai.keepPassword = true;
bool showprompt;
if (first)
showprompt = !checkCachedAuthentication(ai);
else
showprompt = true;
if (showprompt) {
if (!openPassDlg(ai, first ? TQString::null :
i18n("Unable to open the certificate. Try a new password?")))
break;
}
first = false;
pkcs = KSSLCertificateHome::getCertificateByName(certname, ai.password);
} while (!pkcs);
}
// If we could open the certificate, let's send it
if (pkcs) {
if (!d->kssl->setClientCertificate(pkcs)) {
messageBox(Information, i18n("The procedure to set the "
"client certificate for the session "
"failed."), i18n("SSL"));
delete pkcs; // we don't need this anymore
pkcs = 0L;
} else {
kdDebug(7029) << "Client SSL certificate is being used." << endl;
setMetaData("ssl_using_client_cert", "TRUE");
if (save) {
KSSLCertificateHome::setDefaultCertificate(certname, ourHost,
true, false);
}
}
d->pkcs = pkcs;
}
}
bool TCPSlaveBase::usingTLS() const
{
return d->usingTLS;
}
// ### remove this for KDE4 (misses const):
bool TCPSlaveBase::usingTLS()
{
return d->usingTLS;
}
// Returns 0 for failed verification, -1 for rejected cert and 1 for ok
int TCPSlaveBase::verifyCertificate()
{
int rc = 0;
bool permacache = false;
bool isChild = false;
bool _IPmatchesCN = false;
int result;
bool doAddHost = false;
TQString ourHost;
if (!d->realHost.isEmpty())
ourHost = d->realHost;
else ourHost = d->host;
TQString theurl = TQString(m_sServiceName)+"://"+ourHost+":"+TQString::number(m_iPort);
if (!hasMetaData("ssl_militant") || metaData("ssl_militant") == "FALSE")
d->militantSSL = false;
else if (metaData("ssl_militant") == "TRUE")
d->militantSSL = true;
if (!d->cc) d->cc = new KSSLCertificateCache;
KSSLCertificate& pc = d->kssl->peerInfo().getPeerCertificate();
KSSLCertificate::KSSLValidationList ksvl = pc.validateVerbose(KSSLCertificate::SSLServer);
_IPmatchesCN = d->kssl->peerInfo().certMatchesAddress();
if (!_IPmatchesCN) {
#ifndef TQ_WS_WIN //temporary
KNetwork::KResolverResults res = KNetwork::KResolver::resolve(d->kssl->peerInfo().peerHost(), "80", KNetwork::KResolver::CanonName);
if (!res.isEmpty()) {
TQString old = d->kssl->peerInfo().peerHost();
d->kssl->peerInfo().setPeerHost(res[0].canonicalName());
_IPmatchesCN = d->kssl->peerInfo().certMatchesAddress();
if (!_IPmatchesCN) {
d->kssl->peerInfo().setPeerHost(old);
}
}
#endif
if (!_IPmatchesCN && !d->militantSSL) { // force this if the user wants it
if (d->cc->getHostList(pc).contains(ourHost)) {
_IPmatchesCN = true;
}
}
}
if (!_IPmatchesCN) {
ksvl << KSSLCertificate::InvalidHost;
}
KSSLCertificate::KSSLValidation ksv = KSSLCertificate::Ok;
if (!ksvl.isEmpty())
ksv = ksvl.first();
/* Setting the various bits of meta-info that will be needed. */
setMetaData("ssl_cipher", d->kssl->connectionInfo().getCipher());
setMetaData("ssl_cipher_desc",
d->kssl->connectionInfo().getCipherDescription());
setMetaData("ssl_cipher_version",
d->kssl->connectionInfo().getCipherVersion());
setMetaData("ssl_cipher_used_bits",
TQString::number(d->kssl->connectionInfo().getCipherUsedBits()));
setMetaData("ssl_cipher_bits",
TQString::number(d->kssl->connectionInfo().getCipherBits()));
setMetaData("ssl_peer_ip", d->ip);
if (!d->realHost.isEmpty()) {
setMetaData("ssl_proxied", "true");
}
TQString errorStr;
for(KSSLCertificate::KSSLValidationList::ConstIterator it = ksvl.begin();
it != ksvl.end(); ++it)
{
errorStr += TQString::number(*it)+":";
}
setMetaData("ssl_cert_errors", errorStr);
setMetaData("ssl_peer_certificate", pc.toString());
if (pc.chain().isValid() && pc.chain().depth() > 1) {
TQString theChain;
TQPtrList<KSSLCertificate> chain = pc.chain().getChain();
chain.setAutoDelete(true);
for (KSSLCertificate *c = chain.first(); c; c = chain.next()) {
theChain += c->toString();
theChain += "\n";
}
setMetaData("ssl_peer_chain", theChain);
} else setMetaData("ssl_peer_chain", "");
setMetaData("ssl_cert_state", TQString::number(ksv));
if (ksv == KSSLCertificate::Ok) {
rc = 1;
setMetaData("ssl_action", "accept");
}
kdDebug(7029) << "SSL HTTP frame the parent? " << metaData("main_frame_request") << endl;
if (!hasMetaData("main_frame_request") || metaData("main_frame_request") == "TRUE") {
// Since we're the parent, we need to teach the child.
setMetaData("ssl_parent_ip", d->ip);
setMetaData("ssl_parent_cert", pc.toString());
// - Read from cache and see if there is a policy for this
KSSLCertificateCache::KSSLCertificatePolicy cp =
d->cc->getPolicyByCertificate(pc);
// - validation code
if (ksv != KSSLCertificate::Ok) {
if (d->militantSSL) {
return -1;
}
if (cp == KSSLCertificateCache::Unknown ||
cp == KSSLCertificateCache::Ambiguous) {
cp = KSSLCertificateCache::Prompt;
} else {
// A policy was already set so let's honor that.
permacache = d->cc->isPermanent(pc);
}
/*
if (!_IPmatchesCN && cp == KSSLCertificateCache::Accept) {
cp = KSSLCertificateCache::Prompt;
// ksv = KSSLCertificate::Ok;
}
*/
// Precondition: cp is one of Reject, Accept or Prompt
switch (cp) {
case KSSLCertificateCache::Accept:
rc = 1;
setMetaData("ssl_action", "accept");
break;
case KSSLCertificateCache::Reject:
rc = -1;
setMetaData("ssl_action", "reject");
break;
case KSSLCertificateCache::Prompt:
{
do {
if (ksv == KSSLCertificate::InvalidHost) {
TQString msg = i18n("The IP address of the host %1 "
"does not match the one the "
"certificate was issued to.");
result = messageBox( WarningYesNoCancel,
msg.arg(ourHost),
i18n("Server Authentication"),
i18n("&Details"),
i18n("Co&ntinue") );
} else {
TQString msg = i18n("The server certificate failed the "
"authenticity test (%1).");
result = messageBox( WarningYesNoCancel,
msg.arg(ourHost),
i18n("Server Authentication"),
i18n("&Details"),
i18n("Co&ntinue") );
}
if (result == KMessageBox::Yes) {
if (!d->dcc) {
d->dcc = new DCOPClient;
d->dcc->attach();
if (!d->dcc->isApplicationRegistered("tdeio_uiserver")) {
TDEApplication::startServiceByDesktopPath("tdeio_uiserver.desktop",
TQStringList() );
}
}
TQByteArray data, ignore;
TQCString ignoretype;
TQDataStream arg(data, IO_WriteOnly);
arg << theurl << mOutgoingMetaData;
arg << metaData("window-id").toInt();
d->dcc->call("tdeio_uiserver", "UIServer",
"showSSLInfoDialog(TQString,TDEIO::MetaData,int)",
data, ignoretype, ignore);
}
} while (result == KMessageBox::Yes);
if (result == KMessageBox::No) {
setMetaData("ssl_action", "accept");
rc = 1;
cp = KSSLCertificateCache::Accept;
doAddHost = true;
result = messageBox( WarningYesNo,
i18n("Would you like to accept this "
"certificate forever without "
"being prompted?"),
i18n("Server Authentication"),
i18n("&Forever"),
i18n("&Current Sessions Only"));
if (result == KMessageBox::Yes)
permacache = true;
else
permacache = false;
} else {
setMetaData("ssl_action", "reject");
rc = -1;
cp = KSSLCertificateCache::Prompt;
}
break;
}
default:
kdDebug(7029) << "TCPSlaveBase/SSL error in cert code."
<< "Please report this to kfm-devel@kde.org."
<< endl;
break;
}
}
// - cache the results
d->cc->addCertificate(pc, cp, permacache);
if (doAddHost) d->cc->addHost(pc, ourHost);
} else { // Child frame
// - Read from cache and see if there is a policy for this
KSSLCertificateCache::KSSLCertificatePolicy cp =
d->cc->getPolicyByCertificate(pc);
isChild = true;
// Check the cert and IP to make sure they're the same
// as the parent frame
bool certAndIPTheSame = (d->ip == metaData("ssl_parent_ip") &&
pc.toString() == metaData("ssl_parent_cert"));
if (ksv == KSSLCertificate::Ok) {
if (certAndIPTheSame) { // success
rc = 1;
setMetaData("ssl_action", "accept");
} else {
/*
if (d->militantSSL) {
return -1;
}
result = messageBox(WarningYesNo,
i18n("The certificate is valid but does not appear to have been assigned to this server. Do you wish to continue loading?"),
i18n("Server Authentication"));
if (result == KMessageBox::Yes) { // success
rc = 1;
setMetaData("ssl_action", "accept");
} else { // fail
rc = -1;
setMetaData("ssl_action", "reject");
}
*/
setMetaData("ssl_action", "accept");
rc = 1; // Let's accept this now. It's bad, but at least the user
// will see potential attacks in KDE3 with the pseudo-lock
// icon on the toolbar, and can investigate with the RMB
}
} else {
if (d->militantSSL) {
return -1;
}
if (cp == KSSLCertificateCache::Accept) {
if (certAndIPTheSame) { // success
rc = 1;
setMetaData("ssl_action", "accept");
} else { // fail
result = messageBox(WarningYesNo,
i18n("You have indicated that you wish to accept this certificate, but it is not issued to the server who is presenting it. Do you wish to continue loading?"),
i18n("Server Authentication"));
if (result == KMessageBox::Yes) {
rc = 1;
setMetaData("ssl_action", "accept");
d->cc->addHost(pc, ourHost);
} else {
rc = -1;
setMetaData("ssl_action", "reject");
}
}
} else if (cp == KSSLCertificateCache::Reject) { // fail
messageBox(Information, i18n("SSL certificate is being rejected as requested. You can disable this in the Trinity Control Center."),
i18n("Server Authentication"));
rc = -1;
setMetaData("ssl_action", "reject");
} else {
do {
TQString msg = i18n("The server certificate failed the "
"authenticity test (%1).");
result = messageBox(WarningYesNoCancel,
msg.arg(ourHost),
i18n("Server Authentication"),
i18n("&Details"),
i18n("Co&nnect"));
if (result == KMessageBox::Yes) {
if (!d->dcc) {
d->dcc = new DCOPClient;
d->dcc->attach();
if (!d->dcc->isApplicationRegistered("tdeio_uiserver")) {
TDEApplication::startServiceByDesktopPath("tdeio_uiserver.desktop",
TQStringList() );
}
}
TQByteArray data, ignore;
TQCString ignoretype;
TQDataStream arg(data, IO_WriteOnly);
arg << theurl << mOutgoingMetaData;
arg << metaData("window-id").toInt();
d->dcc->call("tdeio_uiserver", "UIServer",
"showSSLInfoDialog(TQString,TDEIO::MetaData,int)",
data, ignoretype, ignore);
}
} while (result == KMessageBox::Yes);
if (result == KMessageBox::No) {
setMetaData("ssl_action", "accept");
rc = 1;
cp = KSSLCertificateCache::Accept;
result = messageBox(WarningYesNo,
i18n("Would you like to accept this "
"certificate forever without "
"being prompted?"),
i18n("Server Authentication"),
i18n("&Forever"),
i18n("&Current Sessions Only"));
permacache = (result == KMessageBox::Yes);
d->cc->addCertificate(pc, cp, permacache);
d->cc->addHost(pc, ourHost);
} else {
setMetaData("ssl_action", "reject");
rc = -1;
cp = KSSLCertificateCache::Prompt;
d->cc->addCertificate(pc, cp, permacache);
}
}
}
}
if (rc == -1) {
return rc;
}
if (metaData("ssl_activate_warnings") == "TRUE") {
// - entering SSL
if (!isChild && metaData("ssl_was_in_use") == "FALSE" &&
d->kssl->settings()->warnOnEnter()) {
int result;
do {
result = messageBox( i18n("You are about to "
"enter secure mode. "
"All transmissions "
"will be encrypted "
"unless otherwise "
"noted.\nThis means "
"that no third party "
"will be able to "
"easily observe your "
"data in transit."),
WarningYesNo,
i18n("Security Information"),
i18n("Display SSL "
"&Information"),
i18n("C&onnect"),
"WarnOnEnterSSLMode" );
// Move this setting into KSSL instead
TDEConfig *config = new TDEConfig("tdeioslaverc");
config->setGroup("Notification Messages");
bool dialogBoxStatus = false;
if( config->hasKey("WarnOnEnterSSLMode") ) {
dialogBoxStatus = true;
}
bool keyStatus = config->readBoolEntry("WarnOnEnterSSLMode", true);
dialogBoxStatus = dialogBoxStatus && keyStatus;
if (!keyStatus) {
config->deleteEntry("WarnOnEnterSSLMode");
config->sync();
d->kssl->settings()->setWarnOnEnter(false);
d->kssl->settings()->save();
}
delete config;
if ( result == KMessageBox::Yes )
{
if (!d->dcc) {
d->dcc = new DCOPClient;
d->dcc->attach();
if (!d->dcc->isApplicationRegistered("tdeio_uiserver")) {
TDEApplication::startServiceByDesktopPath("tdeio_uiserver.desktop",
TQStringList() );
}
}
TQByteArray data, ignore;
TQCString ignoretype;
TQDataStream arg(data, IO_WriteOnly);
arg << theurl << mOutgoingMetaData;
arg << metaData("window-id").toInt();
d->dcc->call("tdeio_uiserver", "UIServer",
"showSSLInfoDialog(TQString,TDEIO::MetaData,int)",
data, ignoretype, ignore);
}
//Laurent: If we disable message box we can't click on KMessageBox::No
if(dialogBoxStatus) {
break;
}
} while (result != KMessageBox::No);
}
} // if ssl_activate_warnings
kdDebug(7029) << "SSL connection information follows:" << endl
<< "+-----------------------------------------------" << endl
<< "| Cipher: " << d->kssl->connectionInfo().getCipher() << endl
<< "| Description: " << d->kssl->connectionInfo().getCipherDescription() << endl
<< "| Version: " << d->kssl->connectionInfo().getCipherVersion() << endl
<< "| Strength: " << d->kssl->connectionInfo().getCipherUsedBits()
<< " of " << d->kssl->connectionInfo().getCipherBits()
<< " bits used." << endl
<< "| PEER:" << endl
<< "| Subject: " << d->kssl->peerInfo().getPeerCertificate().getSubject() << endl
<< "| Issuer: " << d->kssl->peerInfo().getPeerCertificate().getIssuer() << endl
<< "| Validation: " << (int)ksv << endl
<< "| Certificate matches IP: " << _IPmatchesCN << endl
<< "+-----------------------------------------------"
<< endl;
// sendMetaData(); Do not call this function!!
return rc;
}
bool TCPSlaveBase::isConnectionValid()
{
if ( m_iSock == -1 )
return false;
fd_set rdfs;
FD_ZERO(&rdfs);
FD_SET(m_iSock , &rdfs);
struct timeval tv;
tv.tv_usec = 0;
tv.tv_sec = 0;
int retval;
#ifdef Q_OS_UNIX
do {
retval = KSocks::self()->select(m_iSock+1, &rdfs, NULL, NULL, &tv);
if (wasKilled())
return false; // Beam us out of here
} while ((retval == -1) && (errno == EAGAIN));
#else
retval = -1;
#endif
// retval == -1 ==> Error
// retval == 0 ==> Connection Idle
// retval >= 1 ==> Connection Active
//kdDebug(7029) << "TCPSlaveBase::isConnectionValid: select returned: "
// << retval << endl;
if (retval == -1)
return false;
if (retval == 0)
return true;
// Connection is active, check if it has closed.
char buffer[100];
#ifdef Q_OS_UNIX
do {
retval = KSocks::self()->recv(m_iSock, buffer, 80, MSG_PEEK);
} while ((retval == -1) && (errno == EAGAIN));
#else
retval = -1;
#endif
//kdDebug(7029) << "TCPSlaveBase::isConnectionValid: recv returned: "
// << retval << endl;
if (retval <= 0)
return false; // Error or connection closed.
return true; // Connection still valid.
}
bool TCPSlaveBase::waitForResponse( int t )
{
fd_set rd;
struct timeval timeout;
if ( (m_bIsSSL || d->usingTLS) && !d->useSSLTunneling && d->kssl )
if (d->kssl->pending() > 0)
return true;
FD_ZERO(&rd);
FD_SET(m_iSock, &rd);
timeout.tv_usec = 0;
timeout.tv_sec = t;
time_t startTime;
int rc;
int n = t;
reSelect:
startTime = time(NULL);
#ifdef Q_OS_UNIX
rc = KSocks::self()->select(m_iSock+1, &rd, NULL, NULL, &timeout);
#else
rc = -1;
#endif
if (wasKilled())
return false; // We're dead.
if (rc == -1)
return false;
if (FD_ISSET(m_iSock, &rd))
return true;
// Well it returned but it wasn't set. Let's see if it
// returned too early (perhaps from an errant signal) and
// start over with the remaining time
int timeDone = time(NULL) - startTime;
if (timeDone < n)
{
n -= timeDone;
timeout.tv_sec = n;
goto reSelect;
}
return false; // Timed out!
}
int TCPSlaveBase::connectResult()
{
return d->status;
}
void TCPSlaveBase::setBlockConnection( bool b )
{
d->block = b;
}
void TCPSlaveBase::setConnectTimeout( int t )
{
d->timeout = t;
}
bool TCPSlaveBase::isSSLTunnelEnabled()
{
return d->useSSLTunneling;
}
void TCPSlaveBase::setEnableSSLTunnel( bool enable )
{
d->useSSLTunneling = enable;
}
void TCPSlaveBase::setRealHost( const TQString& realHost )
{
d->realHost = realHost;
}
bool TCPSlaveBase::doSSLHandShake( bool sendError )
{
kdDebug(7029) << "TCPSlaveBase::doSSLHandShake: " << endl;
TQString msgHost = d->host;
d->kssl->reInitialize();
if (hasMetaData("ssl_session_id")) {
KSSLSession *s = KSSLSession::fromString(metaData("ssl_session_id"));
if (s) {
Added support for OpenSSL 1.1 Some KOpenSSLProxy methods have been renamed to be consistent with OpenSSL 1.1 API names and to prevent hidden API changes. To ensure API / ABI compatibility, the original methods are still included but have been marked as deprecated. + SSLv23_client_method => TLS_client_method + X509_STORE_CTX_set_chain => X509_STORE_CTX_set0_untrusted + sk_dup => OPENSSL_sk_dup + sk_free => OPENSSL_sk_free + sk_new => OPENSSL_sk_new + sk_num => OPENSSL_sk_num + sk_pop => OPENSSL_sk_pop + sk_push => OPENSSL_sk_push + sk_value => OPENSSL_sk_value Additional methods have been added to KOpenSSLProxy to support the new OpenSSL 1.1 API functions that provide access to the (now) opaque SSL structures. Compatibility with OpenSSL < 1.1 is handled internally in KOpenSSLProxy. + BIO_get_data + DSA_get0_key + DSA_get0_pqg + EVP_PKEY_base_id + EVP_PKEY_get0_DSA + EVP_PKEY_get0_RSA + RSA_get0_key + X509_CRL_get0_lastUpdate + X509_CRL_get0_nextUpdate + X509_OBJECT_get0_X509 + X509_OBJECT_get_type + X509_STORE_CTX_get_current_cert + X509_STORE_CTX_get_error + X509_STORE_CTX_get_error_depth + X509_STORE_CTX_set_error + X509_STORE_get0_objects + X509_STORE_set_verify_cb + X509_get0_signature + X509_getm_notAfter + X509_getm_notBefore + X509_subject_name_cmp + _SSL_session_reused + _SSL_set_options Method "KSSL::setSession" has been renamed to "KSSL::takeSession" and its functionality has changed: the session is now transferred from the argument object to the invoked object. Since it is only used internally in TDE and the functionality is different, the method with the previous name has not been preserved. Signed-off-by: Slávek Banko <slavek.banko@axis.cz> Signed-off-by: Michele Calgaro <michele.calgaro@yahoo.it>
8 years ago
d->kssl->takeSession(s);
delete s;
Added support for OpenSSL 1.1 Some KOpenSSLProxy methods have been renamed to be consistent with OpenSSL 1.1 API names and to prevent hidden API changes. To ensure API / ABI compatibility, the original methods are still included but have been marked as deprecated. + SSLv23_client_method => TLS_client_method + X509_STORE_CTX_set_chain => X509_STORE_CTX_set0_untrusted + sk_dup => OPENSSL_sk_dup + sk_free => OPENSSL_sk_free + sk_new => OPENSSL_sk_new + sk_num => OPENSSL_sk_num + sk_pop => OPENSSL_sk_pop + sk_push => OPENSSL_sk_push + sk_value => OPENSSL_sk_value Additional methods have been added to KOpenSSLProxy to support the new OpenSSL 1.1 API functions that provide access to the (now) opaque SSL structures. Compatibility with OpenSSL < 1.1 is handled internally in KOpenSSLProxy. + BIO_get_data + DSA_get0_key + DSA_get0_pqg + EVP_PKEY_base_id + EVP_PKEY_get0_DSA + EVP_PKEY_get0_RSA + RSA_get0_key + X509_CRL_get0_lastUpdate + X509_CRL_get0_nextUpdate + X509_OBJECT_get0_X509 + X509_OBJECT_get_type + X509_STORE_CTX_get_current_cert + X509_STORE_CTX_get_error + X509_STORE_CTX_get_error_depth + X509_STORE_CTX_set_error + X509_STORE_get0_objects + X509_STORE_set_verify_cb + X509_get0_signature + X509_getm_notAfter + X509_getm_notBefore + X509_subject_name_cmp + _SSL_session_reused + _SSL_set_options Method "KSSL::setSession" has been renamed to "KSSL::takeSession" and its functionality has changed: the session is now transferred from the argument object to the invoked object. Since it is only used internally in TDE and the functionality is different, the method with the previous name has not been preserved. Signed-off-by: Slávek Banko <slavek.banko@axis.cz> Signed-off-by: Michele Calgaro <michele.calgaro@yahoo.it>
8 years ago
}
}
certificatePrompt();
if ( !d->realHost.isEmpty() )
{
msgHost = d->realHost;
}
kdDebug(7029) << "Setting real hostname: " << msgHost << endl;
d->kssl->setPeerHost(msgHost);
d->status = d->kssl->connect(m_iSock);
if (d->status < 0)
{
closeDescriptor();
if ( sendError )
error( ERR_COULD_NOT_CONNECT, msgHost);
return false;
}
setMetaData("ssl_session_id", d->kssl->session()->toString());
setMetaData("ssl_in_use", "TRUE");
if (!d->kssl->reusingSession()) {
int rc = verifyCertificate();
if ( rc != 1 ) {
d->status = -1;
closeDescriptor();
if ( sendError )
error( ERR_COULD_NOT_CONNECT, msgHost);
return false;
}
}
d->needSSLHandShake = false;
d->savedMetaData = mOutgoingMetaData;
return true;
}
bool TCPSlaveBase::userAborted() const
{
return d->userAborted;
}
void TCPSlaveBase::virtual_hook( int id, void* data )
{ SlaveBase::virtual_hook( id, data ); }