summaryrefslogtreecommitdiff
path: root/vdr-vdrmanager
diff options
context:
space:
mode:
authorbju <bju@maxi.fritz.box>2014-01-22 02:04:59 +0100
committerbju <bju@maxi.fritz.box>2014-01-22 02:04:59 +0100
commit96c14c6b7ee52d1a556d6bc0cd78caf959d66eec (patch)
treeffce3289cb334dfc3bc32a7fc6811af9050cadbd /vdr-vdrmanager
parent2676b99b3a7cae3ea8ebe827ac20684a90a84d07 (diff)
downloadvdr-manager-96c14c6b7ee52d1a556d6bc0cd78caf959d66eec.tar.gz
vdr-manager-96c14c6b7ee52d1a556d6bc0cd78caf959d66eec.tar.bz2
http://projects.vdr-developer.org/issues/1267:
- some fixes on client side certificate validation - added SSL server side support to the plugin
Diffstat (limited to 'vdr-vdrmanager')
-rw-r--r--vdr-vdrmanager/clientsock.cpp392
-rw-r--r--vdr-vdrmanager/clientsock.h62
-rw-r--r--vdr-vdrmanager/serversock.cpp148
-rw-r--r--vdr-vdrmanager/serversock.h35
4 files changed, 637 insertions, 0 deletions
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 <unistd.h>
+#include <vdr/plugin.h>
+#include <openssl/err.h>
+#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 <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <openssl/ssl.h>
+#include <string>
+
+#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 <unistd.h>
+#include <vdr/plugin.h>
+#include <openssl/err.h>
+#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 <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <openssl/ssl.h>
+#include <string>
+
+#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