summaryrefslogtreecommitdiffstats
path: root/src/gui/embedded/qtransportauth_qws.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/embedded/qtransportauth_qws.cpp')
-rw-r--r--src/gui/embedded/qtransportauth_qws.cpp1562
1 files changed, 1562 insertions, 0 deletions
diff --git a/src/gui/embedded/qtransportauth_qws.cpp b/src/gui/embedded/qtransportauth_qws.cpp
new file mode 100644
index 0000000..97ba5b8
--- /dev/null
+++ b/src/gui/embedded/qtransportauth_qws.cpp
@@ -0,0 +1,1562 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtransportauth_qws.h"
+#include "qtransportauth_qws_p.h"
+
+#ifndef QT_NO_SXE
+
+#include "../../3rdparty/md5/md5.h"
+#include "../../3rdparty/md5/md5.cpp"
+#include "qwsutils_qws.h"
+#include "qwssocket_qws.h"
+#include "qwscommand_qws_p.h"
+#include "qwindowsystem_qws.h"
+#include "qbuffer.h"
+#include "qthread.h"
+#include "qabstractsocket.h"
+#include "qlibraryinfo.h"
+#include "qfile.h"
+#include "qdebug.h"
+
+#include <syslog.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/file.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+
+#include <QtCore/qcache.h>
+
+#define BUF_SIZE 512
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \internal
+ memset for security purposes, guaranteed not to be optimized away
+ http://www.faqs.org/docs/Linux-HOWTO/Secure-Programs-HOWTO.html
+*/
+Q_GUI_EXPORT void *guaranteed_memset(void *v,int c,size_t n)
+{
+ volatile char *p = (char *)v; while (n--) *p++=c; return v;
+}
+
+/*!
+ \class QTransportAuth
+ \internal
+
+ \brief Authenticate a message transport.
+
+ For performance reasons, message authentication is tied to an individual
+ message transport instance. For example in connection oriented transports
+ the authentication cookie can be cached against the connection avoiding
+ the overhead of authentication on every message.
+
+ For each process there is one instance of the QTransportAuth object.
+ For server processes it can determine the \link secure-exe-environ.html SXE
+ Program Identity \endlink and provide access to policy data to determine if
+ the message should be forwarded for action. If not actioned, the message
+ may be treated as being from a flawed or malicious process.
+
+ Retrieve the instance with the getInstance() method. The constructor is
+ disabled and instances of QTransportAuth should never be constructed by
+ calling classes.
+
+ To make the Authentication easier to use a proxied QIODevice is provided
+ which uses an internal QBuffer.
+
+ In the server code first get a pointer to a QTransportAuth::Data object
+ using the connectTransport() method:
+
+ \snippet doc/src/snippets/code/src_gui_embedded_qtransportauth_qws.cpp 0
+
+ Here it is asserted that the transport is trusted. See the assumptions
+ listed in the \link secure-exe-environ.html SXE documentation \endlink
+
+ Then proxy in the authentication device:
+
+ \snippet doc/src/snippets/code/src_gui_embedded_qtransportauth_qws.cpp 1
+
+ In the client code it is similar. Use the connectTransport() method
+ just the same then proxy in the authentication device instead of the
+ socket in write calls:
+
+ \snippet doc/src/snippets/code/src_gui_embedded_qtransportauth_qws.cpp 2
+*/
+
+static int hmac_md5(
+ unsigned char* text, /* pointer to data stream */
+ int text_length, /* length of data stream */
+ const unsigned char* key, /* pointer to authentication key */
+ int key_length, /* length of authentication key */
+ unsigned char * digest /* caller digest to be filled in */
+ );
+
+
+
+#define KEY_CACHE_SIZE 30
+
+const char * const errorStrings[] = {
+ "pending identity verification",
+ "message too small to carry auth data",
+ "cache miss on connection oriented transport",
+ "no magic bytes on message",
+ "key not found for prog id",
+ "authorization key match failed",
+ "key out of date"
+};
+
+const char *QTransportAuth::errorString( const Data &d )
+{
+ if (( d.status & ErrMask ) == Success )
+ return "success";
+ int e = d.status & ErrMask;
+ if ( e > OutOfDate )
+ return "unknown";
+ return errorStrings[e];
+}
+
+SxeRegistryLocker::SxeRegistryLocker( QObject *reg )
+ : m_success( false )
+ , m_reg( 0 )
+{
+ if ( reg )
+ if ( !QMetaObject::invokeMethod( reg, "lockManifest", Q_RETURN_ARG(bool, m_success) ))
+ m_success = false;
+ m_reg = reg;
+}
+
+SxeRegistryLocker::~SxeRegistryLocker()
+{
+ if ( m_success )
+ QMetaObject::invokeMethod( m_reg, "unlockManifest" );
+}
+
+
+QTransportAuthPrivate::QTransportAuthPrivate()
+ : keyInitialised(false)
+ , m_packageRegistry( 0 )
+{
+}
+
+QTransportAuthPrivate::~QTransportAuthPrivate()
+{
+}
+
+/*!
+ \internal
+ Construct a new QTransportAuth
+*/
+QTransportAuth::QTransportAuth() : QObject(*new QTransportAuthPrivate)
+{
+ // qDebug( "creating transport auth" );
+}
+
+/*!
+ \internal
+ Destructor
+*/
+QTransportAuth::~QTransportAuth()
+{
+ // qDebug( "deleting transport auth" );
+}
+
+/*!
+ Set the process key for this currently running Qt Extended process to
+ the \a authdata. \a authdata should be sizeof(struct AuthCookie)
+ in length and contain the key and program id. Use this method
+ when setting or changing the SXE identity of the current program.
+*/
+void QTransportAuth::setProcessKey( const char *authdata )
+{
+ Q_D(QTransportAuth);
+ ::memcpy(&d->authKey, authdata, sizeof(struct AuthCookie));
+ QFile proc_key( QLatin1String("/proc/self/lids_key") );
+ // where proc key exists use that instead
+ if ( proc_key.open( QIODevice::ReadOnly ))
+ {
+ qint64 kb = proc_key.read( (char*)&d->authKey.key, QSXE_KEY_LEN );
+#ifdef QTRANSPORTAUTH_DEBUG
+ qDebug( "Using %li bytes of /proc/%i/lids_key\n", (long int)kb, getpid() );
+#else
+ Q_UNUSED( kb );
+#endif
+ }
+ d->keyInitialised = true;
+}
+
+
+/*!
+ Apply \a key as the process key for the currently running application.
+
+ \a prog is current ignored
+
+ Deprecated function
+*/
+void QTransportAuth::setProcessKey( const char *key, const char *prog )
+{
+ Q_UNUSED(prog);
+ setProcessKey( key );
+#ifdef QTRANSPORTAUTH_DEBUG
+ char displaybuf[QSXE_KEY_LEN*2+1];
+ hexstring( displaybuf, (const unsigned char *)key, QSXE_KEY_LEN );
+ qDebug() << "key" << displaybuf << "set";
+#endif
+}
+
+/*!
+ Register \a pr as a policy handler object. The object pointed to
+ by \a pr should have a slot as follows
+ \snippet doc/src/snippets/code/src_gui_embedded_qtransportauth_qws.cpp 3
+ All requests received by this server will then generate a call to
+ this slot, and may be processed for policy compliance.
+*/
+void QTransportAuth::registerPolicyReceiver( QObject *pr )
+{
+ // not every policy receiver needs setup - no error if this fails
+ QMetaObject::invokeMethod( pr, "setupPolicyCheck" );
+
+ connect( this, SIGNAL(policyCheck(QTransportAuth::Data&,QString)),
+ pr, SLOT(policyCheck(QTransportAuth::Data&,QString)), Qt::DirectConnection );
+}
+
+/*!
+ Unregister the \a pr from being a policy handler. No more policyCheck signals
+ are received by this object.
+*/
+void QTransportAuth::unregisterPolicyReceiver( QObject *pr )
+{
+ disconnect( pr );
+ // not every policy receiver needs tear down - no error if this fails
+ QMetaObject::invokeMethod( pr, "teardownPolicyCheck" );
+}
+
+/*!
+ Record a new transport connection with \a properties and \a descriptor.
+
+ The calling code is responsible for destroying the returned data when the
+ tranport connection is closed.
+*/
+QTransportAuth::Data *QTransportAuth::connectTransport( unsigned char properties, int descriptor )
+{
+ Data *data = new Data(properties, descriptor);
+ data->status = Pending;
+ return data;
+}
+
+/*!
+ Is the transport trusted. This is true iff data written into the
+ transport medium cannot be intercepted or modified by another process.
+ This is for example true for Unix Domain Sockets, but not for shared
+ memory or UDP sockets.
+
+ There is of course an underlying assumption that the kernel implementing
+ the transport is sound, ie it cannot be compromised by writing to
+ /dev/kmem or loading untrusted modules
+*/
+inline bool QTransportAuth::Data::trusted() const
+{
+ return (bool)(properties & Trusted);
+}
+
+/*!
+ Assert that the transport is trusted.
+
+ For example with respect to shared memory, if it is ensured that no untrusted
+ root processes are running, and that unix permissions have been set such that
+ any untrusted non-root processes do not have access rights, then a shared
+ memory transport could be asserted to be trusted.
+
+ \sa trusted()
+*/
+inline void QTransportAuth::Data::setTrusted( bool t )
+{
+ properties = t ? properties | Trusted : properties & ~Trusted;
+}
+
+/*!
+ Is the transport connection oriented. This is true iff once a connection
+ has been accepted, and state established, then further messages over the
+ transport are guaranteed to have come from the original connecting entity.
+ This is for example true for Unix Domain Sockets, but not
+ for shared memory or UDP sockets.
+
+ By extension if the transport is not trusted() then it should not be
+ assumed to be connection oriented, since spoofed connection information
+ could be created. For example if we assume the TCP/IP transport is
+ trusted, it can be treated as connection oriented; but this is only the
+ case if intervening routers are trusted.
+
+ Connection oriented transports have authorization cached against the
+ connection, and thus authorization is only done at connect time.
+*/
+inline bool QTransportAuth::Data::connection() const
+{
+ return (bool)(properties & Connection);
+}
+
+/*!
+ Assert that the transport is connection oriented.
+
+ \sa connection()
+*/
+inline void QTransportAuth::Data::setConnection( bool t )
+{
+ properties = t ? properties | Connection : properties & ~Connection;
+}
+
+/*!
+ Return a pointer to the instance of this process's QTransportAuth object
+*/
+QTransportAuth *QTransportAuth::getInstance()
+{
+ static QTransportAuth theInstance;
+
+ return &theInstance;
+}
+
+/*!
+ Set the full path to the key file
+
+ Since this is normally relative to Qtopia::qpeDir() this needs to be
+ set within the Qt Extended framework.
+
+ The keyfile should be protected by file permissions or by MAC rules
+ such that it can only be read/written by the "qpe" server process
+*/
+void QTransportAuth::setKeyFilePath( const QString &path )
+{
+ Q_D(QTransportAuth);
+ d->m_keyFilePath = path;
+}
+
+QString QTransportAuth::keyFilePath() const
+{
+ Q_D(const QTransportAuth);
+ return d->m_keyFilePath;
+}
+
+void QTransportAuth::setLogFilePath( const QString &path )
+{
+ Q_D(QTransportAuth);
+ d->m_logFilePath = path;
+}
+
+QString QTransportAuth::logFilePath() const
+{
+ Q_D(const QTransportAuth);
+ return d->m_logFilePath;
+}
+
+void QTransportAuth::setPackageRegistry( QObject *registry )
+{
+ Q_D(QTransportAuth);
+ d->m_packageRegistry = registry;
+}
+
+bool QTransportAuth::isDiscoveryMode() const
+{
+#if defined(SXE_DISCOVERY)
+ static bool checked = false;
+ static bool yesItIs = false;
+
+ if ( checked ) return yesItIs;
+
+ yesItIs = ( getenv( "SXE_DISCOVERY_MODE" ) != 0 );
+ if ( yesItIs )
+ {
+ qWarning("SXE Discovery mode on, ALLOWING ALL requests and logging to %s",
+ qPrintable(logFilePath()));
+ QFile::remove( logFilePath() );
+ }
+ checked = true;
+ return yesItIs;
+#else
+ return false;
+#endif
+}
+
+/*!
+ \internal
+ Return the authorizer device mapped to this client. Note that this
+ could probably all be void* instead of QWSClient* for generality.
+ Until the need for that rears its head its QWSClient* to save the casts.
+
+ #### OK the need has arrived, but the public API is frozen.
+*/
+QIODevice *QTransportAuth::passThroughByClient( QWSClient *client ) const
+{
+ Q_D(const QTransportAuth);
+
+ if ( client == 0 ) return 0;
+ if ( d->buffersByClient.contains( client ))
+ {
+ return d->buffersByClient[client];
+ }
+ // qWarning( "buffer not found for client %p", client );
+ return 0;
+}
+
+/*!
+ \internal
+ Return a QIODevice pointer (to an internal QBuffer) which can be used
+ to receive data after authorisation on transport \a d.
+
+ The return QIODevice will act as a pass-through.
+
+ The data will be consumed from \a iod and forwarded on to the returned
+ QIODevice which can be connected to readyRead() signal handlers in
+ place of the original QIODevice \a iod.
+
+ This will be called in the server process to handle incoming
+ authenticated requests.
+
+ The returned QIODevice will take ownership of \a data which will be deleted
+ when the QIODevice is delected.
+
+ \sa setTargetDevice()
+*/
+QAuthDevice *QTransportAuth::recvBuf( QTransportAuth::Data *data, QIODevice *iod )
+{
+ return new QAuthDevice( iod, data, QAuthDevice::Receive );
+}
+
+/*!
+ Return a QIODevice pointer (to an internal QBuffer) which can be used
+ to write data onto, for authorisation on transport \a d.
+
+ The return QIODevice will act as a pass-through.
+
+ The data written to the return QIODevice will be forwarded on to the
+ returned QIODevice. In the case of a QTcpSocket, this will cause it
+ to send out the data with the authentication information on it.
+
+ This will be called in the client process to generate outgoing
+ authenticated requests.
+
+ The returned QIODevice will take ownership of \a data which will be deleted
+ when the QIODevice is delected.
+
+ \sa setTargetDevice()
+*/
+QAuthDevice *QTransportAuth::authBuf( QTransportAuth::Data *data, QIODevice *iod )
+{
+ return new QAuthDevice( iod, data, QAuthDevice::Send );
+}
+
+const unsigned char *QTransportAuth::getClientKey( unsigned char progId )
+{
+ Q_D(QTransportAuth);
+ return d->getClientKey( progId );
+}
+
+void QTransportAuth::invalidateClientKeyCache()
+{
+ Q_D(QTransportAuth);
+ d->invalidateClientKeyCache();
+}
+
+QMutex *QTransportAuth::getKeyFileMutex()
+{
+ Q_D(QTransportAuth);
+ return &d->keyfileMutex;
+}
+
+/*
+ \internal
+ Respond to the destroyed(QObject*) signal of the QAuthDevice's
+ client object and remove it from the buffersByClient lookup hash.
+*/
+void QTransportAuth::bufferDestroyed( QObject *cli )
+{
+ Q_D(QTransportAuth);
+ if ( cli == NULL ) return;
+
+ if ( d->buffersByClient.contains( cli ))
+ {
+ d->buffersByClient.remove( cli );
+ // qDebug( "@@@@@@@ client %p removed @@@@@@@@@", cli );
+ }
+ // qDebug( " client count %d", d->buffersByClient.count() );
+}
+
+bool QTransportAuth::authorizeRequest( QTransportAuth::Data &d, const QString &request )
+{
+ bool isAuthorized = true;
+
+ if ( !request.isEmpty() && request != QLatin1String("Unknown") )
+ {
+ d.status &= QTransportAuth::ErrMask; // clear the status
+ emit policyCheck( d, request );
+ isAuthorized = (( d.status & QTransportAuth::StatusMask ) == QTransportAuth::Allow );
+ }
+#if defined(SXE_DISCOVERY)
+ if (isDiscoveryMode()) {
+#ifndef QT_NO_TEXTSTREAM
+ if (!logFilePath().isEmpty()) {
+ QFile log( logFilePath() );
+ if (!log.open(QIODevice::WriteOnly | QIODevice::Append)) {
+ qWarning("Could not write to log in discovery mode: %s",
+ qPrintable(logFilePath()));
+ } else {
+ QTextStream ts( &log );
+ ts << d.progId << '\t' << ( isAuthorized ? "Allow" : "Deny" ) << '\t' << request << endl;
+ }
+ }
+#endif
+ isAuthorized = true;
+ }
+#endif
+ if ( !isAuthorized )
+ {
+ qWarning( "%s - denied: for Program Id %u [PID %d]"
+ , qPrintable(request), d.progId, d.processId );
+
+ char linkTarget[BUF_SIZE]="";
+ char exeLink[BUF_SIZE]="";
+ char cmdlinePath[BUF_SIZE]="";
+ char cmdline[BUF_SIZE]="";
+
+ //get executable from /proc/pid/exe
+ snprintf( exeLink, BUF_SIZE, "/proc/%d/exe", d.processId );
+ if ( -1 == ::readlink( exeLink, linkTarget, BUF_SIZE - 1 ) )
+ {
+ qWarning( "SXE:- Error encountered in retrieving executable link target from /proc/%u/exe : %s",
+ d.processId, strerror(errno) );
+ snprintf( linkTarget, BUF_SIZE, "%s", linkTarget );
+ }
+
+ //get cmdline from proc/pid/cmdline
+ snprintf( cmdlinePath, BUF_SIZE, "/proc/%d/cmdline", d.processId );
+ int cmdlineFd = open( cmdlinePath, O_RDONLY );
+ if ( cmdlineFd == -1 )
+ {
+ qWarning( "SXE:- Error encountered in opening /proc/%u/cmdline: %s",
+ d.processId, strerror(errno) );
+ snprintf( cmdline, BUF_SIZE, "%s", "Unknown" );
+ }
+ else
+ {
+ if ( -1 == ::read(cmdlineFd, cmdline, BUF_SIZE - 1 ) )
+ {
+ qWarning( "SXE:- Error encountered in reading /proc/%u/cmdline : %s",
+ d.processId, strerror(errno) );
+ snprintf( cmdline, BUF_SIZE, "%s", "Unknown" );
+ }
+ close( cmdlineFd );
+ }
+
+ syslog( LOG_ERR | LOG_LOCAL6, "%s // PID:%u // ProgId:%u // Exe:%s // Request:%s // Cmdline:%s",
+ "<SXE Breach>", d.processId, d.progId, linkTarget, qPrintable(request), cmdline);
+ }
+
+ return isAuthorized;
+}
+
+inline bool __fileOpen( QFile *f )
+{
+#ifdef QTRANSPORTAUTH_DEBUG
+ if ( f->open( QIODevice::ReadOnly ))
+ {
+ qDebug( "Opened file: %s\n", qPrintable( f->fileName() ));
+ return true;
+ }
+ else
+ {
+ qWarning( "Could not open file: %s\n", qPrintable( f->fileName() ));
+ return false;
+ }
+#else
+ return ( f->open( QIODevice::ReadOnly ));
+#endif
+}
+
+/*!
+ \internal
+ Find client keys for the \a progId. If it is cached should be very
+ fast, otherwise requires a read of the secret key file
+
+ In the success case a pointer to the keys is returned. The pointer is
+ to storage allocated for the internal cache and must be used asap.
+
+ The list returned is a sequence of one or more keys which match the
+ progId. There is no separator, each 16 byte sequence represents a key.
+ The sequence is followed by two iterations of the SXE magic
+ bytes,eg 0xBA, 0xD4, 0xD4, 0xBA, 0xBA, 0xD4, 0xD4, 0xBA
+
+ NULL is returned in the following cases:
+ \list
+ \o the keyfiles could not be accessed - error condition
+ \o there was no key for the supplied program id - key auth failed
+ \endlist
+
+ Note that for the keyfiles, there is multi-thread and multi-process
+ concurrency issues: they can be read by the qpe process when
+ QTransportAuth calls getClientKey to verify a request, and they can be
+ read or written by the packagemanager when updating package data.
+
+ To protect against this, the keyfileMutex & SxeRegistryLocker is used.
+
+ The sxe_installer tool can also update inode and device numbers in
+ the manifest file, but this only occurs outside of normal operation,
+ so qpe and packagemanager are never running when this occurs.
+*/
+const unsigned char *QTransportAuthPrivate::getClientKey(unsigned char progId)
+{
+ int manifestMatchCount = 0;
+ struct IdBlock mr;
+ int total_size = 0;
+ char *result = 0;
+ char *result_ptr;
+ int keysFound = 0;
+ bool foundKey;
+ int keysRead = 0;
+ struct usr_key_entry keys_list[128];
+
+ if ( keyCache.contains( progId ))
+ return (const unsigned char *)keyCache[progId];
+
+ SxeRegistryLocker rlock( m_packageRegistry );
+
+ // ### Qt 4.3: this is hacky - see documentation for setKeyFilePath
+ QString manifestPath = m_keyFilePath + QLatin1String("/manifest");
+ QString actualKeyPath = QLatin1String("/proc/lids/keys");
+ bool noFailOnKeyMissing = true;
+ if ( !QFile::exists( actualKeyPath )) {
+ actualKeyPath = m_keyFilePath + QLatin1String( "/" QSXE_KEYFILE );
+ }
+ QFile kf( actualKeyPath );
+ QFile mn( manifestPath );
+ if ( !__fileOpen( &mn ))
+ goto key_not_found;
+ // first find how much storage is needed
+ while ( mn.read( (char*)&mr, sizeof(struct IdBlock)) > 0 )
+ if ( mr.progId == progId )
+ manifestMatchCount++;
+ if ( manifestMatchCount == 0 )
+ goto key_not_found;
+ if ( !__fileOpen( &kf ))
+ {
+ noFailOnKeyMissing = false;
+ goto key_not_found;
+ }
+ total_size = 2 * QSXE_MAGIC_BYTES + manifestMatchCount * QSXE_KEY_LEN;
+ result = (char*)malloc( total_size );
+ Q_CHECK_PTR( result );
+ mn.seek( 0 );
+ result_ptr = result;
+ /* reading whole key array in is much more efficient, 99% case is this loop only
+ executes once, should not have more than 128 keyed items */
+ while (( keysRead = kf.read( (char*)keys_list, sizeof(struct usr_key_entry)*128 )) > 0 )
+ {
+ /* qDebug("PID %d: getClientKey() - read %d bytes = %d keys from %s", getpid(), keysRead,
+ keysRead/sizeof(struct usr_key_entry), qPrintable(actualKeyPath)); */
+ keysRead /= sizeof(struct usr_key_entry);
+ while ( mn.read( (char*)&mr, sizeof(struct IdBlock)) > 0 )
+ {
+ if ( mr.progId == progId )
+ {
+ foundKey = false;
+ for ( int i = 0; i < keysRead; ++i )
+ {
+ /* if ( i == 0 )
+ qDebug() << " pid" << getpid() << "looking for device" << (dev_t)mr.device << "inode" << (ino_t)mr.inode;
+ qDebug() << " pid" << getpid() << "trying device" << keys_list[i].dev << "inode" << keys_list[i].ino; */
+ if ( keys_list[i].ino == (ino_t)mr.inode && keys_list[i].dev == (dev_t)mr.device )
+ {
+ memcpy( result_ptr, keys_list[i].key, QSXE_KEY_LEN );
+ result_ptr += QSXE_KEY_LEN;
+ foundKey = true;
+ break;
+ }
+ }
+ if ( foundKey )
+ {
+ keysFound++;
+ if ( keysFound == manifestMatchCount )
+ break;
+ }
+ }
+ }
+ }
+ if ( result_ptr == result ) // nothing found!
+ goto key_not_found;
+ // 2 x magic bytes sentinel at end of sequence
+ for ( int i = 0; i < 2; ++i )
+ for ( int j = 0; j < QSXE_MAGIC_BYTES; ++j )
+ *result_ptr++ = magic[j];
+ keyCache.insert( progId, result, total_size / 10 );
+ /* qDebug( "PID %d : Found %d client keys for prog %u", getpid(), keysFound, progId ); */
+ goto success_out;
+
+key_not_found:
+ if ( noFailOnKeyMissing ) // return an "empty" set of keys in this case
+ {
+ if ( result == 0 )
+ {
+ result = (char*)malloc( 2 * QSXE_MAGIC_BYTES );
+ Q_CHECK_PTR( result );
+ }
+ result_ptr = result;
+ for ( int i = 0; i < 2; ++i )
+ for ( int j = 0; j < QSXE_MAGIC_BYTES; ++j )
+ *result_ptr++ = magic[j];
+ return (unsigned char *)result;
+ }
+ qWarning( "PID %d : Not found client key for prog %u", getpid(), progId );
+ if ( result )
+ {
+ free( result );
+ result = 0;
+ }
+success_out:
+ if ( mn.isOpen() )
+ mn.close();
+ if ( kf.isOpen() )
+ kf.close();
+ return (unsigned char *)result;
+}
+
+void QTransportAuthPrivate::invalidateClientKeyCache()
+{
+ keyfileMutex.lock();
+ keyCache.clear();
+ keyfileMutex.unlock();
+}
+
+////////////////////////////////////////////////////////////////////////
+////
+//// RequestAnalyzer definition
+////
+
+
+RequestAnalyzer::RequestAnalyzer()
+ : moreData( false )
+ , dataSize( 0 )
+{
+}
+
+RequestAnalyzer::~RequestAnalyzer()
+{
+}
+
+/*!
+ Analzye the data in the\a msgQueue according to some protocol
+ and produce a request string for policy analysis.
+
+ If enough data is in the queue for analysis of a complete message,
+ return a non-null string, and set a flag so requireMoreData() will
+ return false; otherwise return a null string and requireMoreData()
+ return true.
+
+ The amount of bytes analyzed is then available via bytesAnalyzed().
+
+ A null string is also returned in the case where the message was
+ corrupt and could not be analyzed. In this case requireMoreData()
+ returns false.
+
+Note: this method will modify the msgQueue and pull off the data
+ deemed to be corrupt, in the case of corrupt data.
+
+ In all other cases the msgQueue is left alone. The calling code
+ should then pull off the analyzed data. Use bytesAnalzyed() to
+ find how much data to pull off the queue.
+*/
+QString RequestAnalyzer::analyze( QByteArray *msgQueue )
+{
+#ifdef Q_WS_QWS
+ dataSize = 0;
+ moreData = false;
+ QBuffer cmdBuf( msgQueue );
+ cmdBuf.open( QIODevice::ReadOnly | QIODevice::Unbuffered );
+ QWSCommand::Type command_type = (QWSCommand::Type)(qws_read_uint( &cmdBuf ));
+ QWSCommand *command = QWSCommand::factory(command_type);
+ // if NULL, factory will have already printed warning for bogus
+ // command_type just purge the bad stuff and attempt to recover
+ if ( command == NULL )
+ {
+ *msgQueue = msgQueue->mid( sizeof(int) );
+ return QString();
+ }
+ QString request = QLatin1String(qws_getCommandTypeString(command_type));
+#ifndef QT_NO_COP
+ if ( !command->read( &cmdBuf ))
+ {
+ // not all command arrived yet - come back later
+ delete command;
+ moreData = true;
+ return QString();
+ }
+ if ( command_type == QWSCommand::QCopSend )
+ {
+ QWSQCopSendCommand *sendCommand = static_cast<QWSQCopSendCommand*>(command);
+ request += QString( QLatin1String("/QCop/%1/%2") ).arg( sendCommand->channel ).arg( sendCommand->message );
+ }
+ if ( command_type == QWSCommand::QCopRegisterChannel )
+ {
+ QWSQCopRegisterChannelCommand *registerCommand = static_cast<QWSQCopRegisterChannelCommand*>(command);
+ request += QString( QLatin1String("/QCop/RegisterChannel/%1") ).arg( registerCommand->channel );
+ }
+#endif
+ dataSize = QWS_PROTOCOL_ITEM_SIZE( *command );
+ delete command;
+ return request;
+#else
+ Q_UNUSED(msgQueue);
+ return QString();
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////
+////
+//// AuthDevice definition
+////
+
+/*!
+ Constructs a new auth device for the transport \a data and I/O device \a parent.
+
+ Incoming or outgoing data will be authenticated according to the auth direction \a dir.
+
+ The auth device will take ownership of the transport \a data and delete it when the device
+ is destroyed.
+*/
+QAuthDevice::QAuthDevice( QIODevice *parent, QTransportAuth::Data *data, AuthDirection dir )
+ : QIODevice( parent )
+ , d( data )
+ , way( dir )
+ , m_target( parent )
+ , m_client( 0 )
+ , m_bytesAvailable( 0 )
+ , m_skipWritten( 0 )
+ , analyzer( 0 )
+{
+ if ( dir == Receive ) // server side
+ {
+ connect( m_target, SIGNAL(readyRead()),
+ this, SLOT(recvReadyRead()));
+ } else {
+ connect( m_target, SIGNAL(readyRead()),
+ this, SIGNAL(readyRead()));
+ }
+ connect( m_target, SIGNAL(bytesWritten(qint64)),
+ this, SLOT(targetBytesWritten(qint64)) );
+ open( QIODevice::ReadWrite | QIODevice::Unbuffered );
+}
+
+QAuthDevice::~QAuthDevice()
+{
+ if ( analyzer )
+ delete analyzer;
+ delete d;
+}
+
+/*!
+ \internal
+ Store a pointer to the related device or instance which this
+ authorizer is proxying for
+*/
+void QAuthDevice::setClient( QObject *cli )
+{
+ m_client = cli;
+ QTransportAuth::getInstance()->d_func()->buffersByClient[cli] = this;
+ QObject::connect( cli, SIGNAL(destroyed(QObject*)),
+ QTransportAuth::getInstance(), SLOT(bufferDestroyed(QObject*)) );
+ // qDebug( "@@@@@@@@@@@@ client set %p @@@@@@@@@", cli );
+ // qDebug( " client count %d", QTransportAuth::getInstance()->d_func()->buffersByClient.count() );
+}
+
+QObject *QAuthDevice::client() const
+{
+ return m_client;
+}
+
+/*
+ \fn void QAuthDevice::authViolation(QTransportAuth::Data &)
+
+ This signal is emitted if an authorization failure is generated, as
+ described in checkAuth();
+
+ \sa checkAuth()
+*/
+
+
+/*
+ \fn void QAuthDevice::policyCheck(QTransportAuth::Data &transport, const QString &request )
+
+ This signal is emitted when a transport successfully delivers a request
+ and gives the opportunity to either deny or accept the request.
+
+ This signal must be connected in the same thread, ie it cannot be queued.
+
+ As soon as all handlers connected to this signal are processed the Allow or
+ Deny state on the \a transport is checked, and the request is allowed or denied
+ accordingly.
+
+ \sa checkAuth()
+*/
+
+/*!
+ \internal
+ Reimplement QIODevice writeData method.
+
+ For client end, when the device is written to the incoming data is
+ processed and an authentication header calculated. This is pushed
+ into the target device, followed by the actual incoming data (the
+ payload).
+
+ For server end, it is a fatal error to write to the device.
+*/
+qint64 QAuthDevice::writeData(const char *data, qint64 len)
+{
+ if ( way == Receive ) // server
+ return m_target->write( data, len );
+ // client
+#ifdef QTRANSPORTAUTH_DEBUG
+ char displaybuf[1024];
+#endif
+ char header[QSXE_HEADER_LEN];
+ ::memset( header, 0, QSXE_HEADER_LEN );
+ qint64 bytes = 0;
+ if ( QTransportAuth::getInstance()->authToMessage( *d, header, data, len ))
+ {
+ m_target->write( header, QSXE_HEADER_LEN );
+#ifdef QTRANSPORTAUTH_DEBUG
+ hexstring( displaybuf, (const unsigned char *)header, QSXE_HEADER_LEN );
+ qDebug( "%d QAuthDevice::writeData - CLIENT: Header written: %s", getpid(), displaybuf );
+#endif
+ m_skipWritten += QSXE_HEADER_LEN;
+ }
+ m_target->write( data, len );
+ bytes += len;
+#ifdef QTRANSPORTAUTH_DEBUG
+ int bytesToDisplay = bytes;
+ const unsigned char *dataptr = (const unsigned char *)data;
+ while ( bytesToDisplay > 0 )
+ {
+ int amt = bytes < 500 ? bytes : 500;
+ hexstring( displaybuf, dataptr, amt );
+ qDebug( "%d QAuthDevice::writeData - CLIENT: %s", getpid(), bytes > 0 ? displaybuf : "(null)" );
+ dataptr += 500;
+ bytesToDisplay -= 500;
+ }
+#endif
+ if ( m_target->inherits( "QAbstractSocket" ))
+ static_cast<QAbstractSocket*>(m_target)->flush();
+ return bytes;
+}
+
+/*!
+ Reimplement from QIODevice
+
+ Read data out of the internal message queue, reduce the queue by the amount
+ read. Note that the amount available is only ever the size of a command
+ (although a command can be very big) since we need to check at command
+ boundaries for new authentication headers.
+*/
+qint64 QAuthDevice::readData( char *data, qint64 maxSize )
+{
+ if ( way == Send ) // client
+ return m_target->read( data, maxSize );
+ if ( msgQueue.size() == 0 )
+ return 0;
+#ifdef QTRANSPORTAUTH_DEBUG
+ char displaybuf[1024];
+ hexstring( displaybuf, reinterpret_cast<const unsigned char *>(msgQueue.constData()),
+ msgQueue.size() > 500 ? 500 : msgQueue.size() );
+ qDebug() << getpid() << "QAuthDevice::readData() buffered/requested/avail"
+ << msgQueue.size() << maxSize << m_bytesAvailable << displaybuf;
+#endif
+ Q_ASSERT( m_bytesAvailable <= msgQueue.size() );
+ qint64 bytes = ( maxSize > m_bytesAvailable ) ? m_bytesAvailable : maxSize;
+ ::memcpy( data, msgQueue.constData(), bytes );
+ msgQueue = msgQueue.mid( bytes );
+ m_bytesAvailable -= bytes;
+ return bytes;
+}
+
+/*!
+ \internal
+ Receive readyRead signal from the target recv device. In response
+ authorize the data, and write results out to the recvBuf() device
+ for processing by the application. Trigger the readyRead signal.
+
+ Authorizing involves first checking the transport is valid, ie the
+ handshake has either already been done and is cached on a trusted
+ transport, or was valid with this message; then second passing the
+ string representation of the service request up to any policyReceivers
+
+ If either of these fail, the message is denied. In discovery mode
+ denied messages are allowed, but the message is logged.
+*/
+void QAuthDevice::recvReadyRead()
+{
+ qint64 bytes = m_target->bytesAvailable();
+ if ( bytes <= 0 ) return;
+ open( QIODevice::ReadWrite | QIODevice::Unbuffered );
+ QUnixSocket *usock = static_cast<QUnixSocket*>(m_target);
+ QUnixSocketMessage msg = usock->read();
+ msgQueue.append( msg.bytes() );
+ d->processId = msg.processId();
+ // if "fragmented" packet 1/2 way through start of a command, ie
+ // in the QWS msg type, cant do anything, come back later when
+ // there's more of the packet
+ if ( msgQueue.size() < (int)sizeof(int) )
+ {
+ // qDebug() << "returning: msg size too small" << msgQueue.size();
+ return;
+ }
+#ifdef QTRANSPORTAUTH_DEBUG
+ char displaybuf[1024];
+ hexstring( displaybuf, reinterpret_cast<const unsigned char *>(msgQueue.constData()),
+ msgQueue.size() > 500 ? 500 : msgQueue.size() );
+ qDebug( "%d ***** SERVER read %lli bytes - msg %s", getpid(), bytes, displaybuf );
+#endif
+
+ bool bufHasMessages = msgQueue.size() >= (int)sizeof(int);
+ while ( bufHasMessages )
+ {
+ unsigned char saveStatus = d->status;
+ if (( d->status & QTransportAuth::ErrMask ) == QTransportAuth::NoSuchKey )
+ {
+ QTransportAuth::getInstance()->authorizeRequest( *d, QLatin1String("NoSuchKey") );
+ break;
+ }
+ if ( !QTransportAuth::getInstance()->authFromMessage( *d, msgQueue, msgQueue.size() ))
+ {
+ // not all arrived yet? come back later
+ if (( d->status & QTransportAuth::ErrMask ) == QTransportAuth::TooSmall )
+ {
+ d->status = saveStatus;
+ return;
+ }
+ }
+ if (( d->status & QTransportAuth::ErrMask ) == QTransportAuth::NoMagic )
+ {
+ // no msg auth header, don't change the success status for connections
+ if ( d->connection() )
+ d->status = saveStatus;
+ }
+ else
+ {
+ // msg auth header detected and auth determined, remove hdr
+ msgQueue = msgQueue.mid( QSXE_HEADER_LEN );
+ }
+ if ( !authorizeMessage() )
+ break;
+ bufHasMessages = msgQueue.size() >= (int)sizeof(int);
+ }
+}
+
+/**
+ \internal
+ Handle bytesWritten signals from the underlying target device.
+ We adjust the target's value for bytes that are part of auth packets.
+*/
+void QAuthDevice::targetBytesWritten( qint64 bytes )
+{
+ if ( m_skipWritten >= bytes ) {
+ m_skipWritten -= bytes;
+ bytes = 0;
+ } else if ( m_skipWritten > 0 ) {
+ bytes -= m_skipWritten;
+ m_skipWritten = 0;
+ }
+ if ( bytes > 0 ) {
+ emit bytesWritten( bytes );
+ }
+}
+
+/**
+ \internal
+ Pre-process the message to determine what QWS command it is. This
+ information is used as the "request" for the purposes of authorization.
+
+ The request and other data on the connection (id, PID, etc.) are forwarded
+ to all policy listeners by emitting a signal.
+
+ The signal must be processed synchronously because on return the allow/deny
+ status is used immediately to either drop or continue processing the message.
+*/
+bool QAuthDevice::authorizeMessage()
+{
+ if ( analyzer == NULL )
+ analyzer = new RequestAnalyzer();
+ QString request = (*analyzer)( &msgQueue );
+ if ( analyzer->requireMoreData() )
+ return false;
+ bool isAuthorized = true;
+
+ if ( !request.isEmpty() && request != QLatin1String("Unknown") )
+ {
+ isAuthorized = QTransportAuth::getInstance()->authorizeRequest( *d, request );
+ }
+
+ bool moreToProcess = ( msgQueue.size() - analyzer->bytesAnalyzed() ) > (int)sizeof(int);
+ if ( isAuthorized )
+ {
+#ifdef QTRANSPORTAUTH_DEBUG
+ qDebug() << getpid() << "SERVER authorized: releasing" << analyzer->bytesAnalyzed() << "byte command" << request;
+#endif
+ m_bytesAvailable = analyzer->bytesAnalyzed();
+ emit QIODevice::readyRead();
+ return moreToProcess;
+ }
+ else
+ {
+ msgQueue = msgQueue.mid( analyzer->bytesAnalyzed() );
+ }
+
+ return true;
+}
+
+void QAuthDevice::setRequestAnalyzer( RequestAnalyzer *ra )
+{
+ Q_ASSERT( ra );
+ if ( analyzer )
+ delete analyzer;
+ analyzer = ra;
+}
+
+/*!
+ \internal
+ Add authentication header to the beginning of a message
+
+ Note that the per-process auth cookie is used. This key should be rewritten in
+ the binary image of the executable at install time to make it unique.
+
+ For this to be secure some mechanism (eg MAC kernel or other
+ permissions) must prevent other processes from reading the key.
+
+ The buffer must have AUTH_SPACE(0) bytes spare at the beginning for the
+ authentication header to be added.
+
+ Returns true if header successfully added. Will fail if the
+ per-process key has not yet been set with setProcessKey()
+*/
+bool QTransportAuth::authToMessage( QTransportAuth::Data &d, char *hdr, const char *msg, int msgLen )
+{
+ // qDebug( "authToMessage(): prog id %u", d.progId );
+ // only authorize connection oriented transports once, unless key has changed
+ if ( d.connection() && ((d.status & QTransportAuth::ErrMask) != QTransportAuth::Pending) &&
+ d_func()->authKey.progId == d.progId )
+ return false;
+ d.progId = d_func()->authKey.progId;
+ // If Unix socket credentials are being used the key wont be set
+ if ( !d_func()->keyInitialised )
+ return false;
+ unsigned char digest[QSXE_KEY_LEN];
+ char *msgPtr = hdr;
+ // magic always goes on the beginning
+ for ( int m = 0; m < QSXE_MAGIC_BYTES; ++m )
+ *msgPtr++ = magic[m];
+ hdr[ QSXE_LEN_IDX ] = (unsigned char)msgLen;
+ if ( !d.trusted())
+ {
+ // Use HMAC
+ int rc = hmac_md5( (unsigned char *)msg, msgLen, d_func()->authKey.key, QSXE_KEY_LEN, digest );
+ if ( rc == -1 )
+ return false;
+ memcpy( hdr + QSXE_KEY_IDX, digest, QSXE_KEY_LEN );
+ }
+ else
+ {
+ memcpy( hdr + QSXE_KEY_IDX, d_func()->authKey.key, QSXE_KEY_LEN );
+ }
+
+ hdr[ QSXE_PROG_IDX ] = d_func()->authKey.progId;
+
+#ifdef QTRANSPORTAUTH_DEBUG
+ char keydisplay[QSXE_KEY_LEN*2+1];
+ hexstring( keydisplay, d_func()->authKey.key, QSXE_KEY_LEN );
+
+ qDebug( "%d CLIENT Auth to message %s against prog id %u and key %s\n",
+ getpid(), msg, d_func()->authKey.progId, keydisplay );
+#endif
+
+ // TODO implement sequence to prevent replay attack, not required
+ // for trusted transports
+ hdr[ QSXE_SEQ_IDX ] = 1; // dummy sequence
+
+ d.status = ( d.status & QTransportAuth::StatusMask ) | QTransportAuth::Success;
+ return true;
+}
+
+
+/*!
+ Check authorization on the \a msg, which must be of size \a msgLen,
+ for the transport \a d.
+
+ If able to determine authorization, return the program identity of
+ the message source in the reference \a progId, and return true.
+
+ Otherwise return false.
+
+ If data is being received on a socket, it may be that more data is yet
+ needed before authentication can proceed.
+
+ Also the message may not be an authenticated at all.
+
+ In these cases the method returns false to indicate authorization could
+ not be determined:
+ \list
+ \i The message is too small to carry the authentication data
+ (status TooSmall is set on the \a d transport )
+ \i The 4 magic bytes are missing from the message start
+ (status NoMagic is set on the \a d transport )
+ \i The message is too small to carry the auth + claimed payload
+ (status TooSmall is set on the \a d transport )
+ \endlist
+
+ If however the authentication header (preceded by the magic bytes) and
+ any authenticated payload is received the method will determine the
+ authentication status, and return true.
+
+ In the following cases as well as returning true it will also emit
+ an authViolation():
+ \list
+ \i If the program id claimed by the message is not found in the key file
+ (status NoSuchKey is set on the \a d transport )
+ \i The authentication token failed against the claimed program id:
+ \list
+ \i in the case of trusted transports, the secret did not match
+ \i in the case of untrusted transports the HMAC code did not match
+ \endlist
+ (status FailMatch is set on the \a d transport )
+ \endlist
+
+ In these cases the authViolation( QTransportAuth::Data d ) signal is emitted
+ and the error string can be obtained from the status like this:
+ \snippet doc/src/snippets/code/src_gui_embedded_qtransportauth_qws.cpp 4
+*/
+bool QTransportAuth::authFromMessage( QTransportAuth::Data &d, const char *msg, int msgLen )
+{
+ if ( msgLen < QSXE_MAGIC_BYTES )
+ {
+ d.status = ( d.status & QTransportAuth::StatusMask ) | QTransportAuth::TooSmall;
+ return false;
+ }
+ // if no magic bytes, exit straight away
+ int m;
+ const unsigned char *mptr = reinterpret_cast<const unsigned char *>(msg);
+ for ( m = 0; m < QSXE_MAGIC_BYTES; ++m )
+ {
+ if ( *mptr++ != magic[m] )
+ {
+ d.status = ( d.status & QTransportAuth::StatusMask ) | QTransportAuth::NoMagic;
+ return false;
+ }
+ }
+
+ if ( msgLen < AUTH_SPACE(1) )
+ {
+ d.status = ( d.status & QTransportAuth::StatusMask ) | QTransportAuth::TooSmall;
+ return false;
+ }
+
+ // At this point we know the header is at least long enough to contain valid auth
+ // data, however the data may be spoofed. If it is not verified then the status will
+ // be set to uncertified so the spoofed data will not be relied on. However we want to
+ // know the program id which is being reported (even if it might be spoofed) for
+ // policy debugging purposes. So set it here, rather than after verification.
+ d.progId = msg[QSXE_PROG_IDX];
+
+#ifdef QTRANSPORTAUTH_DEBUG
+ char authhdr[QSXE_HEADER_LEN*2+1];
+ hexstring( authhdr, reinterpret_cast<const unsigned char *>(msg), QSXE_HEADER_LEN );
+ qDebug( "%d SERVER authFromMessage(): message header is %s",
+ getpid(), authhdr );
+#endif
+
+ unsigned char authLen = (unsigned char)(msg[ QSXE_LEN_IDX ]);
+
+ if ( msgLen < AUTH_SPACE(authLen) )
+ {
+ d.status = ( d.status & QTransportAuth::StatusMask ) | QTransportAuth::TooSmall;
+ return false;
+ }
+
+ bool isCached = d_func()->keyCache.contains( d.progId );
+ const unsigned char *clientKey = d_func()->getClientKey( d.progId );
+ if ( clientKey == NULL )
+ {
+ d.status = ( d.status & QTransportAuth::StatusMask ) | QTransportAuth::NoSuchKey;
+ return false;
+ }
+
+#ifdef QTRANSPORTAUTH_DEBUG
+ char keydisplay[QSXE_KEY_LEN*2+1];
+ hexstring( keydisplay, clientKey, QSXE_KEY_LEN );
+ qDebug( "\t\tauthFromMessage(): message %s against prog id %u and key %s\n",
+ AUTH_DATA(msg), ((unsigned int)d.progId), keydisplay );
+#endif
+
+ const unsigned char *auth_tok;
+ unsigned char digest[QSXE_KEY_LEN];
+ bool multi_tok = false;
+
+ bool need_to_recheck=false;
+ do
+ {
+ if ( !d.trusted())
+ {
+ hmac_md5( AUTH_DATA(msg), authLen, clientKey, QSXE_KEY_LEN, digest );
+ auth_tok = digest;
+ }
+ else
+ {
+ auth_tok = clientKey;
+ multi_tok = true; // 1 or more keys are in the clientKey
+ }
+ while( true )
+ {
+ if ( memcmp( auth_tok, magic, QSXE_MAGIC_BYTES ) == 0
+ && memcmp( auth_tok + QSXE_MAGIC_BYTES, magic, QSXE_MAGIC_BYTES ) == 0 )
+ break;
+ if ( memcmp( msg + QSXE_KEY_IDX, auth_tok, QSXE_KEY_LEN ) == 0 )
+ {
+ d.status = ( d.status & QTransportAuth::StatusMask ) | QTransportAuth::Success;
+ return true;
+ }
+ if ( !multi_tok )
+ break;
+ auth_tok += QSXE_KEY_LEN;
+ }
+ //the keys cached on d.progId may not contain the binary key because the cache entry was made
+ //before the binary had first started, must search for client key again.
+ if ( isCached )
+ {
+ d_func()->keyCache.remove(d.progId);
+ isCached = false;
+
+#ifdef QTRANSPORTAUTH_DEBUG
+ qDebug() << "QTransportAuth::authFromMessage(): key not found in set of keys cached"
+ << "against prog Id =" << d.progId << ". Re-obtaining client key. ";
+#endif
+ clientKey = d_func()->getClientKey( d.progId );
+ if ( clientKey == NULL )
+ {
+ d.status = ( d.status & QTransportAuth::StatusMask ) | QTransportAuth::NoSuchKey;
+ return false;
+ }
+ need_to_recheck = true;
+ }
+ else
+ {
+ need_to_recheck = false;
+ }
+ } while( need_to_recheck );
+
+ d.status = ( d.status & QTransportAuth::StatusMask ) | QTransportAuth::FailMatch;
+ qWarning() << "QTransportAuth::authFromMessage():failed authentication";
+ FAREnforcer::getInstance()->logAuthAttempt( QDateTime::currentDateTime() );
+ emit authViolation( d );
+ return false;
+}
+
+
+#ifdef QTRANSPORTAUTH_DEBUG
+/*!
+ sprintf into hex - dest \a buf, src \a key, \a key_len is length of key.
+
+ The target buf should be [ key_len * 2 + 1 ] in size
+*/
+void hexstring( char *buf, const unsigned char* key, size_t key_len )
+{
+ unsigned int i, p;
+ for ( i = 0, p = 0; i < key_len; i++, p+=2 )
+ {
+ unsigned char lo_nibble = key[i] & 0x0f;
+ unsigned char hi_nibble = key[i] >> 4;
+ buf[p] = (int)hi_nibble > 9 ? hi_nibble-10 + 'A' : hi_nibble + '0';
+ buf[p+1] = (int)lo_nibble > 9 ? lo_nibble-10 + 'A' : lo_nibble + '0';
+ }
+ buf[p] = '\0';
+}
+#endif
+
+/*
+ HMAC MD5 as listed in RFC 2104
+
+ This code is taken from:
+
+ http://www.faqs.org/rfcs/rfc2104.html
+
+ with the allowance for keys other than length 16 removed, but otherwise
+ a straight cut-and-paste.
+
+ The HMAC_MD5 transform looks like:
+
+ \snippet doc/src/snippets/code/src.gui.embedded.qtransportauth_qws.cpp 5
+
+ \list
+ \i where K is an n byte key
+ \i ipad is the byte 0x36 repeated 64 times
+ \i opad is the byte 0x5c repeated 64 times
+ \i and text is the data being protected
+ \endlist
+
+ Hardware is available with accelerated implementations of HMAC-MD5 and
+ HMAC-SHA1. Where this hardware is available, this routine should be
+ replaced with a call into the accelerated version.
+*/
+
+static int hmac_md5(
+ unsigned char* text, /* pointer to data stream */
+ int text_length, /* length of data stream */
+ const unsigned char* key, /* pointer to authentication key */
+ int key_length, /* length of authentication key */
+ unsigned char * digest /* caller digest to be filled in */
+ )
+{
+ MD5Context context;
+ unsigned char k_ipad[65]; /* inner padding - * key XORd with ipad */
+ unsigned char k_opad[65]; /* outer padding - * key XORd with opad */
+ int i;
+
+ /* in this implementation key_length == 16 */
+ if ( key_length != 16 )
+ {
+ fprintf( stderr, "Key length was %d - must be 16 bytes", key_length );
+ return 0;
+ }
+
+ /* start out by storing key in pads */
+ memset( k_ipad, 0, sizeof k_ipad );
+ memset( k_opad, 0, sizeof k_opad );
+ memcpy( k_ipad, key, key_length );
+ memcpy( k_opad, key, key_length );
+
+ /* XOR key with ipad and opad values */
+ for (i=0; i<64; i++) {
+ k_ipad[i] ^= 0x36;
+ k_opad[i] ^= 0x5c;
+ }
+
+ /* perform inner MD5 */
+ MD5Init(&context); /* init context for 1st pass */
+ MD5Update(&context, k_ipad, 64); /* start with inner pad */
+ MD5Update(&context, text, text_length); /* then text of datagram */
+ MD5Final(&context, digest); /* finish up 1st pass */
+
+ /* perform outer MD5 */
+ MD5Init(&context); /* init context for 2nd pass */
+ MD5Update(&context, k_opad, 64); /* start with outer pad */
+ MD5Update(&context, digest, 16); /* then results of 1st * hash */
+ MD5Final(&context, digest); /* finish up 2nd pass */
+ return 1;
+}
+
+
+const int FAREnforcer::minutelyRate = 4; //allowed number of false authentication attempts per minute
+const QString FAREnforcer::FARMessage = QLatin1String("FAR_Exceeded");
+const QString FAREnforcer::SxeTag = QLatin1String("<SXE Breach>");
+const int FAREnforcer::minute = 60;
+
+FAREnforcer::FAREnforcer():authAttempts()
+{
+ QDateTime nullDateTime = QDateTime();
+ for (int i = 0; i < minutelyRate; i++ )
+ authAttempts << nullDateTime;
+}
+
+
+FAREnforcer *FAREnforcer::getInstance()
+{
+ static FAREnforcer theInstance;
+ return &theInstance;
+}
+
+void FAREnforcer::logAuthAttempt( QDateTime time )
+{
+ QDateTime dt = authAttempts.takeFirst();
+
+ authAttempts.append( time );
+ if ( dt.secsTo( authAttempts.last() ) <= minute )
+ {
+#if defined(SXE_DISCOVERY)
+ if ( QTransportAuth::getInstance()->isDiscoveryMode() ) {
+ static QBasicAtomicInt reported = Q_BASIC_ATOMIC_INITIALIZER(0);
+ if ( reported.testAndSetRelaxed(0,1) ) {
+#ifndef QT_NO_TEXTSTREAM
+ QString logFilePath = QTransportAuth::getInstance()->logFilePath();
+ if ( !logFilePath.isEmpty() ) {
+ QFile log( logFilePath );
+ if ( !log.open(QIODevice::WriteOnly | QIODevice::Append) ) {
+ qWarning("Could not write to log in discovery mode: %s",
+ qPrintable(logFilePath) );
+ } else {
+ QTextStream ts( &log );
+ ts << "\t\tWarning: False Authentication Rate of " << minutelyRate << "\n"
+ << "\t\tserver connections/authentications per minute has been exceeded,\n"
+ << "\t\tno further warnings will be issued\n";
+ }
+ }
+ }
+#endif
+ reset();
+ return;
+ }
+#endif
+ syslog( LOG_ERR | LOG_LOCAL6, "%s %s",
+ qPrintable( FAREnforcer::SxeTag ),
+ qPrintable( FAREnforcer::FARMessage ) );
+ reset();
+ }
+}
+
+void FAREnforcer::reset()
+{
+ QDateTime nullDateTime = QDateTime();
+ for (int i = 0; i < minutelyRate; i++ )
+ authAttempts[i] = nullDateTime;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qtransportauth_qws_p.cpp"
+
+#endif // QT_NO_SXE