/*************************************************************************** * 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 "tdekrbsocket.h" #define NET_SEC_BUF_SIZE (2048) class SASLDataPrivate { public: sasl_callback_t m_callbacks[N_CALLBACKS]; sasl_conn_t *m_krbConnection; }; 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) { saslData = new SASLDataPrivate; saslData->m_krbConnection = NULL; } TDEKerberosClientSocket::~TDEKerberosClientSocket() { delete saslData; } bool TDEKerberosClientSocket::open(int mode) { bool ret = TQSocket::open(mode); if (m_kerberosRequested) { initializeKerberosInterface(); } return ret; } void TDEKerberosClientSocket::close() { TQSocket::close(); } 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)) { ret = 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; } Q_LONG TDEKerberosClientSocket::readBlock(char *data, Q_ULONG maxlen) { Q_LONG ret = TQSocket::readBlock(data, maxlen); return ret; } Q_LONG TDEKerberosClientSocket::writeBlock(const char *data, Q_ULONG len) { Q_LONG ret = TQSocket::writeBlock(data, len); return ret; } Q_LONG TDEKerberosClientSocket::readLine(char *data, Q_ULONG maxlen) { Q_LONG ret; if (m_kerberosRequested) { ret = getSASLDataFromNetwork(data, maxlen); } else { ret = TQSocket::readLine(data, maxlen); } return ret; } TQString TDEKerberosClientSocket::readLine() { TQString ret; char buf[NET_SEC_BUF_SIZE]; if (m_kerberosRequested) { receiveEncryptedData(buf, NET_SEC_BUF_SIZE); ret = TQString(buf); } else { ret = TQSocket::readLine(); } return ret; } void TDEKerberosClientSocket::writeLine(TQString str) { if (m_kerberosRequested) { 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; char txbuf[NET_SEC_BUF_SIZE]; alloclen = ((length / 3) + 1) * 4 + 1; buf = (char*)malloc(alloclen); 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; } sprintf(txbuf, "%s\n", buf); write(netfd, txbuf, strlen(txbuf)); free(buf); } unsigned int TDEKerberosClientSocket::getSASLDataFromNetwork(char *buf, int trunclen) { unsigned int len; int result; len = 0; while (1) { tqApp->processEvents(); if (state() != TQSocket::Connected) { return -1; } if (TQSocket::readBlock(buf+len, 1) > 0) { if (buf[len] == '\n') { buf[len] = 0; break; } if (buf[len] != '\r') { len++; } } if (len >= trunclen) { break; } } len = strlen(buf); result = sasl_decode64(buf, (unsigned) strlen(buf), buf, trunclen, &len); if (result != SASL_OK) { printf("[ERROR] Decoding data from base64 returned %s (%d)\n\r", sasl_errstring(result, NULL, NULL), result); return -1; } buf[len] = '\0'; return len; } int TDEKerberosClientSocket::transmitEncryptedData(int fd, const char* readbuf, int cc) { int result = 0; unsigned int len; const char *data; result=sasl_encode(saslData->m_krbConnection, readbuf, cc, &data, &len); if (result != SASL_OK) { printf("[ERROR] Encrypting data returned %s (%d)\n\r", sasl_errdetail(saslData->m_krbConnection), result); return -1; } sendSASLDataToNetwork(data, len, fd); return 0; } int TDEKerberosClientSocket::receiveEncryptedData(char *buf, int trunclen) { unsigned int recv_len; const char *recv_data; int result; int len; len = getSASLDataFromNetwork(buf, trunclen); if (len >= 0) { result=sasl_decode(saslData->m_krbConnection, buf, len, &recv_data, &recv_len); if (result != SASL_OK) { printf("[ERROR] Decrypting data returned %s (%d)\n\r", sasl_errdetail(saslData->m_krbConnection), result); return -1; } strncpy(buf, recv_data, trunclen); } return 0; } int TDEKerberosClientSocket::initializeKerberosInterface() { if (state() != TQSocket::Connected) { saslData->m_krbConnection = false; return -1; } sasl_callback_t *callback; char buf[NET_SEC_BUF_SIZE]; int result = 0; int serverlast = 0; sasl_security_properties_t secprops; const char *chosenmech; unsigned int len; const char *data; char user_authorized = 0; sasl_ssf_t *ssf; 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; result = sasl_client_init(saslData->m_callbacks); if (result != SASL_OK) { printf("[ERROR] Initializing libsasl returned %s (%d)\n\r", sasl_errstring(result, NULL, NULL), result); return -1; } result = sasl_client_new(service, fqdn, iplocal, ipremote, NULL, serverlast, &saslData->m_krbConnection); if (result != SASL_OK) { printf("[ERROR] Allocating sasl connection state returned %s (%d)\n\r", sasl_errstring(result, NULL, NULL), result); return -1; } result = sasl_setprop(saslData->m_krbConnection, SASL_SEC_PROPS, &secprops); if (result != SASL_OK) { printf("[ERROR] Setting security properties returned %s (%d)\n\r", sasl_errstring(result, NULL, NULL), result); freeKerberosConnection(); return -1; } printf("[DEBUG] Waiting for mechanism list from server...\n\r"); len = getSASLDataFromNetwork(buf, NET_SEC_BUF_SIZE); printf("Choosing best mechanism from: %s\n", buf); result = sasl_client_start(saslData->m_krbConnection, buf, NULL, &data, &len, &chosenmech); if (result != SASL_OK && result != SASL_CONTINUE) { printf("[ERROR] Starting SASL negotiation returned %s (%d)\n\r", sasl_errstring(result, NULL, NULL), result); freeKerberosConnection(); return -1; } 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(); return -1; } 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()); while (result == SASL_CONTINUE) { printf("[DEBUG] Waiting for server reply...\n\r"); len = getSASLDataFromNetwork(buf, NET_SEC_BUF_SIZE); if (state() != TQSocket::Connected) { return -1; } result = sasl_client_step(saslData->m_krbConnection, buf, len, NULL, &data, &len); if (result != SASL_OK && result != SASL_CONTINUE) { printf("[ERROR] Performing SASL negotiation returned %s (%d)\n\r", sasl_errstring(result, NULL, NULL), result); freeKerberosConnection(); return -1; } if (data && len) { printf("[DEBUG] Sending response...\n\r"); sendSASLDataToNetwork(data, len, socket()); } else if (result != SASL_OK || !serverlast) { sendSASLDataToNetwork("", 0, socket()); } } printf("[DEBUG] Negotiation complete!\n\r"); result = sasl_getprop(saslData->m_krbConnection, SASL_USERNAME, (const void **)&data); if (result != SASL_OK) { printf("[WARNING] Unable to determine authenticated username!\n\r"); } else { printf("[DEBUG] Authenticated username: %s\n\r", data ? data : "(NULL)"); } result = sasl_getprop(saslData->m_krbConnection, SASL_DEFUSERREALM, (const void **)&data); if (result != SASL_OK) { printf("[WARNING] Unable to determine authenticated realm!\n\r"); } else { printf("[DEBUG] Authenticated realm: %s\n\r", data ? data : "(NULL)"); } result = sasl_getprop(saslData->m_krbConnection, SASL_SSF, (const void **)&ssf); if (result != SASL_OK) { printf("[WARNING] Unable to determine SSF!\n\r"); } else { printf("[DEBUG] Authenticated SSF: %d\n", *ssf); } return 0; }