From 96c14c6b7ee52d1a556d6bc0cd78caf959d66eec Mon Sep 17 00:00:00 2001 From: bju Date: Wed, 22 Jan 2014 02:04:59 +0100 Subject: http://projects.vdr-developer.org/issues/1267: - some fixes on client side certificate validation - added SSL server side support to the plugin --- vdr-vdrmanager/clientsock.cpp | 392 ++++++++++++++++++++++++++++++++++++++++++ vdr-vdrmanager/clientsock.h | 62 +++++++ vdr-vdrmanager/serversock.cpp | 148 ++++++++++++++++ vdr-vdrmanager/serversock.h | 35 ++++ 4 files changed, 637 insertions(+) create mode 100644 vdr-vdrmanager/clientsock.cpp create mode 100644 vdr-vdrmanager/clientsock.h create mode 100644 vdr-vdrmanager/serversock.cpp create mode 100644 vdr-vdrmanager/serversock.h diff --git a/vdr-vdrmanager/clientsock.cpp b/vdr-vdrmanager/clientsock.cpp new file mode 100644 index 0000000..82b12f4 --- /dev/null +++ b/vdr-vdrmanager/clientsock.cpp @@ -0,0 +1,392 @@ +/* + * extendes sockets + */ +#include +#include +#include +#include "clientsock.h" +#include "helpers.h" +#include "compressor.h" + +static int clientno = 0; + +/* + * cVdrmonClientSocket + */ +cVdrmanagerClientSocket::cVdrmanagerClientSocket(const char * password, int compressionMode) { + readbuf = ""; + writebuf = ""; + sendbuf = NULL; + sendsize = 0; + sendoffset = 0; + disconnected = false; + initDisconnect = false; + client = ++clientno; + this->password = password; + this->compressionMode = compressionMode; + login = false; + compression = false; + initCompression = false; + ssl = NULL; + sslReadWrite = SSL_NO_RETRY; + sslWantsSelect = SSL_ERROR_NONE; +} + +cVdrmanagerClientSocket::~cVdrmanagerClientSocket() { + if (ssl) { + SSL_free(ssl); + } +} + +bool cVdrmanagerClientSocket::IsLineComplete() { + // check a for complete line + string::size_type pos = readbuf.find("\r", 0); + if (pos == string::npos) + pos = readbuf.find("\n"); + return pos != string::npos; +} + +bool cVdrmanagerClientSocket::GetLine(string& line) { + // check the line + string::size_type pos = readbuf.find("\r", 0); + if (pos == string::npos) + pos = readbuf.find("\n", 0); + if (pos == string::npos) + return false; + + // extract the line ... + line = readbuf.substr(0, pos); + + // handle \r\n + if (readbuf[pos] == '\r' && readbuf.length() > pos + && readbuf[pos + 1] == '\n') + pos++; + + // ... and move the remainder + readbuf = readbuf.substr(pos + 1); + + return true; +} + +bool cVdrmanagerClientSocket::Read() { + + if (Disconnected()) + return false; + + sslReadWrite = SSL_NO_RETRY; + sslWantsSelect = SSL_ERROR_NONE; + + int rc; + for(;;) { + if (ssl) { + rc = ReadSSL(); + } else { + rc = ReadNoSSL(); + } + + // something read? + if (rc <= 0) + break; + + // command string completed? + if (readbuf.find("\n") != string::npos) + break; + } + + // command string completed + if (rc > 0) { + return true; + } + + // we must retry + if (rc == 0) { + return true; + } + + // socket closed? + if (rc == -1) { + disconnected = true; + return true; + } + + // real error + disconnected = true; + return false; +} + +int cVdrmanagerClientSocket::ReadNoSSL() { + + char buf[2001]; + int rc = read(sock, buf, sizeof(buf) - 1); + + if (rc > 0) { + // data read + buf[rc] = 0; + readbuf += buf; + return rc; + } + + // socket closed + if (rc == 0) { + return -1; + } + + if (errno == EAGAIN) { + return 0; + } + + return -2; +} + +int cVdrmanagerClientSocket::ReadSSL() { + + sslReadWrite = SSL_NO_RETRY; + sslWantsSelect = SSL_ERROR_NONE; + + bool len = 0; + char buf[2001]; + + ERR_clear_error(); + int rc = SSL_read(ssl, buf, sizeof(buf) - 1); + + if (rc > 0) { + buf[rc] = 0; + readbuf += buf; + return rc; + } + + int error = SSL_get_error(ssl, rc); + if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) { + // we must retry + sslReadWrite = SSL_RETRY_READ; + sslWantsSelect = error; + return 0; + } + + if (error == SSL_ERROR_ZERO_RETURN) { + // socket closed + return -1; + } + + // real error + long errorCode = ERR_get_error(); + char * errorText = ERR_error_string(errorCode, NULL); + esyslog("[vdrmanager] error reading from SSL (%ld) %s", errorCode, errorText); + return -2; +} + +bool cVdrmanagerClientSocket::Disconnected() { + return disconnected; +} + +void cVdrmanagerClientSocket::Disconnect() { + initDisconnect = true; +} + +void cVdrmanagerClientSocket::Write(string line) { + writebuf += line; +} + +bool cVdrmanagerClientSocket::Flush() { + + if (Disconnected()) { + return false; + } + + // initialize sendbuf if needed + if (sendbuf == NULL) { + if (!compression) { + sendbuf = (char *)malloc(writebuf.length()+1); + strcpy(sendbuf, writebuf.c_str()); + sendsize = writebuf.length(); + } else { + Compress(); + } + sendoffset = 0; + writebuf.clear(); + } + + // write so many bytes as possible + int rc; + for(;sendsize > 0;) { + if (ssl) { + rc = FlushSSL(); + } else { + rc = FlushNoSSL(); + } + if (rc <= 0) { + break; + } + sendsize -= rc; + sendoffset += rc; + } + + if (rc == 0) { + // nothing written + return true; + } + + // error + if (rc < 0) { + if (sendbuf != NULL) { + free(sendbuf); + sendbuf = NULL; + } + disconnected = true; + return false; + } + + // all data written? + if (sendsize > 0) { + return true; + } + + if (sendbuf != NULL) { + free(sendbuf); + sendbuf = NULL; + } + + if (initCompression) { + isyslog("Compression is activated now"); + initCompression = false; + compression = true; + } + + if (initDisconnect) { + initDisconnect = false; + disconnected = true; + } + + return true; +} + +int cVdrmanagerClientSocket::FlushNoSSL() { + + // write so many bytes as possible + int rc = write(sock, sendbuf + sendoffset, sendsize); + if (rc >= 0) { + return rc; + } + + if (errno == EAGAIN) { + return 0; + } + + LOG_ERROR; + return -1; +} + +int cVdrmanagerClientSocket::FlushSSL() { + + sslReadWrite = SSL_NO_RETRY; + sslWantsSelect = SSL_ERROR_NONE; + + ERR_clear_error(); + int rc = SSL_write(ssl, sendbuf + sendoffset, sendsize); + if (rc > 0) { + return rc; + } + + int error = SSL_get_error(ssl, rc); + if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) { + // we must retry after the wanted operation is possible + sslReadWrite = SSL_RETRY_WRITE; + sslWantsSelect = error; + return 0; + } + + if (error == SSL_ERROR_ZERO_RETURN) { + // the socket was closed + return -1; + } + + // real error + long errorCode = ERR_get_error(); + char * errorText = ERR_error_string(errorCode, NULL); + esyslog("[vdrmanager] error writing to SSL (%ld) %s", errorCode, errorText); + return -1; +} + +bool cVdrmanagerClientSocket::Attach(int fd, SSL_CTX * sslCtx) { + sock = fd; + if (!MakeDontBlock()) { + return false; + } + + if (sslCtx) { + ssl = SSL_new(sslCtx); + SSL_set_accept_state(ssl); + BIO *bio = BIO_new_socket(sock, BIO_NOCLOSE); + SSL_set_bio(ssl, bio, bio); + BIO_set_nbio(bio, 1); + } + + return true; +} + +int cVdrmanagerClientSocket::GetClientId() { + return client; +} + +bool cVdrmanagerClientSocket::WritePending() { + return sendsize > 0; +} + +bool cVdrmanagerClientSocket::IsLoggedIn() { + return login || !password || !*password; +} + +void cVdrmanagerClientSocket::SetLoggedIn() { + login = true; +} + +void cVdrmanagerClientSocket::ActivateCompression() { + + string mode = "NONE"; + switch (compressionMode) { + case COMPRESSION_GZIP: + mode = "GZIP"; + initCompression = true; + break; + case COMPRESSION_ZLIB: + mode = "ZLIB"; + initCompression = true; + break; + default: + mode = "NONE"; + break; + } + + Write("!OK " + mode + "\r\n"); +} + +void cVdrmanagerClientSocket::Compress() { + cCompressor compressor = cCompressor(); + + switch (compressionMode) { + case COMPRESSION_GZIP: + compressor.CompressGzip(writebuf); + break; + case COMPRESSION_ZLIB: + compressor.CompressZlib(writebuf); + break; + } + + sendbuf = compressor.GetData(); + sendsize = compressor.getDataSize(); + + double ratio = 1.0 * writebuf.length() / sendsize; + isyslog("Compression stats: raw %ld, compressed %ld, ratio %f:1", writebuf.length(), sendsize, ratio); +} + +int cVdrmanagerClientSocket::GetSslReadWrite() { + return sslReadWrite; +} + +int cVdrmanagerClientSocket::GetSslWantsSelect() { + return sslWantsSelect; +} + +bool cVdrmanagerClientSocket::IsSSL() { + return ssl != NULL; +} diff --git a/vdr-vdrmanager/clientsock.h b/vdr-vdrmanager/clientsock.h new file mode 100644 index 0000000..d921650 --- /dev/null +++ b/vdr-vdrmanager/clientsock.h @@ -0,0 +1,62 @@ +/* + * extendes sockets + */ + +#ifndef _VDRMON_CLIENTSOCK +#define _VDRMON_CLIENTSOCK + +#include +#include +#include +#include +#include + +#include "sock.h" + +using namespace std; + +class cVdrmanagerClientSocket : public cVdrmanagerSocket +{ +private: + string readbuf; + string writebuf; + char * sendbuf; + size_t sendsize; + size_t sendoffset; + bool disconnected; + bool initDisconnect; + int client; + bool login; + bool compression; + bool initCompression; + int compressionMode; + SSL * ssl; + int sslReadWrite; + int sslWantsSelect; +public: + cVdrmanagerClientSocket(const char * password, int compressionMode); + virtual ~cVdrmanagerClientSocket(); + bool Attach(int fd, SSL_CTX * sslCtx); + bool IsLineComplete(); + bool GetLine(string& line); + void Write(string line); + bool Read(); + int ReadNoSSL(); + int ReadSSL(); + bool Disconnected(); + void Disconnect(); + bool Flush(); + int FlushNoSSL(); + int FlushSSL(); + int GetClientId(); + bool WritePending(); + bool IsLoggedIn(); + void SetLoggedIn(); + void ActivateCompression(); + void Compress(); + int GetSslReadWrite(); + int GetSslWantsSelect(); + bool IsSSL(); +}; + +#endif diff --git a/vdr-vdrmanager/serversock.cpp b/vdr-vdrmanager/serversock.cpp new file mode 100644 index 0000000..927a788 --- /dev/null +++ b/vdr-vdrmanager/serversock.cpp @@ -0,0 +1,148 @@ +/* + * extendes sockets + */ +#include +#include +#include +#include "serversock.h" +#include "helpers.h" +#include "compressor.h" + +static int clientno = 0; + +/* + * cVdrmonServerSocket + */ +cVdrmanagerServerSocket::cVdrmanagerServerSocket() : cVdrmanagerSocket() { + port = -1; + sslCtx = NULL; +} + +cVdrmanagerServerSocket::~cVdrmanagerServerSocket() { + if (sslCtx) + SSL_CTX_free(sslCtx); +} + +bool cVdrmanagerServerSocket::Create(int port, const char * password, bool forceCheckSvrp, int compressionMode, + const char * certFile, const char * keyFile) { + + this->port = port; + this->password = password; + this->forceCheckSvdrp = forceCheckSvrp; + this->compressionMode = compressionMode; + + // create socket + sock = socket(PF_INET, SOCK_STREAM, 0); + if (sock < 0) { + LOG_ERROR; + return false; + } + + isyslog("[vdrmanager] created %sSSL server socket on port %d", certFile ? "" : "non ", port); + + // allow it to always reuse the same port: + int ReUseAddr = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &ReUseAddr, sizeof(ReUseAddr)); + + // bind to address + struct sockaddr_in name; + name.sin_family = AF_INET; + name.sin_port = htons(port); + name.sin_addr.s_addr = htonl(INADDR_ANY); + if (bind(sock, (struct sockaddr *) &name, sizeof(name)) < 0) { + LOG_ERROR; + Close(); + return false; + } + + // make it non-blocking: + if (!MakeDontBlock()) { + Close(); + return false; + } + + // listen to the socket: + if (listen(sock, 100) < 0) { + LOG_ERROR; + Close(); + return false; + } + + if (certFile) { + isyslog("[vdrmanager] initialize SSL"); + + OpenSSL_add_all_algorithms(); + OpenSSL_add_all_ciphers(); + OpenSSL_add_all_digests(); + OpenSSL_add_ssl_algorithms(); + + SSL_load_error_strings(); + SSL_library_init(); + + const SSL_METHOD * method = SSLv23_server_method(); + sslCtx = SSL_CTX_new(method); + if (sslCtx == NULL) { + long errorCode = ERR_get_error(); + char * error = ERR_error_string(errorCode, NULL); + esyslog("[vdrmanager] Error initializing SSL context: %s", error); + Close(); + return false; + } + + /* set the local certificate from CertFile */ + SSL_CTX_use_certificate_file(sslCtx, certFile, SSL_FILETYPE_PEM); + /* set the private key from KeyFile */ + SSL_CTX_use_PrivateKey_file(sslCtx, keyFile, SSL_FILETYPE_PEM); + /* verify private key */ + if (!SSL_CTX_check_private_key(sslCtx)) { + long errorCode = ERR_get_error(); + char * error = ERR_error_string(errorCode, NULL); + esyslog("[vdrmanager] Error checking SSL keys: %s", error); + Close(); + return false; + } + + SSL_CTX_set_mode(sslCtx, SSL_MODE_ENABLE_PARTIAL_WRITE); + } + + return true; +} + +cVdrmanagerClientSocket * cVdrmanagerServerSocket::Accept() { + cVdrmanagerClientSocket * newsocket = NULL; + + isyslog("[vdrmanager] new %sclient on port %d", sslCtx ? "SSL " : "", port); + + // accept the connection + struct sockaddr_in clientname; + uint size = sizeof(clientname); + int newsock = accept(sock, (struct sockaddr *) &clientname, &size); + if (newsock > 0) { + // create client socket + newsocket = new cVdrmanagerClientSocket(password, compressionMode); + if (!newsocket->Attach(newsock, sslCtx)) { + delete newsocket; + return NULL; + } + + if (!IsPasswordSet() || forceCheckSvdrp == true) { + bool accepted = SVDRPhosts.Acceptable(clientname.sin_addr.s_addr); + if (!accepted) { + newsocket->Write(string("NACC Access denied.\n")); + newsocket->Flush(); + delete newsocket; + newsocket = NULL; + } + dsyslog( + "[vdrmanager] connect from %s, port %hd - %s", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port), accepted ? "accepted" : "DENIED"); + } + } else if (errno != EINTR && errno != EAGAIN + ) + LOG_ERROR; + + return newsocket; +} + +int cVdrmanagerServerSocket::GetPort() { + return port; +} diff --git a/vdr-vdrmanager/serversock.h b/vdr-vdrmanager/serversock.h new file mode 100644 index 0000000..53eec62 --- /dev/null +++ b/vdr-vdrmanager/serversock.h @@ -0,0 +1,35 @@ +/* + * extendes sockets + */ + +#ifndef _VDRMON_SERVERSOCK +#define _VDRMON_SERVERSOCK + +#include +#include +#include +#include +#include + +#include "clientsock.h" + +#define SSL_NO_RETRY 0 +#define SSL_RETRY_READ 1 +#define SSL_RETRY_WRITE 2 + +using namespace std; + +class cVdrmanagerServerSocket : public cVdrmanagerSocket +{ +private: + int port; + SSL_CTX * sslCtx; +public: + cVdrmanagerServerSocket(); + virtual ~cVdrmanagerServerSocket(); + bool Create(int port, const char * password, bool forceCheckSvdrp, int compressionMode, const char * certFile, const char * keyFile); + cVdrmanagerClientSocket * Accept(); + int GetPort(); +}; + +#endif -- cgit v1.2.3