/* * extendes sockets */ #include #include #include #include #include "sock.h" #include "helpers.h" static int clientno = 0; /* * cVdrmonSocket */ cVdrmanagerSocket::cVdrmanagerSocket() { sock = -1; useSSL = false; password = ""; forceCheckSvdrp = false; } cVdrmanagerSocket::~cVdrmanagerSocket() { Close(); } void cVdrmanagerSocket::Close() { if (sock >= 0) { close(sock); sock = -1; } } int cVdrmanagerSocket::GetSocket() { return sock; } bool cVdrmanagerSocket::MakeDontBlock() { // make it non-blocking: int oldflags = fcntl(sock, F_GETFL, 0); if (oldflags < 0) { LOG_ERROR; return false; } oldflags |= O_NONBLOCK; if (fcntl(sock, F_SETFL, oldflags) < 0) { LOG_ERROR; return false; } return true; } const char * cVdrmanagerSocket::GetPassword() { return password; } void cVdrmanagerSocket::LogSSLError() { char * error = ERR_error_string(ERR_get_error(), NULL); esyslog("SSL error: %s", error); } /* * cVdrmonServerSocket */ cVdrmanagerServerSocket::cVdrmanagerServerSocket() : cVdrmanagerSocket() { sslContext = NULL; } cVdrmanagerServerSocket::~cVdrmanagerServerSocket() { } bool cVdrmanagerServerSocket::Create(int port, const char * password, bool forceCheckSvrp, bool useSSL, const char * pemFile) { this->password = password; this->forceCheckSvdrp = forceCheckSvrp; this->useSSL = useSSL; // create SSL context if (useSSL && !InitSSL(pemFile)) { return false; } // create socket sock = socket(PF_INET, SOCK_STREAM, 0); if (sock < 0) { LOG_ERROR; return false; } // 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; } return true; } cVdrmanagerClientSocket * cVdrmanagerServerSocket::Accept() { cVdrmanagerClientSocket * newsocket = NULL; // 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); if (!newsocket->Attach(newsock)) { delete newsocket; return NULL; } // Attach client SSL if (!newsocket->InitSSL(sslContext)) { delete newsocket; return NULL; } if (!IsPasswordSet() || forceCheckSvdrp == true) { bool accepted = SVDRPhosts.Acceptable(clientname.sin_addr.s_addr); if (!accepted) { newsocket->PutLine(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; } bool cVdrmanagerServerSocket::InitSSL(const char * pemFile) { sslContext = SSL_CTX_new(SSLv3_server_method()); if (sslContext == NULL) { LogSSLError(); return false; } if (SSL_CTX_use_certificate_file(sslContext, pemFile, SSL_FILETYPE_PEM) != 1) { LogSSLError(); return false; } if (SSL_CTX_use_PrivateKey_file(sslContext, pemFile, SSL_FILETYPE_PEM) != 1) { LogSSLError(); return false; } return true; } /* * cVdrmonClientSocket */ cVdrmanagerClientSocket::cVdrmanagerClientSocket(const char * password) { readbuf = writebuf = ""; disconnected = false; client = ++clientno; this->password = password; login = false; } cVdrmanagerClientSocket::~cVdrmanagerClientSocket() { if (sslContext) { SSL_free(sslContext); } } 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 cVdrmanagerSocket::IsPasswordSet(){ return strcmp(password, ""); } 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; int rc; bool len = 0; char buf[2001]; for(;;) { if (useSSL) { rc = read(sock, buf, sizeof(buf)-1); } else { rc = SSL_read(sslContext, buf, sizeof(buf)-1); } if (rc <= 0) break; buf[rc] = 0; readbuf += buf; len += rc; } if (rc < 0 && errno != EAGAIN) { LOG_ERROR; return false; } else if (rc == 0) { disconnected = true; } return len > 0; } bool cVdrmanagerClientSocket::Disconnected() { return disconnected; } void cVdrmanagerClientSocket::Disconnect() { disconnected = true; } bool cVdrmanagerClientSocket::PutLine(string line) { //TODO http://projects.vdr-developer.org/issues/790 //string line2 = cHelpers::compress_string(line); //unsigned long l = line.size(); //unsigned long l2 = line2.size(); //if(l2 == 0){ //l2 = 1; //} //dsyslog("[vdrmanager] PutLine, line size is %lu, with zlib it would be %lu (factor %lu)", l, l2, l/l2); // add line to write buffer writebuf += line; // data present? if (writebuf.length() > 0) { int rc; if (useSSL) { rc = SSL_write(sslContext, writebuf.c_str(), writebuf.length()); } else { rc = write(sock, writebuf.c_str(), writebuf.length()); } if (rc < 0 && errno != EAGAIN) { LOG_ERROR; return false; } // move the remainder if (rc > 0) writebuf = writebuf.substr(rc, writebuf.length() - rc); } return true; } bool cVdrmanagerClientSocket::Flush() { string empty = ""; return PutLine(empty); } bool cVdrmanagerClientSocket::Attach(int fd) { sock = fd; return MakeDontBlock(); } int cVdrmanagerClientSocket::GetClientId() { return client; } bool cVdrmanagerClientSocket::WritePending() { return writebuf.length() > 0; } bool cVdrmanagerClientSocket::IsLoggedIn() { return login || !password || !*password; } void cVdrmanagerClientSocket::SetLoggedIn() { login = true; } bool::cVdrmanagerClientSocket::InitSSL(SSL_CTX * sslContext) { // create a new SSL context this->sslContext = SSL_new(sslContext); if (this->sslContext == NULL) { LOG_ERROR_STR("Error creating new SSL context"); return false; } // connect context to the socket if (SSL_set_fd(this->sslContext, sock) != 1) { SSL_free(this->sslContext); this->sslContext = NULL; LOG_ERROR_STR("Error connecting SSL and socket"); return false; } }