tigervnc/network/TcpSocket.cxx
Constantin Kaplinsky 47ed8d321c Initial revision
git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@2 3789f03b-4d11-0410-bbf8-ca57d06f2519
2004-10-08 09:43:57 +00:00

459 lines
11 KiB
C++

/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved.
*
* This 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 software 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 software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#ifdef WIN32
//#include <io.h>
#include <winsock2.h>
#define errorNumber WSAGetLastError()
#define snprintf _snprintf
#else
#define errorNumber errno
#define closesocket close
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#endif
#include <network/TcpSocket.h>
#include <rfb/util.h>
#include <rfb/LogWriter.h>
#ifndef VNC_SOCKLEN_T
#define VNC_SOCKLEN_T int
#endif
#ifndef INADDR_NONE
#define INADDR_NONE ((unsigned long)-1)
#endif
using namespace network;
using namespace rdr;
static rfb::LogWriter vlog("TcpSocket");
void
TcpSocket::initTcpSockets() {
#ifdef WIN32
WORD requiredVersion = MAKEWORD(2,0);
WSADATA initResult;
if (WSAStartup(requiredVersion, &initResult) != 0)
throw SocketException("unable to initialise Winsock2", errorNumber);
#else
signal(SIGPIPE, SIG_IGN);
#endif
}
// -=- TcpSocket
TcpSocket::TcpSocket(int sock, bool close)
: Socket(new FdInStream(sock), new FdOutStream(sock), true), closeFd(close)
{
}
TcpSocket::TcpSocket(const char *host, int port)
: closeFd(true)
{
int sock;
// - Create a socket
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
throw SocketException("unable to create socket", errorNumber);
#ifndef WIN32
// - By default, close the socket on exec()
fcntl(sock, F_SETFD, FD_CLOEXEC);
#endif
// - Connect it to something
// Try processing the host as an IP address
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(host);
addr.sin_port = htons(port);
if ((int)addr.sin_addr.s_addr == -1) {
// Host was not an IP address - try resolving as DNS name
struct hostent *hostinfo;
hostinfo = gethostbyname(host);
if (hostinfo && hostinfo->h_addr) {
addr.sin_addr.s_addr = ((struct in_addr *)hostinfo->h_addr)->s_addr;
} else {
int e = errorNumber;
closesocket(sock);
throw SocketException("unable to resolve host by name", e);
}
}
// Attempt to connect to the remote host
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
int e = errorNumber;
closesocket(sock);
throw SocketException("unable to connect to host", e);
}
int one = 1;
if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
(char *)&one, sizeof(one)) < 0) {
int e = errorNumber;
closesocket(sock);
throw SocketException("unable to setsockopt TCP_NODELAY", e);
}
// Create the input and output streams
instream = new FdInStream(sock);
outstream = new FdOutStream(sock);
own_streams = true;
}
TcpSocket::~TcpSocket() {
if (closeFd)
closesocket(getFd());
}
char* TcpSocket::getMyAddress() {
struct sockaddr_in info;
struct in_addr addr;
VNC_SOCKLEN_T info_size = sizeof(info);
getsockname(getFd(), (struct sockaddr *)&info, &info_size);
memcpy(&addr, &info.sin_addr, sizeof(addr));
char* name = inet_ntoa(addr);
if (name) {
return rfb::strDup(name);
} else {
return rfb::strDup("");
}
}
int TcpSocket::getMyPort() {
return getSockPort(getFd());
}
char* TcpSocket::getMyEndpoint() {
rfb::CharArray address; address.buf = getMyAddress();
int port = getMyPort();
int buflen = strlen(address.buf) + 32;
char* buffer = new char[buflen];
sprintf(buffer, "%s::%d", address.buf, port);
return buffer;
}
char* TcpSocket::getPeerAddress() {
struct sockaddr_in info;
struct in_addr addr;
VNC_SOCKLEN_T info_size = sizeof(info);
getpeername(getFd(), (struct sockaddr *)&info, &info_size);
memcpy(&addr, &info.sin_addr, sizeof(addr));
char* name = inet_ntoa(addr);
if (name) {
return rfb::strDup(name);
} else {
return rfb::strDup("");
}
}
int TcpSocket::getPeerPort() {
struct sockaddr_in info;
VNC_SOCKLEN_T info_size = sizeof(info);
getpeername(getFd(), (struct sockaddr *)&info, &info_size);
return ntohs(info.sin_port);
}
char* TcpSocket::getPeerEndpoint() {
rfb::CharArray address; address.buf = getPeerAddress();
int port = getPeerPort();
int buflen = strlen(address.buf) + 32;
char* buffer = new char[buflen];
sprintf(buffer, "%s::%d", address.buf, port);
return buffer;
}
bool TcpSocket::sameMachine() {
struct sockaddr_in peeraddr, myaddr;
VNC_SOCKLEN_T addrlen = sizeof(struct sockaddr_in);
getpeername(getFd(), (struct sockaddr *)&peeraddr, &addrlen);
getsockname(getFd(), (struct sockaddr *)&myaddr, &addrlen);
return (peeraddr.sin_addr.s_addr == myaddr.sin_addr.s_addr);
}
void TcpSocket::shutdown()
{
::shutdown(getFd(), 2);
}
bool TcpSocket::isSocket(int sock)
{
struct sockaddr_in info;
VNC_SOCKLEN_T info_size = sizeof(info);
return getsockname(sock, (struct sockaddr *)&info, &info_size) >= 0;
}
bool TcpSocket::isConnected(int sock)
{
struct sockaddr_in info;
VNC_SOCKLEN_T info_size = sizeof(info);
return getpeername(sock, (struct sockaddr *)&info, &info_size) >= 0;
}
int TcpSocket::getSockPort(int sock)
{
struct sockaddr_in info;
VNC_SOCKLEN_T info_size = sizeof(info);
if (getsockname(sock, (struct sockaddr *)&info, &info_size) < 0)
return 0;
return ntohs(info.sin_port);
}
TcpListener::TcpListener(int port, bool localhostOnly, int sock, bool close_)
: closeFd(close_)
{
if (sock != -1) {
fd = sock;
return;
}
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
throw SocketException("unable to create listening socket", errorNumber);
#ifndef WIN32
// - By default, close the socket on exec()
fcntl(sock, F_SETFD, FD_CLOEXEC);
int one = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
(const char *)&one, sizeof(one)) < 0) {
int e = errorNumber;
closesocket(fd);
throw SocketException("unable to create listening socket", e);
}
#endif
// - Bind it to the desired port
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (localhostOnly)
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
else
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
int e = errorNumber;
closesocket(fd);
throw SocketException("unable to bind listening socket", e);
}
// - Set it to be a listening socket
if (listen(fd, 5) < 0) {
int e = errorNumber;
closesocket(fd);
throw SocketException("unable to set socket to listening mode", e);
}
}
TcpListener::~TcpListener() {
if (closeFd) closesocket(fd);
}
void TcpListener::shutdown()
{
#ifdef WIN32
closesocket(getFd());
#else
::shutdown(getFd(), 2);
#endif
}
Socket*
TcpListener::accept() {
int new_sock = -1;
// Accept an incoming connection
if ((new_sock = ::accept(fd, 0, 0)) < 0)
throw SocketException("unable to accept new connection", errorNumber);
#ifndef WIN32
// - By default, close the socket on exec()
fcntl(new_sock, F_SETFD, FD_CLOEXEC);
#endif
// Disable Nagle's algorithm
int one = 1;
if (setsockopt(new_sock, IPPROTO_TCP, TCP_NODELAY,
(char *)&one, sizeof(one)) < 0) {
int e = errorNumber;
closesocket(new_sock);
throw SocketException("unable to setsockopt TCP_NODELAY", e);
}
// Create the socket object & check connection is allowed
TcpSocket* s = new TcpSocket(new_sock);
if (filter && !filter->verifyConnection(s)) {
delete s;
return 0;
}
return s;
}
void TcpListener::getMyAddresses(std::list<char*>* result) {
const hostent* addrs = gethostbyname(0);
if (addrs == 0)
throw rdr::SystemException("gethostbyname", errorNumber);
if (addrs->h_addrtype != AF_INET)
throw rdr::Exception("getMyAddresses: bad family");
for (int i=0; addrs->h_addr_list[i] != 0; i++) {
const char* addrC = inet_ntoa(*((struct in_addr*)addrs->h_addr_list[i]));
char* addr = new char[strlen(addrC)+1];
strcpy(addr, addrC);
result->push_back(addr);
}
}
int TcpListener::getMyPort() {
return TcpSocket::getSockPort(getFd());
}
TcpFilter::TcpFilter(const char* spec) {
rfb::CharArray tmp;
tmp.buf = rfb::strDup(spec);
while (tmp.buf) {
rfb::CharArray first;
rfb::strSplit(tmp.buf, ',', &first.buf, &tmp.buf);
if (strlen(first.buf))
filter.push_back(parsePattern(first.buf));
}
}
TcpFilter::~TcpFilter() {
}
static bool
patternMatchIP(const TcpFilter::Pattern& pattern, const char* value) {
unsigned long address = inet_addr(value);
if (address == INADDR_NONE) return false;
return ((pattern.address & pattern.mask) == (address & pattern.mask));
}
bool
TcpFilter::verifyConnection(Socket* s) {
rfb::CharArray name;
name.buf = s->getPeerAddress();
std::list<TcpFilter::Pattern>::iterator i;
for (i=filter.begin(); i!=filter.end(); i++) {
if (patternMatchIP(*i, name.buf)) {
switch ((*i).action) {
case Accept:
vlog.debug("ACCEPT %s", name.buf);
return true;
case Query:
vlog.debug("QUERY %s", name.buf);
return queryUserAcceptConnection(s);
case Reject:
vlog.debug("REJECT %s", name.buf);
return false;
}
}
}
vlog.debug("[REJECT] %s", name.buf);
return false;
}
TcpFilter::Pattern TcpFilter::parsePattern(const char* p) {
TcpFilter::Pattern pattern;
bool expandMask = false;
rfb::CharArray addr, mask;
if (rfb::strSplit(&p[1], '/', &addr.buf, &mask.buf)) {
if (rfb::strContains(mask.buf, '.')) {
pattern.mask = inet_addr(mask.buf);
} else {
pattern.mask = atoi(mask.buf);
expandMask = true;
}
} else {
pattern.mask = 32;
expandMask = true;
}
if (expandMask) {
unsigned long expanded = 0;
// *** check endianness!
for (int i=0; i<(int)pattern.mask; i++)
expanded |= 1<<(31-i);
pattern.mask = htonl(expanded);
}
pattern.address = inet_addr(addr.buf) & pattern.mask;
if ((pattern.address == INADDR_NONE) ||
(pattern.address == 0)) pattern.mask = 0;
switch(p[0]) {
case '+': pattern.action = TcpFilter::Accept; break;
case '-': pattern.action = TcpFilter::Reject; break;
case '?': pattern.action = TcpFilter::Query; break;
};
return pattern;
}
char* TcpFilter::patternToStr(const TcpFilter::Pattern& p) {
in_addr tmp;
rfb::CharArray addr, mask;
tmp.s_addr = p.address;
addr.buf = rfb::strDup(inet_ntoa(tmp));
tmp.s_addr = p.mask;
mask.buf = rfb::strDup(inet_ntoa(tmp));
char* result = new char[strlen(addr.buf)+1+strlen(mask.buf)+1+1];
switch (p.action) {
case Accept: result[0] = '+'; break;
case Reject: result[0] = '-'; break;
case Query: result[0] = '?'; break;
};
result[1] = 0;
strcat(result, addr.buf);
strcat(result, "/");
strcat(result, mask.buf);
return result;
}