/*************************************************************************** * Copyright (C) 2012 by Timothy Pearson * * kb9vqf@pearsoncomputing.net * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include "tdekrbclientsocket.h" #define NET_SEC_BUF_SIZE (2048) // When control comes back from processEvents() my object may be completely gone! This attempts to mitigate the risk #define SAFELY_PROCESS_EVENTS if (!m_canary) { \ m_canary = new bool; \ *m_canary = false; \ } \ bool* canary = m_canary; \ tqApp->eventLoop()->processEvents(TQEventLoop::ExcludeUserInput); \ if (*canary == true) { \ delete canary; \ return -1; \ } \ delete m_canary; \ m_canary = NULL; /* exception handling */ struct exit_exception { int c; exit_exception(int c):c(c) { } }; class SASLDataPrivate { public: sasl_callback_t m_callbacks[N_CALLBACKS]; sasl_conn_t *m_krbConnection; }; static const char * safe_sasl_errdetail(sasl_conn_t *conn) { const char * str = sasl_errdetail(conn); if (str) { return str; } else { return "unknown error"; } } static int logSASLMessages(void *context __attribute__((unused)), int priority, const char *message) { const char *label; if (!message) { return SASL_BADPARAM; } switch (priority) { case SASL_LOG_ERR: label = "Error"; break; case SASL_LOG_NOTE: label = "Info"; break; default: label = "Other"; break; } printf("[SASL %s] %s\n\r", label, message); return SASL_OK; } TDEKerberosClientSocket::TDEKerberosClientSocket(TQObject *parent, const char *name) : TQSocket(parent, name), m_kerberosRequested(false), m_criticalSection(0), m_bufferLength(0), m_krbInitRunning(false), m_krbInitState(-1), m_dataTimeout(-1), kerberosInitLoopTimer(NULL), m_canary(NULL), m_negotiatedMaxBufferSize(NET_SEC_BUF_SIZE) { saslData = new SASLDataPrivate; saslData->m_krbConnection = NULL; m_buffer = new TQBuffer(); m_buffer->open(IO_ReadWrite|IO_Truncate); } TDEKerberosClientSocket::~TDEKerberosClientSocket() { if (m_canary) { *m_canary = true; } if (kerberosInitLoopTimer) { kerberosInitLoopTimer->stop(); delete kerberosInitLoopTimer; kerberosInitLoopTimer = NULL; } m_buffer->close(); delete m_buffer; delete saslData; } void TDEKerberosClientSocket::setDataTimeout(int timeoutms) { m_dataTimeout = timeoutms; } bool TDEKerberosClientSocket::open(int mode) { setStatusMessage(i18n("Establishing initial connection to server")); bool ret = TQSocket::open(mode); if (m_kerberosRequested) { initializeKerberosInterface(); } return ret; } void TDEKerberosClientSocket::close() { TQSocket::close(); setStatusMessage(i18n("Disconnected")); } void TDEKerberosClientSocket::flush() { if (kerberosStatus() == KerberosInUse) { // FIXME // If a write buffer is implemented, it will need to be flushed before the following call is made TQSocket::flush(); } else { TQSocket::flush(); } } TQIODevice::Offset TDEKerberosClientSocket::size() const { TQIODevice::Offset ret; if (kerberosStatus() == KerberosInUse) { ret = m_bufferLength; } else { ret = TQSocket::size(); } return ret; } TQIODevice::Offset TDEKerberosClientSocket::at() const { return TQSocket::at(); } bool TDEKerberosClientSocket::at(TQIODevice::Offset off) { long i; bool ret; if (kerberosStatus() == KerberosInUse) { if (off > 0) { // Prevent overflow if (off > (unsigned long)m_bufferLength) { off = m_bufferLength; } // Remove the specified bytes from the buffer m_bufferLength = m_bufferLength-off; TQByteArray ba = m_buffer->buffer(); for (i=0; iclose(); m_buffer->open(IO_ReadWrite|IO_Truncate); } } return true; } else { ret = TQSocket::at(off); } return ret; } bool TDEKerberosClientSocket::atEnd() const { bool ret; if (kerberosStatus() == KerberosInUse) { ret = TQSocket::atEnd(); } else { ret = TQSocket::atEnd(); } return ret; } TQ_ULONG TDEKerberosClientSocket::bytesAvailable() const { bool ret; if (kerberosStatus() == KerberosInUse) { ret = m_bufferLength; } else { ret = TQSocket::bytesAvailable(); } return ret; } int TDEKerberosClientSocket::processPendingData() { if (kerberosStatus() == KerberosInUse) { int reclen; int wrlen; if (m_bufferLength <= 0) { char* buf = (char*)malloc(m_negotiatedMaxBufferSize); reclen = receiveEncryptedData(buf, m_negotiatedMaxBufferSize); if (reclen < 0) { free(buf); return -1; } if (reclen > 0) { m_buffer->at(m_bufferLength); wrlen = m_buffer->writeBlock(buf, reclen); if (wrlen > 0) { m_bufferLength = m_bufferLength + wrlen; emit(newDataReceived()); } } free(buf); } } return 0; } int TDEKerberosClientSocket::setUsingKerberos(bool krbactive) { int ret = 0; if (m_serviceName == "") { printf("[ERROR] No service name set!\n\r"); fflush(stdout); return -1; } if (krbactive) { m_kerberosRequested = true; if ((!saslData->m_krbConnection) && (state() == TQSocket::Connected)) { initializeKerberosInterface(); } } else { m_kerberosRequested = false; if (saslData->m_krbConnection) { freeKerberosConnection(); } } return ret; } void TDEKerberosClientSocket::setServiceName(TQString name) { m_serviceName = name; } void TDEKerberosClientSocket::setServerFQDN(TQString name) { m_serverFQDN = name; } TQ_LONG TDEKerberosClientSocket::readBlock(char *data, TQ_ULONG maxlen) { long i; TQ_LONG ret; if (kerberosStatus() == KerberosInUse) { int reclen; int wrlen; if (m_bufferLength <= 0) { char* buf = (char*)malloc(m_negotiatedMaxBufferSize); reclen = receiveEncryptedData(buf, m_negotiatedMaxBufferSize); if (reclen < 0) { free(buf); return -1; } if (reclen > 0) { m_buffer->at(m_bufferLength); wrlen = m_buffer->writeBlock(buf, reclen); if (wrlen > 0) { m_bufferLength = m_bufferLength + wrlen; emit(newDataReceived()); } } free(buf); } if (maxlen > (unsigned int)m_bufferLength) { maxlen = m_bufferLength; } m_buffer->at(0); ret = m_buffer->readBlock(data, maxlen); if (ret > 0) { // Remove the read bytes from the buffer m_bufferLength = m_bufferLength-ret; TQByteArray ba = m_buffer->buffer(); for (i=0; iclose(); m_buffer->open(IO_ReadWrite|IO_Truncate); } } } else { ret = TQSocket::readBlock(data, maxlen); } return ret; } TQ_LONG TDEKerberosClientSocket::writeBlock(const char *data, TQ_ULONG len) { TQ_LONG ret; if (kerberosStatus() == KerberosInUse) { ret = transmitEncryptedData(socket(), data, len); } else { ret = TQSocket::writeBlock(data, len); } return ret; } TQ_LONG TDEKerberosClientSocket::readLine(char *data, TQ_ULONG maxlen) { long i; TQ_LONG ret; if (kerberosStatus() == KerberosInUse) { int reclen; int wrlen; if (m_bufferLength <= 0) { char* buf = (char*)malloc(m_negotiatedMaxBufferSize); reclen = receiveEncryptedData(buf, m_negotiatedMaxBufferSize); if (reclen < 0) { free(buf); return -1; } if (reclen > 0) { m_buffer->at(m_bufferLength); wrlen = m_buffer->writeBlock(buf, reclen); if (wrlen > 0) { m_bufferLength = m_bufferLength + wrlen; emit(newDataReceived()); } } free(buf); } if (maxlen > (unsigned int)m_bufferLength) { maxlen = m_bufferLength; } m_buffer->at(0); ret = m_buffer->readLine(data, maxlen); if (ret > 0) { // Remove the read bytes from the buffer m_bufferLength = m_bufferLength-ret; TQByteArray ba = m_buffer->buffer(); for (i=0; iclose(); m_buffer->open(IO_ReadWrite|IO_Truncate); } } } else { ret = TQSocket::readLine(data, maxlen); } return ret; } TQString TDEKerberosClientSocket::readLine() { long i; TQString ret; long maxlen; if (kerberosStatus() == KerberosInUse) { int reclen; int wrlen; int readlen; char* buf; maxlen = m_negotiatedMaxBufferSize; if (m_bufferLength <= 0) { buf = (char*)malloc(m_negotiatedMaxBufferSize); reclen = receiveEncryptedData(buf, m_negotiatedMaxBufferSize); if (reclen < 0) { free(buf); return TQString::null; } if (reclen > 0) { m_buffer->at(m_bufferLength); wrlen = m_buffer->writeBlock(buf, reclen); if (wrlen > 0) { m_bufferLength = m_bufferLength + wrlen; emit(newDataReceived()); } } free(buf); } if (maxlen > m_bufferLength) { maxlen = m_bufferLength; } m_buffer->at(0); buf = (char*)malloc(maxlen); readlen = m_buffer->readLine(buf, maxlen); if (readlen > 0) { // Remove the read bytes from the buffer m_bufferLength = m_bufferLength-readlen; TQByteArray ba = m_buffer->buffer(); for (i=0; iclose(); m_buffer->open(IO_ReadWrite|IO_Truncate); } ret = TQString(buf); } else { ret == TQString::null; } free(buf); } else { ret = TQSocket::readLine(); } return ret; } void TDEKerberosClientSocket::writeLine(TQString str) { if (kerberosStatus() == KerberosInUse) { transmitEncryptedData(socket(), str.ascii(), str.length()); } else { TQSocket::writeBlock(str.ascii(), str.length()); } } void TDEKerberosClientSocket::freeKerberosConnection(void) { if (saslData->m_krbConnection) { sasl_dispose(&saslData->m_krbConnection); } saslData->m_krbConnection = 0; } void TDEKerberosClientSocket::sendSASLDataToNetwork(const char *buffer, unsigned length, int netfd) { char *buf; unsigned len, alloclen; int result; alloclen = ((length / 3) + 1) * 4 + 1; buf = (char*)malloc(alloclen+1); if (!buf) { printf("[ERROR] Unable to malloc()!\n\r"); return; } result = sasl_encode64(buffer, length, buf, alloclen, &len); if (result != SASL_OK) { printf("[ERROR] Encoding data in base64 returned %s (%d)\n\r", sasl_errstring(result, NULL, NULL), result); return; } len = strlen(buf); buf[len] = '\n'; buf[len+1] = 0; if (write(netfd, buf, len+1) < 0) { // ERROR } free(buf); } int TDEKerberosClientSocket::getSASLDataFromNetwork(char *buf, int trunclen, bool shouldblock) { m_criticalSection++; try { unsigned int len; int result; TQByteArray ba(2048); if (!shouldblock) { if ((!TQSocket::canReadLine()) || (state() != TQSocket::Connected)) { return 0; } } len = 0; TQTimer dataTimeoutTimer; if (m_dataTimeout > 0) { dataTimeoutTimer.start(m_dataTimeout, TRUE); } while (dataTimeoutTimer.isActive() || (m_dataTimeout < 0)) { if ((shouldblock) && (dataTimeoutTimer.isActive() || (m_dataTimeout < 0))) { SAFELY_PROCESS_EVENTS } if (state() != TQSocket::Connected) { m_criticalSection--; return -1; } if (TQSocket::readBlock(ba.data()+len, 1) > 0) { if (ba.data()[len] == '\n') { ba.data()[len] = 0; break; } if (ba.data()[len] != '\r') { len++; } if (m_dataTimeout > 0) { dataTimeoutTimer.stop(); dataTimeoutTimer.start(m_dataTimeout, TRUE); } } else { if (shouldblock) { usleep(1000); } else { break; } } if (len >= (ba.size()-1)) { ba.resize(ba.size()+2048); } } len = strlen(ba.data()); result = sasl_decode64(ba.data(), strlen(ba.data()), buf, trunclen, &len); if (result != SASL_OK) { printf("[ERROR] Decoding data from base64 returned %s (%d)\n\r", sasl_errstring(result, NULL, NULL), result); m_criticalSection--; return -1; } buf[len] = '\0'; m_criticalSection--; return len; } catch(exit_exception& e) { m_criticalSection--; return -1; } } int TDEKerberosClientSocket::transmitEncryptedData(int fd, const char* readbuf, int cc) { int result = 0; unsigned int len; const char *data; long data_remaining; long remnant_position; TQTimer dataTimeoutTimer; if (m_dataTimeout > 0) { dataTimeoutTimer.start(m_dataTimeout, TRUE); } data_remaining = cc; remnant_position = 0; while ((data_remaining > 0) && (dataTimeoutTimer.isActive() || (m_dataTimeout < 0))) { int data_to_write_len; if (data_remaining > (m_negotiatedMaxBufferSize/2)) { data_to_write_len = m_negotiatedMaxBufferSize/2; } else { data_to_write_len = data_remaining; } result=sasl_encode(saslData->m_krbConnection, readbuf+remnant_position, data_to_write_len, &data, &len); if (result != SASL_OK) { printf("[ERROR] Encrypting data returned %s (%d)\n\r", safe_sasl_errdetail(saslData->m_krbConnection), result); return -1; } sendSASLDataToNetwork(data, len, fd); data_remaining = data_remaining - data_to_write_len; remnant_position = remnant_position + data_to_write_len; if ((data_remaining > 0) && (dataTimeoutTimer.isActive() || (m_dataTimeout < 0))) { SAFELY_PROCESS_EVENTS } } return 0; } int TDEKerberosClientSocket::receiveEncryptedData(char *buf, unsigned int trunclen, bool shouldblock) { unsigned int recv_len; const char *recv_data; int result; int len; char *encbuf = (char*)malloc(m_negotiatedMaxBufferSize); len = getSASLDataFromNetwork(encbuf, m_negotiatedMaxBufferSize, shouldblock); if (len < 0) { return -1; } if (len >= 0) { result=sasl_decode(saslData->m_krbConnection, encbuf, len, &recv_data, &recv_len); if (result != SASL_OK) { free(encbuf); printf("[ERROR] Decrypting data returned %s (%d)\n\r", safe_sasl_errdetail(saslData->m_krbConnection), result); return -1; } if (recv_len > trunclen) { recv_len = trunclen; } memcpy(buf, recv_data, recv_len); } free(encbuf); return recv_len; } TDEKerberosClientSocket::KerberosStatus TDEKerberosClientSocket::kerberosStatus() const { if (!m_kerberosRequested) { return KerberosNotRequested; } if (m_krbInitRunning) { return KerberosInitializing; } if (m_krbInitState < 0) { return KerberosFailure; } return KerberosInUse; } bool TDEKerberosClientSocket::canReadData() { return TQSocket::canReadLine(); } void TDEKerberosClientSocket::setStatusMessage(TQString message) { if (message != m_prevStatusMessage) { emit(statusMessageUpdated(message)); m_prevStatusMessage = message; } } void TDEKerberosClientSocket::continueKerberosInitialization() { int slen; char buf[NET_SEC_BUF_SIZE]; unsigned int len; const char *data; const char *chosenmech; sasl_ssf_t *ssf; if (m_krbInitRunning) { switch (m_krbInitState) { case 0: if (state() == TQSocket::Connected) { setStatusMessage(i18n("Waiting for mechanism list from server")); if (canReadLine()) { printf("[DEBUG] Waiting for mechanism list from server...\n\r"); slen = getSASLDataFromNetwork(buf, NET_SEC_BUF_SIZE); if (slen < 0) { m_krbInitState = -2; m_krbInitRunning = false; setStatusMessage(i18n("Kerberos connection failed")); return; } len = slen; printf("Choosing best mechanism from: %s\n", buf); m_krbInitResult = sasl_client_start(saslData->m_krbConnection, buf, NULL, &data, &len, &chosenmech); if (m_krbInitResult != SASL_OK && m_krbInitResult != SASL_CONTINUE) { printf("[ERROR] Starting SASL negotiation returned %s (%d)\n\r", sasl_errstring(m_krbInitResult, NULL, NULL), m_krbInitResult); freeKerberosConnection(); m_krbInitState = -1; m_krbInitRunning = false; setStatusMessage(i18n("Kerberos connection failed")); return; } printf("[DEBUG] Using mechanism %s\n\r", chosenmech); strcpy(buf, chosenmech); if (data) { if (NET_SEC_BUF_SIZE - strlen(buf) - 1 < len) { printf("[ERROR] Insufficient buffer space to construct initial response!\n\r"); freeKerberosConnection(); m_krbInitState = -1; m_krbInitRunning = false; setStatusMessage(i18n("Kerberos connection failed")); return; } printf("[DEBUG] Preparing initial response...\n\r"); memcpy(buf + strlen(buf) + 1, data, len); len += (unsigned) strlen(buf) + 1; data = NULL; } else { len = (unsigned) strlen(buf); } printf("[DEBUG] Sending initial response...\n\r"); sendSASLDataToNetwork(buf, len, socket()); m_krbInitState = 1; } } else { m_krbInitState = -3; m_krbInitRunning = false; } break; case 1: if (state() == TQSocket::Connected) { if (m_krbInitResult == SASL_CONTINUE) { setStatusMessage(i18n("Waiting for server reply")); if (canReadLine()) { printf("[DEBUG] Waiting for server reply...\n\r"); slen = getSASLDataFromNetwork(buf, NET_SEC_BUF_SIZE); if (slen < 0) { m_krbInitState = -2; m_krbInitRunning = false; setStatusMessage(i18n("Kerberos connection failed")); return; } len = slen; m_krbInitResult = sasl_client_step(saslData->m_krbConnection, buf, len, NULL, &data, &len); if (m_krbInitResult != SASL_OK && m_krbInitResult != SASL_CONTINUE) { printf("[ERROR] Performing SASL negotiation returned %s (%d)\n\r", sasl_errstring(m_krbInitResult, NULL, NULL), m_krbInitResult); freeKerberosConnection(); m_krbInitState = -1; m_krbInitRunning = false; setStatusMessage(i18n("Kerberos connection failed")); return; } if (data && len) { printf("[DEBUG] Sending response...\n\r"); sendSASLDataToNetwork(data, len, socket()); } else if (m_krbInitResult != SASL_OK || !m_krbInitServerLast) { sendSASLDataToNetwork("", 0, socket()); } } } else { printf("[DEBUG] Negotiation complete!\n\r"); m_krbInitState = 2; } } else { m_krbInitState = -3; m_krbInitRunning = false; setStatusMessage(i18n("Kerberos connection failed")); return; } break; case 2: if (state() == TQSocket::Connected) { m_krbInitResult = sasl_getprop(saslData->m_krbConnection, SASL_USERNAME, (const void **)&data); if (m_krbInitResult != SASL_OK) { printf("[WARNING] Unable to determine authenticated username!\n\r"); } else { printf("[DEBUG] Authenticated username: %s\n\r", data ? data : "(NULL)"); } m_krbInitResult = sasl_getprop(saslData->m_krbConnection, SASL_DEFUSERREALM, (const void **)&data); if (m_krbInitResult != SASL_OK) { printf("[WARNING] Unable to determine authenticated realm!\n\r"); } else { printf("[DEBUG] Authenticated realm: %s\n\r", data ? data : "(NULL)"); } m_krbInitResult = sasl_getprop(saslData->m_krbConnection, SASL_SSF, (const void **)&ssf); if (m_krbInitResult != SASL_OK) { printf("[WARNING] Unable to determine SSF!\n\r"); } else { printf("[DEBUG] Authenticated SSF: %d\n", *ssf); } m_krbInitResult = sasl_getprop(saslData->m_krbConnection, SASL_MAXOUTBUF, (const void **)&m_negotiatedMaxBufferSize); if (m_krbInitResult != SASL_OK) { printf("[WARNING] Unable to determine maximum buffer size!\n\r"); m_negotiatedMaxBufferSize = NET_SEC_BUF_SIZE; } else { // For some reason m_negotiatedMaxBufferSize can be set negative under certain circumstances // Prevent that from happening! if (m_negotiatedMaxBufferSize < NET_SEC_BUF_SIZE) { m_negotiatedMaxBufferSize = NET_SEC_BUF_SIZE; } printf("[DEBUG] Maximum buffer size: %d\n", m_negotiatedMaxBufferSize); } m_krbInitState = 3; m_krbInitRunning = false; setStatusMessage(i18n("Kerberos connection established")); return; } else { m_krbInitState = -3; m_krbInitRunning = false; setStatusMessage(i18n("Kerberos connection failed")); return; } break; } if (kerberosInitLoopTimer) kerberosInitLoopTimer->start(0, TRUE); } } int TDEKerberosClientSocket::initializeKerberosInterface() { if (state() != TQSocket::Connected) { saslData->m_krbConnection = false; return -1; } sasl_callback_t *callback; m_krbInitResult = 0; m_krbInitServerLast = 0; sasl_security_properties_t secprops; char *iplocal = NULL; char *ipremote = NULL; const char *service = m_serviceName.ascii(); const char *fqdn = m_serverFQDN.ascii(); callback = saslData->m_callbacks; // log callback->id = SASL_CB_LOG; callback->proc = (sasl_callback_ft)&logSASLMessages; callback->context = NULL; ++callback; // end of callback list callback->id = SASL_CB_LIST_END; callback->proc = NULL; callback->context = NULL; ++callback; // Initialize default data structures memset(&secprops, 0L, sizeof(secprops)); secprops.maxbufsize = NET_SEC_BUF_SIZE; secprops.max_ssf = UINT_MAX; m_krbInitResult = sasl_client_init(saslData->m_callbacks); if (m_krbInitResult != SASL_OK) { printf("[ERROR] Initializing libsasl returned %s (%d)\n\r", sasl_errstring(m_krbInitResult, NULL, NULL), m_krbInitResult); return -1; } m_krbInitResult = sasl_client_new(service, fqdn, iplocal, ipremote, NULL, m_krbInitServerLast, &saslData->m_krbConnection); if (m_krbInitResult != SASL_OK) { printf("[ERROR] Allocating sasl connection state returned %s (%d)\n\r", sasl_errstring(m_krbInitResult, NULL, NULL), m_krbInitResult); return -1; } m_krbInitResult = sasl_setprop(saslData->m_krbConnection, SASL_SEC_PROPS, &secprops); if (m_krbInitResult != SASL_OK) { printf("[ERROR] Setting security properties returned %s (%d)\n\r", sasl_errstring(m_krbInitResult, NULL, NULL), m_krbInitResult); freeKerberosConnection(); return -1; } m_krbInitRunning = true; m_krbInitState = 0; if (!kerberosInitLoopTimer) { kerberosInitLoopTimer = new TQTimer(); connect(kerberosInitLoopTimer, SIGNAL(timeout()), this, SLOT(continueKerberosInitialization())); } if (kerberosInitLoopTimer) kerberosInitLoopTimer->start(0, TRUE); return 0; }