diff options
21 files changed, 1814 insertions, 307 deletions
diff --git a/java/CMakeLists.txt b/java/CMakeLists.txt index f0a884b8..f8364fba 100644 --- a/java/CMakeLists.txt +++ b/java/CMakeLists.txt @@ -44,6 +44,7 @@ endforeach() file(GLOB DEPEND_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/com/tigervnc/rfb/*.java ${CMAKE_CURRENT_SOURCE_DIR}/com/tigervnc/rdr/*.java + ${CMAKE_CURRENT_SOURCE_DIR}/com/tigervnc/network/*.java ${CMAKE_CURRENT_SOURCE_DIR}/com/jcraft/jzlib/*.java) string(REGEX REPLACE " " ";" JAVACFLAGS "${JAVACFLAGS}") @@ -81,6 +82,7 @@ add_custom_command(OUTPUT VncViewer.jar com/tigervnc/vncviewer/*.class com/tigervnc/rfb/*.class com/tigervnc/rdr/*.class + com/tigervnc/network/*.class com/jcraft/jzlib/*.class com/tigervnc/vncviewer/tigervnc.png com/tigervnc/vncviewer/tigervnc.ico diff --git a/java/com/tigervnc/network/FileDescriptor.java b/java/com/tigervnc/network/FileDescriptor.java new file mode 100644 index 00000000..e2d04fab --- /dev/null +++ b/java/com/tigervnc/network/FileDescriptor.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012 TigerVNC Team. + * + * 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. + */ +package com.tigervnc.network; + +import java.lang.Exception; +import java.io.IOException; + +public interface FileDescriptor { + + public int read(byte[] buf, int bufPtr, int length) throws java.lang.Exception; + public int write(byte[] buf, int bufPtr, int length) throws java.lang.Exception; + public int select(int interestOps, int timeout) throws java.lang.Exception; + public void close() throws IOException; + +} diff --git a/java/com/tigervnc/network/SSLEngineManager.java b/java/com/tigervnc/network/SSLEngineManager.java new file mode 100644 index 00000000..0f5ad66a --- /dev/null +++ b/java/com/tigervnc/network/SSLEngineManager.java @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2004, 2006, Oracle and/or its affiliates. All rights reserved. + * Copyright (C) 2008 Trustin Heuiseung Lee + * Copyright (C) 2012 TigerVNC Team + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA + */ + package com.tigervnc.network; + +import java.io.*; +import java.nio.*; +import java.nio.channels.*; +import javax.net.ssl.*; +import javax.net.ssl.SSLEngineResult.*; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +import com.tigervnc.rdr.FdInStream; +import com.tigervnc.rdr.FdOutStream; + +public class SSLEngineManager { + + private SSLEngine engine = null; + + private int applicationBufferSize; + private int packetBufferSize; + + private ByteBuffer myAppData; + private ByteBuffer myNetData; + private ByteBuffer peerAppData; + private ByteBuffer peerNetData; + + + private Executor executor; + private FdInStream inStream; + private FdOutStream outStream; + + public SSLEngineManager(SSLEngine sslEngine, FdInStream is, + FdOutStream os) throws IOException { + + inStream = is; + outStream = os; + engine = sslEngine; + + executor = Executors.newSingleThreadExecutor(); + + packetBufferSize = engine.getSession().getPacketBufferSize(); + applicationBufferSize = engine.getSession().getApplicationBufferSize(); + + myAppData = ByteBuffer.allocate(applicationBufferSize); + myNetData = ByteBuffer.allocate(packetBufferSize); + peerAppData = ByteBuffer.allocate(applicationBufferSize); + peerNetData = ByteBuffer.allocate(packetBufferSize); + } + + public void doHandshake() throws Exception { + + // Begin handshake + engine.beginHandshake(); + SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus(); + + // Process handshaking message + while (hs != SSLEngineResult.HandshakeStatus.FINISHED && + hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { + + switch (hs) { + + case NEED_UNWRAP: + // Receive handshaking data from peer + pull(peerNetData); + //if (pull(peerNetData) < 0) { + // Handle closed channel + //} + + // Process incoming handshaking data + peerNetData.flip(); + SSLEngineResult res = engine.unwrap(peerNetData, peerAppData); + peerNetData.compact(); + hs = res.getHandshakeStatus(); + + // Check status + switch (res.getStatus()) { + case OK : + // Handle OK status + break; + + // Handle other status: BUFFER_UNDERFLOW, BUFFER_OVERFLOW, CLOSED + //... + } + break; + + case NEED_WRAP : + // Empty the local network packet buffer. + myNetData.clear(); + + // Generate handshaking data + res = engine.wrap(myAppData, myNetData); + hs = res.getHandshakeStatus(); + + // Check status + switch (res.getStatus()) { + case OK : + //myNetData.flip(); + + push(myNetData); + // Send the handshaking data to peer + //while (myNetData.hasRemaining()) { + // if (push(myNetData) < 0) { + // // Handle closed channel + // } + //} + break; + + // Handle other status: BUFFER_OVERFLOW, BUFFER_UNDERFLOW, CLOSED + //... + } + break; + + case NEED_TASK : + // Handle blocking tasks + executeTasks(); + break; + + // Handle other status: // FINISHED or NOT_HANDSHAKING + //... + } + hs = engine.getHandshakeStatus(); + } + + // Processes after handshaking + //... +} + + private void executeTasks() { + Runnable task; + while ((task = engine.getDelegatedTask()) != null) { + executor.execute(task); + } + } + + public int read(byte[] data, int dataPtr, int length) throws IOException { + // Read SSL/TLS encoded data from peer + int bytesRead = 0; + SSLEngineResult res; + do { + // Process incoming data + int packetLength = pull(peerNetData); + peerNetData.flip(); + res = engine.unwrap(peerNetData, peerAppData); + peerNetData.compact(); + bytesRead += packetLength; + } while (res.getStatus() != SSLEngineResult.Status.OK); + peerAppData.flip(); + int n = Math.min(peerAppData.remaining(), length); + peerAppData.get(data, dataPtr, n); + peerAppData.compact(); + return n; + } + + public int write(byte[] data, int dataPtr, int length) throws IOException { + int n = 0; +//while (myAppData.hasRemaining()) { + // Generate SSL/TLS encoded data (handshake or application data) + // FIXME: + // Need to make sure myAppData has space for data! + myAppData.put(data, dataPtr, length); + myAppData.flip(); + SSLEngineResult res = engine.wrap(myAppData, myNetData); + myAppData.compact(); + + // Process status of call + //if (res.getStatus() == SSLEngineResult.Status.OK) { + //myAppData.compact(); + + // Send SSL/TLS encoded data to peer + //while(myNetData.hasRemaining()) { + int num = push(myNetData); + if (num == -1) { + // handle closed channel + } else if (num == 0) { + // no bytes written; try again later + } + //n += num; + //} + //} + + // Handle other status: BUFFER_OVERFLOW, CLOSED + //... + //} + return num; + } + + private int push(ByteBuffer src) throws IOException { + src.flip(); + int n = src.remaining(); + byte[] b = new byte[n]; + src.get(b); + src.clear(); + outStream.writeBytes(b, 0, n); + outStream.flush(); + return n; + } + + private int pull(ByteBuffer dst) throws IOException { + int packetLength = 0; + inStream.checkNoWait(5); + //if (!inStream.checkNoWait(5)) { + // return 0; + //} + + byte[] header = new byte[5]; + inStream.readBytes(header, 0, 5); + + boolean tls; + int h = header[0] & 0xFF; + switch (header[0] & 0xFF) { + case 20: // change_cipher_spec + case 21: // alert + case 22: // handshake + case 23: // application_data + tls = true; + break; + default: + // SSLv2 bad data + tls = false; + } + + if (tls) { + int majorVersion = (int)(header[1] & 0xFF); + if (majorVersion >= 3 && majorVersion < 10) { + // SSLv3 or TLS + packetLength = (int)(((header[3] << 8) | (header[4] & 0xFF)) & 0xFFFF) + 5; + if (packetLength <= 5) { + // Neither SSLv2 or TLSv1 + tls = false; + } + } else { + // Neither SSLv2 or TLSv1 + tls = false; + } + } + + if (!tls) { + boolean sslv2 = true; + int headerLength = (int)((header[0] & 0xFF) & 0x80) != 0 ? 2 : 3; + int majorVersion = (int)(header[headerLength + 1] & 0xFF); + if (majorVersion >= 2 && majorVersion < 10) { + // SSLv2 + if (headerLength == 2) { + packetLength = (int)(((header[0] << 8) | (header[1] & 0xFF)) & 0x7FFF) + 2; + } else { + packetLength = (int)(((header[0] << 8) | (header[1] & 0xFF)) & 0x3FFF) + 3; + } + if (packetLength <= headerLength) { + sslv2 = false; + } + } else { + sslv2 = false; + } + + if (!sslv2) { + throw new IOException("not an SSL/TLS record"); + } + } + + assert packetLength > 0; + + byte[] buf = new byte[packetLength - 5]; + inStream.readBytes(buf, 0, packetLength - 5); + dst.put(header); + dst.put(buf); + return packetLength; + } + + public SSLSession getSession() { + return engine.getSession(); + } + +} diff --git a/java/com/tigervnc/network/Socket.java b/java/com/tigervnc/network/Socket.java new file mode 100644 index 00000000..212d130d --- /dev/null +++ b/java/com/tigervnc/network/Socket.java @@ -0,0 +1,109 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright (C) 2012 TigerVNC Team + * + * 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. + */ + +// -=- Socket - abstract base-class for any kind of network stream/socket + +package com.tigervnc.network; + +import com.tigervnc.rdr.*; +import java.nio.channels.*; +import java.nio.channels.spi.SelectorProvider; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +abstract public class Socket { + + public Socket(FileDescriptor fd) { + instream = new FdInStream(fd); + outstream = new FdOutStream(fd); + ownStreams = true; isShutdown_ = false; + queryConnection = false; + } + + public FdInStream inStream() {return instream;} + public FdOutStream outStream() {return outstream;} + public FileDescriptor getFd() {return outstream.getFd();} + + // if shutdown() is overridden then the override MUST call on to here + public void shutdown() {isShutdown_ = true;} + public void close() throws IOException {getFd().close();} + public final boolean isShutdown() {return isShutdown_;} + + // information about this end of the socket + abstract public int getMyPort(); + + // information about the remote end of the socket + abstract public String getPeerAddress(); // a string e.g. "192.168.0.1" + abstract public int getPeerPort(); + abstract public String getPeerEndpoint(); // <address>::<port> + + // Is the remote end on the same machine? + abstract public boolean sameMachine(); + + // Was there a "?" in the ConnectionFilter used to accept this Socket? + public void setRequiresQuery() {queryConnection = true;} + public final boolean requiresQuery() {return queryConnection;} + + protected Socket() { + instream = null; outstream = null; ownStreams = false; + isShutdown_ = false; queryConnection = false; + } + + protected Socket(FdInStream i, FdOutStream o, boolean own) { + instream = i; outstream = o; ownStreams = own; + isShutdown_ = false; queryConnection = false; + } + + protected FdInStream instream; + protected FdOutStream outstream; + boolean ownStreams; + boolean isShutdown_; + boolean queryConnection; +} + +/* +abstract class ConnectionFilter { + public abstract boolean verifyConnection(Socket s); +}; + +abstract class SocketListener { + public SocketListener() { + fd = null; filter = null; + } + + // shutdown() stops the socket from accepting further connections + public abstract void shutdown(); + + // accept() returns a new Socket object if there is a connection + // attempt in progress AND if the connection passes the filter + // if one is installed. Otherwise, returns 0. + public abstract Socket accept(); + + // setFilter() applies the specified filter to all new connections + public void setFilter(ConnectionFilter f) {filter = f;} + //public SocketDescriptor getFd() {return fd;} + protected FileDescriptor fd; + protected ConnectionFilter filter; +}; +*/ + +//struct SocketException : public rdr::SystemException { +// SocketException(const char* text, int err_) : rdr::SystemException(text, err_) {} +//}; diff --git a/java/com/tigervnc/network/SocketDescriptor.java b/java/com/tigervnc/network/SocketDescriptor.java new file mode 100644 index 00000000..375b0335 --- /dev/null +++ b/java/com/tigervnc/network/SocketDescriptor.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2012 TigerVNC Team + * + * 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. + */ +package com.tigervnc.network; + +import java.io.IOException; +import java.lang.Exception; + +import java.net.SocketAddress; +import java.nio.*; +import java.nio.channels.*; +import java.nio.channels.spi.SelectorProvider; + +import java.util.Set; +import java.util.Iterator; + +public class SocketDescriptor extends SocketChannel + implements FileDescriptor { + + public SocketDescriptor() throws Exception { + super(SelectorProvider.provider()); + try { + channel = SocketChannel.open(); + channel.configureBlocking(false); + selector = Selector.open(); + } catch (java.io.IOException e) { + throw new Exception(e.toString()); + } + try { + channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE ); + } catch (java.nio.channels.ClosedChannelException e) { + throw new Exception(e.toString()); + } + } + + public int read(byte[] buf, int bufPtr, int length) throws Exception { + int n; + ByteBuffer b = ByteBuffer.allocate(length); + try { + n = channel.read(b); + } catch (java.io.IOException e) { + System.out.println(e.toString()); + throw new Exception(e.toString()); + } + //if (n == 0) + // throw new Exception; + b.flip(); + b.get(buf, bufPtr, n); + b.clear(); + return n; + + } + + public int write(byte[] buf, int bufPtr, int length) throws Exception { + int n; + ByteBuffer b = ByteBuffer.allocate(length); + b.put(buf, bufPtr, length); + b.flip(); + try { + n = channel.write(b); + } catch (java.io.IOException e) { + System.out.println(e.toString()); + throw new Exception(e.toString()); + } + b.clear(); + return n; + } + + public int select(int interestOps, int timeout) throws Exception { + int n; + try { + n = selector.select(timeout); + } catch (Exception e) { + System.out.println(e.toString()); + throw new Exception(e.toString()); + } + Set keys = selector.selectedKeys(); + Iterator iter = keys.iterator(); + while (iter.hasNext()) { + SelectionKey key = (SelectionKey)iter.next(); + if ((key.readyOps() & interestOps) != 0) { + n = 1; + break; + } else { + n = -1; + } + } + keys.clear(); + return n; + } + + public int write(ByteBuffer buf) throws IOException { + int n = 0; + try { + n = channel.write(buf); + } catch (java.io.IOException e) { + System.out.println(e.toString()); + throw e; + } + return n; + } + + public long write(ByteBuffer[] buf, int offset, int length) + throws IOException + { + long n = 0; + try { + n = channel.write(buf, offset, length); + } catch (java.io.IOException e) { + System.out.println(e.toString()); + } + return n; + } + + public int read(ByteBuffer buf) throws IOException { + int n = 0; + try { + n = channel.read(buf); + } catch (java.io.IOException e) { + System.out.println(e.toString()); + throw e; + } + return n; + } + + public long read(ByteBuffer[] buf, int offset, int length) + throws IOException + { + long n = 0; + try { + n = channel.read(buf, offset, length); + } catch (java.io.IOException e) { + System.out.println(e.toString()); + } + return n; + } + + public java.net.Socket socket() { + return channel.socket(); + } + + public boolean isConnectionPending() { + return channel.isConnectionPending(); + } + + public boolean connect(SocketAddress remote) throws IOException { + return channel.connect(remote); + } + + public boolean finishConnect() throws IOException { + return channel.finishConnect(); + } + + public boolean isConnected() { + return channel.isConnected(); + } + + protected void implConfigureBlocking(boolean block) throws IOException { + channel.configureBlocking(block); + } + + protected synchronized void implCloseSelectableChannel() throws IOException { + channel.close(); + notifyAll(); + } + + protected void setChannel(SocketChannel channel_) { + try { + if (channel != null) + channel.close(); + if (selector != null) + selector.close(); + channel = channel_; + channel.configureBlocking(false); + selector = Selector.open(); + } catch (java.io.IOException e) { + System.out.println(e.toString()); + } + try { + channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE ); + } catch (java.nio.channels.ClosedChannelException e) { + System.out.println(e.toString()); + } + } + + protected SocketChannel channel; + protected Selector selector; + +} diff --git a/java/com/tigervnc/network/SocketListener.java b/java/com/tigervnc/network/SocketListener.java new file mode 100644 index 00000000..cb78388e --- /dev/null +++ b/java/com/tigervnc/network/SocketListener.java @@ -0,0 +1,46 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright (C) 2012 Brian P Hinz. 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. + */ + +// -=- SocketListener - abstract base-class for any kind of network stream/socket + +package com.tigervnc.network; + +import java.nio.channels.*; +import java.nio.channels.spi.SelectorProvider; + +abstract public class SocketListener { + + public SocketListener() {} + + // shutdown() stops the socket from accepting further connections + abstract public void shutdown(); + + // accept() returns a new Socket object if there is a connection + // attempt in progress AND if the connection passes the filter + // if one is installed. Otherwise, returns 0. + abstract public Socket accept(); + + // setFilter() applies the specified filter to all new connections + //public void setFilter(ConnectionFilter* f) {filter = f;} + public FileDescriptor getFd() {return fd;} + + protected FileDescriptor fd; + //protected ConnectionFilter* filter; + +} diff --git a/java/com/tigervnc/network/TcpListener.java b/java/com/tigervnc/network/TcpListener.java new file mode 100644 index 00000000..63a2ff19 --- /dev/null +++ b/java/com/tigervnc/network/TcpListener.java @@ -0,0 +1,168 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright (C) 2012 TigerVNC Team + * + * 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. + */ + +package com.tigervnc.network; + +import java.io.IOException; +import java.lang.Exception; +import java.nio.*; +import java.nio.channels.*; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.UnknownHostException; +import java.util.Set; +import java.util.Iterator; + +public class TcpListener extends SocketListener { + + public static boolean socketsInitialised = false; + + public TcpListener(String listenaddr, int port, boolean localhostOnly, + SocketDescriptor sock, boolean close_) throws Exception { + closeFd = close_; + if (sock != null) { + fd = sock; + return; + } + + TcpSocket.initSockets(); + try { + channel = ServerSocketChannel.open(); + channel.configureBlocking(false); + } catch(IOException e) { + throw new Exception("unable to create listening socket: "+e.toString()); + } + + // - Bind it to the desired port + InetAddress addr = null; + + try { + if (localhostOnly) { + addr = InetAddress.getByName(null); + } else if (listenaddr != null) { + addr = java.net.InetAddress.getByName(listenaddr); + } else { + // FIXME: need to be sure we get the wildcard address? + addr = InetAddress.getByName(null); + //addr = InetAddress.getLocalHost(); + } + } catch (UnknownHostException e) { + System.out.println(e.toString()); + System.exit(-1); + } + + try { + channel.socket().bind(new InetSocketAddress(addr, port)); + } catch (IOException e) { + throw new Exception("unable to bind listening socket: "+e.toString()); + } + + // - Set it to be a listening socket + try { + selector = Selector.open(); + channel.register(selector, SelectionKey.OP_ACCEPT); + } catch (IOException e) { + throw new Exception("unable to set socket to listening mode: "+e.toString()); + } + } + + public TcpListener(String listenaddr, int port) throws Exception { + this(listenaddr, port, false, null, true); + } + +// TcpListener::~TcpListener() { +// if (closeFd) closesocket(fd); +// } + + public void shutdown() { + //shutdown(getFd(), 2); + } + + public TcpSocket accept() { + SocketChannel new_sock = null; + + // Accept an incoming connection + try { + if (selector.select() > 0) { + Set keys = selector.selectedKeys(); + Iterator iter = keys.iterator(); + while (iter.hasNext()) { + SelectionKey key = (SelectionKey)iter.next(); + iter.remove(); + if (key.isAcceptable()) { + new_sock = channel.accept(); + break; + } + } + keys.clear(); + if (new_sock == null) + return null; + } + } catch (IOException e) { + //throw SocketException("unable to accept new connection", errorNumber); + System.out.println(e.toString()); + } + + // Disable Nagle's algorithm, to reduce latency + TcpSocket.enableNagles(new_sock, false); + + // Create the socket object & check connection is allowed + SocketDescriptor fd = null; + try { + fd = new SocketDescriptor(); + } catch (java.lang.Exception e) { + System.out.println(e.toString()); + System.exit(-1); + } + fd.setChannel(new_sock); + TcpSocket s = new TcpSocket(fd); + //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); + } +} + */ + + //public int getMyPort() { + // return TcpSocket.getSockPort(); + //} + + private boolean closeFd; + private ServerSocketChannel channel; + private Selector selector; + +} + diff --git a/java/com/tigervnc/network/TcpSocket.java b/java/com/tigervnc/network/TcpSocket.java new file mode 100644 index 00000000..926aa975 --- /dev/null +++ b/java/com/tigervnc/network/TcpSocket.java @@ -0,0 +1,201 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright (C) 2012 TigerVNC Team + * + * 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. + */ + +package com.tigervnc.network; + +import com.tigervnc.rdr.FdInStream; +import com.tigervnc.rdr.FdOutStream; +import com.tigervnc.rfb.LogWriter; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.nio.*; +import java.nio.channels.*; + +public class TcpSocket extends Socket { + + // -=- Socket initialisation + public static boolean socketsInitialised = false; + public static void initSockets() { + if (socketsInitialised) + return; + socketsInitialised = true; + } + + // -=- TcpSocket + + public TcpSocket(SocketDescriptor sock, boolean close) { + super(new FdInStream(sock), new FdOutStream(sock), true); + //this.sock = sock; + closeFd = close; + } + + public TcpSocket(SocketDescriptor sock) { + this(sock, true); + } + + public TcpSocket(String host, int port) throws Exception { + closeFd = true; + SocketDescriptor sock = null; + InetAddress addr = null; + boolean result = false; + + // - Create a socket + initSockets(); + + try { + addr = java.net.InetAddress.getByName(host); + } catch(UnknownHostException e) { + throw new Exception("unable to resolve host by name: "+e.toString()); + } + + try { + sock = new SocketDescriptor(); + } catch(UnknownHostException e) { + throw new Exception("unable to create socket: "+e.toString()); + } + + /* Attempt to connect to the remote host */ + try { + result = sock.connect(new InetSocketAddress(addr, port)); + } catch(java.io.IOException e) { + //throw new java.lang.Exception(e.getMessage()); + System.out.println("connect failed: "+e.getMessage()); + } + + if (!result && sock.isConnectionPending()) { + while (!result) { + try { + result = sock.finishConnect(); + } catch(java.nio.channels.ClosedChannelException e) { + throw new Exception(e.getMessage()); + } + } + } + + if (!result) + throw new Exception("unable connect to socket"); + + // Disable Nagle's algorithm, to reduce latency + enableNagles(sock, false); + + // Create the input and output streams + instream = new FdInStream(sock); + outstream = new FdOutStream(sock); + ownStreams = true; + } + + protected void finalize() throws Exception { + if (closeFd) + try { + ((SocketDescriptor)getFd()).close(); + } catch (IOException e) { + throw new Exception(e.toString()); + } + } + + public int getMyPort() { + SocketAddress address = ((SocketDescriptor)getFd()).socket().getLocalSocketAddress(); + return ((InetSocketAddress)address).getPort(); + } + + public String getPeerAddress() { + SocketAddress peer = ((SocketDescriptor)getFd()).socket().getRemoteSocketAddress(); + if (peer != null) + return peer.toString(); + return ""; + } + + public int getPeerPort() { + SocketAddress address = ((SocketDescriptor)getFd()).socket().getRemoteSocketAddress(); + return ((InetSocketAddress)address).getPort(); + } + + public String getPeerEndpoint() { + String address = getPeerAddress(); + int port = getPeerPort(); + return address+"::"+port; + } + + public boolean sameMachine() { + SocketAddress peeraddr = ((SocketDescriptor)getFd()).socket().getRemoteSocketAddress(); + SocketAddress myaddr = ((SocketDescriptor)getFd()).socket().getLocalSocketAddress(); + return myaddr.equals(peeraddr); + } + + public void shutdown() { + super.shutdown(); + } + + public void close() throws IOException { + ((SocketDescriptor)getFd()).close(); + } + + public static boolean enableNagles(SocketChannel sock, boolean enable) { + try { + sock.socket().setTcpNoDelay(!enable); + } catch(SocketException e) { + vlog.error(e.getMessage()); + return false; + } + return true; + } + + public static boolean isSocket(java.net.Socket sock) { + return sock.getClass().toString().equals("com.tigervnc.net.Socket"); + } + + public boolean isConnected() { + return ((SocketDescriptor)getFd()).isConnected(); + } + + public int getSockPort() { + return ((InetSocketAddress)((SocketDescriptor)getFd()).socket().getRemoteSocketAddress()).getPort(); + } + + private boolean closeFd; + static LogWriter vlog = new LogWriter("TcpSocket"); + +} + +/* Tunnelling support. */ +/* +public int findFreeTcpPort() { + int sock; + + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) + throw SocketException("unable to create socket", errorNumber); + + int port = 0; + if (bind (sock, (struct sockaddr *)&addr, sizeof (addr)) < 0) + throw SocketException("unable to find free port", errorNumber); + + socklen_t n = sizeof(addr); + if (getsockname (sock, (struct sockaddr *)&addr, &n) < 0) + throw SocketException("unable to get port number", errorNumber); + + closesocket(sock); + return ntohs(addr.sin_port); +} +*/ + diff --git a/java/com/tigervnc/rdr/FdInStream.java b/java/com/tigervnc/rdr/FdInStream.java new file mode 100644 index 00000000..c638f4b2 --- /dev/null +++ b/java/com/tigervnc/rdr/FdInStream.java @@ -0,0 +1,219 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright (C) 2012 TigerVNC Team + * + * 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. + */ + +package com.tigervnc.rdr; + +import com.tigervnc.network.*; +import java.nio.channels.Selector; +import java.nio.channels.SelectionKey; +import java.util.Set; +import java.util.Iterator; + +public class FdInStream extends InStream { + + static final int defaultBufSize = 8192; + static final int minBulkSize = 1024; + + public FdInStream(FileDescriptor fd_, int bufSize_) { + fd = fd_; + bufSize = bufSize_; + b = new byte[bufSize]; + ptr = end = offset = 0; + timeoutms = 0; + timing = false; + timeWaitedIn100us = 5; + timedKbits = 0; + } + + public FdInStream(FileDescriptor fd_) { this(fd_, defaultBufSize); } + + public final void readBytes(byte[] data, int dataPtr, int length) { + if (length < minBulkSize) { + super.readBytes(data, dataPtr, length); + return; + } + + int n = end - ptr; + if (n > length) n = length; + + System.arraycopy(b, ptr, data, dataPtr, n); + dataPtr += n; + length -= n; + ptr += n; + + while (length > 0) { + n = readWithTimeoutOrCallback(data, dataPtr, length); + dataPtr += n; + length -= n; + offset += n; + } + } + + public void setTimeout(int timeoutms_) { + timeoutms = timeoutms_; + } + + public void setBlockCallback(FdInStreamBlockCallback blockCallback_) + { + blockCallback = blockCallback_; + timeoutms = 0; + } + + public final int pos() { return offset + ptr; } + + public final void startTiming() { + timing = true; + + // Carry over up to 1s worth of previous rate for smoothing. + + if (timeWaitedIn100us > 10000) { + timedKbits = timedKbits * 10000 / timeWaitedIn100us; + timeWaitedIn100us = 10000; + } + } + + public final void stopTiming() { + timing = false; + if (timeWaitedIn100us < timedKbits/2) + timeWaitedIn100us = timedKbits/2; // upper limit 20Mbit/s + } + + public final long kbitsPerSecond() { + return timedKbits * 10000 / timeWaitedIn100us; + } + + public final long timeWaited() { return timeWaitedIn100us; } + + protected int overrun(int itemSize, int nItems, boolean wait) { + if (itemSize > bufSize) + throw new Exception("FdInStream overrun: max itemSize exceeded"); + + if (end - ptr != 0) + System.arraycopy(b, ptr, b, 0, end - ptr); + + offset += ptr; + end -= ptr; + ptr = 0; + + int bytes_to_read; + while (end < itemSize) { + bytes_to_read = bufSize - end; + if (!timing) { + // When not timing, we must be careful not to read too much + // extra data into the buffer. Otherwise, the line speed + // estimation might stay at zero for a long time: All reads + // during timing=1 can be satisfied without calling + // readWithTimeoutOrCallback. However, reading only 1 or 2 bytes + // bytes is ineffecient. + bytes_to_read = Math.min(bytes_to_read, Math.max(itemSize*nItems, 8)); + } + int n = readWithTimeoutOrCallback(b, end, bytes_to_read, wait); + if (n == 0) return 0; + end += n; + } + + if (itemSize * nItems > end - ptr) + nItems = (end - ptr) / itemSize; + + return nItems; + } + + protected int readWithTimeoutOrCallback(byte[] buf, int bufPtr, int len, boolean wait) { + long before = 0; + long timeout; + if (timing) + before = System.nanoTime(); + + int n; + while (true) { + + if (!wait) { + timeout = 0; + } else if (timeoutms != -1) { + timeout = (long)timeoutms; + } else { + timeout = 0; + } + + try { + n = fd.select(SelectionKey.OP_READ, timeoutms); + } catch (java.lang.Exception e) { + System.out.println(e.toString()); + throw new Exception(e.toString()); + } + + + if (n > 0) break; + if (!wait) return 0; + //if (blockCallback == null) throw TimedOut(); + + blockCallback.blockCallback(); + } + + try { + n = fd.read(buf, bufPtr, len); + } catch (java.lang.Exception e) { + System.out.println("read:"+e.toString()); + throw new Exception(e.toString()); + } + + if (n == 0) throw new EndOfStream(); + + if (timing) { + long after = System.nanoTime(); + long newTimeWaited = (after - before) / 100000; + int newKbits = n * 8 / 1000; + + // limit rate to between 10kbit/s and 40Mbit/s + + if (newTimeWaited > newKbits*1000) { + newTimeWaited = newKbits*1000; + } else if (newTimeWaited < newKbits/4) { + newTimeWaited = newKbits/4; + } + + timeWaitedIn100us += newTimeWaited; + timedKbits += newKbits; + } + + return n; + } + + private int readWithTimeoutOrCallback(byte[] buf, int bufPtr, int len) { + return readWithTimeoutOrCallback(buf, bufPtr, len, true); + } + + public FileDescriptor getFd() { + return fd; + } + + public void setFd(FileDescriptor fd_) { + fd = fd_; + } + + private int offset; + private int bufSize; + private FileDescriptor fd; + private FdInStreamBlockCallback blockCallback; + protected int timeoutms; + + protected boolean timing; + protected long timeWaitedIn100us; + protected long timedKbits; +} diff --git a/java/com/tigervnc/rdr/FdInStreamBlockCallback.java b/java/com/tigervnc/rdr/FdInStreamBlockCallback.java new file mode 100644 index 00000000..45d5d2ae --- /dev/null +++ b/java/com/tigervnc/rdr/FdInStreamBlockCallback.java @@ -0,0 +1,23 @@ +/* Copyright (C) 2010-2012 TigerVNC Team + * + * 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. + */ + +package com.tigervnc.rdr; + +public interface FdInStreamBlockCallback { + abstract public void blockCallback(); +} diff --git a/java/com/tigervnc/rdr/FdOutStream.java b/java/com/tigervnc/rdr/FdOutStream.java new file mode 100644 index 00000000..d2e95ea0 --- /dev/null +++ b/java/com/tigervnc/rdr/FdOutStream.java @@ -0,0 +1,177 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright (C) 2012 TigerVnc Team + * + * 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. + */ + +package com.tigervnc.rdr; + +import com.tigervnc.network.*; +import java.nio.channels.SelectionKey; + +public class FdOutStream extends OutStream { + + static final int defaultBufSize = 8192; + static final int minBulkSize = 1024; + + public FdOutStream(FileDescriptor fd_, boolean blocking_, int timeoutms_, int bufSize_) + { + fd = fd_; + blocking = blocking_; + timeoutms = timeoutms_; + bufSize = bufSize_; + b = new byte[bufSize]; + offset = 0; + ptr = sentUpTo = start = 0; + end = start + bufSize; + } + + public FdOutStream(FileDescriptor fd_) { this(fd_, false, 0, defaultBufSize); } + + public void setTimeout(int timeoutms_) { + timeoutms = timeoutms_; + } + + public void setBlocking(boolean blocking_) { + blocking = blocking_; + } + + public int length() + { + return offset + ptr - sentUpTo; + } + + public void flush() + { + int timeoutms_; + + if (blocking) + timeoutms_ = timeoutms; + else + timeoutms_ = 0; + + while (sentUpTo < ptr) { + int n = writeWithTimeout(b, sentUpTo, ptr - sentUpTo, timeoutms_); + + // Timeout? + if (n == 0) { + // If non-blocking then we're done here + if (!blocking) + break; + + // Otherwise try blocking (with possible timeout) + if ((timeoutms_ == 0) && (timeoutms != 0)) { + timeoutms_ = timeoutms; + break; + } + + // Proper timeout + //throw TimedOut(); + } + + sentUpTo += n; + offset += n; + } + + // Managed to flush everything? + if (sentUpTo == ptr) + ptr = sentUpTo = start; + } + + private int writeWithTimeout(byte[] data, int dataPtr, int length, int timeoutms) + { + long timeout; + int n; + + do { + + if (timeoutms != -1) { + timeout = (long)timeoutms; + } else { + timeout = 0; + } + + try { + n = fd.select(SelectionKey.OP_WRITE, timeoutms); + } catch (java.lang.Exception e) { + System.out.println(e.toString()); + throw new Exception(e.toString()); + } + + } while (n < 0); + + try { + n = fd.write(data, dataPtr, length); + } catch (java.lang.Exception e) { + System.out.println("read:"+e.toString()); + throw new Exception(e.toString()); + } + + return n; + } + + protected int overrun(int itemSize, int nItems) + { + if (itemSize > bufSize) + throw new Exception("FdOutStream overrun: max itemSize exceeded"); + + // First try to get rid of the data we have + flush(); + + // Still not enough space? + if (itemSize > end - ptr) { + // Can we shuffle things around? + // (don't do this if it gains us less than 25%) + if ((sentUpTo - start > bufSize / 4) && + (itemSize < bufSize - (ptr - sentUpTo))) { + System.arraycopy(b, ptr, b, start, ptr - sentUpTo); + ptr = start + (ptr - sentUpTo); + sentUpTo = start; + } else { + // Have to get rid of more data, so turn off non-blocking + // for a bit... + boolean realBlocking; + + realBlocking = blocking; + blocking = true; + flush(); + blocking = realBlocking; + } + } + + // Can we fit all the items asked for? + if (itemSize * nItems > end - ptr) + nItems = (end - ptr) / itemSize; + + return nItems; + } + + public FileDescriptor getFd() { + return fd; + } + + public void setFd(FileDescriptor fd_) { + fd = fd_; + } + + protected FileDescriptor fd; + protected boolean blocking; + protected int timeoutms; + protected int start; + protected int sentUpTo; + protected int offset; + protected int bufSize; +} diff --git a/java/com/tigervnc/rdr/InStream.java b/java/com/tigervnc/rdr/InStream.java index a18ea4ed..8e58fbda 100644 --- a/java/com/tigervnc/rdr/InStream.java +++ b/java/com/tigervnc/rdr/InStream.java @@ -23,6 +23,8 @@ package com.tigervnc.rdr; +import com.tigervnc.network.*; + abstract public class InStream { // check() ensures there is buffer data for at least one item of size diff --git a/java/com/tigervnc/rdr/JavaInStream.java b/java/com/tigervnc/rdr/JavaInStream.java deleted file mode 100644 index faa968ac..00000000 --- a/java/com/tigervnc/rdr/JavaInStream.java +++ /dev/null @@ -1,162 +0,0 @@ -/* Copyright (C) 2002-2005 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. - */ - -// -// A JavaInStream reads from a java.io.InputStream -// - -package com.tigervnc.rdr; - -public class JavaInStream extends InStream { - - static final int defaultBufSize = 8192; - static final int minBulkSize = 1024; - - public JavaInStream(java.io.InputStream jis_, int bufSize_) { - jis = jis_; - bufSize = bufSize_; - b = new byte[bufSize]; - ptr = end = offset = 0; - timing = false; - timeWaitedIn100us = 5; - timedKbits = 0; - } - - public JavaInStream(java.io.InputStream jis_) { this(jis_, defaultBufSize); } - - public void readBytes(byte[] data, int dataPtr, int length) { - if (length < minBulkSize) { - super.readBytes(data, dataPtr, length); - return; - } - - int n = end - ptr; - if (n > length) n = length; - - System.arraycopy(b, ptr, data, dataPtr, n); - dataPtr += n; - length -= n; - ptr += n; - - while (length > 0) { - n = read(data, dataPtr, length); - dataPtr += n; - length -= n; - offset += n; - } - } - - public int pos() { return offset + ptr; } - - public void startTiming() { - timing = true; - - // Carry over up to 1s worth of previous rate for smoothing. - - if (timeWaitedIn100us > 10000) { - timedKbits = timedKbits * 10000 / timeWaitedIn100us; - timeWaitedIn100us = 10000; - } - } - - public void stopTiming() { - timing = false; - if (timeWaitedIn100us < timedKbits/2) - timeWaitedIn100us = timedKbits/2; // upper limit 20Mbit/s - } - - public long kbitsPerSecond() { - return timedKbits * 10000 / timeWaitedIn100us; - } - - public long timeWaited() { return timeWaitedIn100us; } - - protected int overrun(int itemSize, int nItems, boolean wait) { - if (itemSize > bufSize) - throw new Exception("JavaInStream overrun: max itemSize exceeded"); - - if (end - ptr != 0) - System.arraycopy(b, ptr, b, 0, end - ptr); - - offset += ptr; - end -= ptr; - ptr = 0; - - while (end < itemSize) { - int bytes_to_read = bufSize - end; - - if (!timing) { - bytes_to_read = Math.min(bytes_to_read, Math.max(itemSize*nItems, 8)); - } - - int n = read(b, end, bytes_to_read, wait); - - end += n; - } - - if (itemSize * nItems > end) - nItems = end / itemSize; - - return nItems; - } - - private int read(byte[] buf, int bufPtr, int len, boolean wait) { - long before = 0; - if (timing) - before = System.nanoTime(); - - int n = -1; - try { - n = jis.read(buf, bufPtr, len); - } catch (java.io.IOException e) { - throw new IOException(e); - } - - if (n < 0) throw new EndOfStream(); - if (n == 0) return 0; - - if (timing) { - long after = System.nanoTime(); - long newTimeWaited = (after - before) / 100000; - int newKbits = n * 8 / 1000; - - // limit rate to between 10kbit/s and 40Mbit/s - - if (newTimeWaited > newKbits*1000) { - newTimeWaited = newKbits*1000; - } else if (newTimeWaited < newKbits/4) { - newTimeWaited = newKbits/4; - } - - timeWaitedIn100us += newTimeWaited; - timedKbits += newKbits; - } - - return n; - - } - private int read(byte[] buf, int bufPtr, int len) { return read(buf, bufPtr, len, true); } - - private java.io.InputStream jis; - private int offset; - private int bufSize; - - boolean timing; - long timeWaitedIn100us; - long timedKbits; -} diff --git a/java/com/tigervnc/rdr/OutStream.java b/java/com/tigervnc/rdr/OutStream.java index 0919453e..b4698772 100644 --- a/java/com/tigervnc/rdr/OutStream.java +++ b/java/com/tigervnc/rdr/OutStream.java @@ -23,6 +23,8 @@ package com.tigervnc.rdr; +import com.tigervnc.network.*; + abstract public class OutStream { // check() ensures there is buffer space for at least one item of size diff --git a/java/com/tigervnc/rdr/TLSException.java b/java/com/tigervnc/rdr/TLSException.java new file mode 100644 index 00000000..1d7ff9d7 --- /dev/null +++ b/java/com/tigervnc/rdr/TLSException.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2004 Red Hat Inc. + * Copyright (C) 2010-2012 TigerVNC Team + * + * 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. + */ + +package com.tigervnc.rdr; + +class TLSException extends Exception { + public TLSException(String s, int n) { + // FIXME: append enumerated n + super(s); + } +} diff --git a/java/com/tigervnc/rdr/TLSInStream.java b/java/com/tigervnc/rdr/TLSInStream.java new file mode 100644 index 00000000..f0e5fbe0 --- /dev/null +++ b/java/com/tigervnc/rdr/TLSInStream.java @@ -0,0 +1,111 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright (C) 2005 Martin Koegler + * Copyright (C) 2010-2012 TigerVNC Team + * + * 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. + */ + +package com.tigervnc.rdr; + +import java.nio.ByteBuffer; +import java.nio.channels.*; +import javax.net.ssl.*; + +import com.tigervnc.network.*; + +public class TLSInStream extends InStream { + + static final int defaultBufSize = 16384; + + public TLSInStream(InStream _in, SSLEngineManager _manager) { + in = (FdInStream)_in; + manager = _manager; + offset = 0; + SSLSession session = manager.getSession(); + bufSize = session.getApplicationBufferSize(); + b = new byte[bufSize]; + ptr = end = start = 0; + } + + public final int pos() { + return offset + ptr - start; + } + + public final void startTiming() { + in.startTiming(); + } + + public final void stopTiming() { + in.stopTiming(); + } + + public final long kbitsPerSecond() { + return in.kbitsPerSecond(); + } + + public final long timeWaited() { + return in.timeWaited(); + } + + protected final int overrun(int itemSize, int nItems, boolean wait) { + if (itemSize > bufSize) + throw new Exception("TLSInStream overrun: max itemSize exceeded"); + + if (end - ptr != 0) + System.arraycopy(b, ptr, b, 0, end - ptr); + + offset += ptr - start; + end -= ptr - start; + ptr = start; + + while (end < start + itemSize) { + int n = readTLS(b, end, start + bufSize - end, wait); + if (!wait && n == 0) + return 0; + end += n; + } + + if (itemSize * nItems > end - ptr) + nItems = (end - ptr) / itemSize; + + return nItems; + } + + protected int readTLS(byte[] buf, int bufPtr, int len, boolean wait) + { + int n = -1; + + //n = in.check(1, 1, wait); + //if (n == 0) + // return 0; + + try { + n = manager.read(buf, bufPtr, len); + } catch (java.io.IOException e) { + e.printStackTrace(); + } + + if (n < 0) throw new TLSException("readTLS", n); + + return n; + } + + private SSLEngineManager manager; + private int offset; + private int start; + private int bufSize; + private FdInStream in; +} diff --git a/java/com/tigervnc/rdr/JavaOutStream.java b/java/com/tigervnc/rdr/TLSOutStream.java index 015f81f5..8fa15762 100644 --- a/java/com/tigervnc/rdr/JavaOutStream.java +++ b/java/com/tigervnc/rdr/TLSOutStream.java @@ -1,4 +1,6 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright (C) 2005 Martin Koegler + * Copyright (C) 2010-2012 TigerVNC Team * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,27 +18,28 @@ * USA. */ -// -// A JavaOutStream writes to a java.io.OutputStream -// - package com.tigervnc.rdr; -public class JavaOutStream extends OutStream { +import java.nio.ByteBuffer; +import java.nio.channels.*; +import javax.net.ssl.*; + +import com.tigervnc.network.*; + +public class TLSOutStream extends OutStream { static final int defaultBufSize = 16384; - static final int minBulkSize = 1024; - public JavaOutStream(java.io.OutputStream jos_, int bufSize_) { - jos = jos_; - bufSize = bufSize_; + public TLSOutStream(OutStream _out, SSLEngineManager _manager) { + manager = _manager; + out = (FdOutStream)_out; + SSLSession session = manager.getSession(); + bufSize = session.getApplicationBufferSize(); b = new byte[bufSize]; ptr = offset = start = 0; end = start + bufSize; } - public JavaOutStream(java.io.OutputStream jos) { this(jos, defaultBufSize); } - public int length() { return offset + ptr - start; @@ -46,21 +49,19 @@ public class JavaOutStream extends OutStream { { int sentUpTo = start; while (sentUpTo < ptr) { - try { - jos.write(b, sentUpTo, ptr - sentUpTo); - sentUpTo += ptr - sentUpTo; - offset += ptr - sentUpTo; - } catch (java.io.IOException e) { - throw new IOException(e); - } + int n = writeTLS(b, sentUpTo, ptr - sentUpTo); + sentUpTo += n; + offset += n; } + ptr = start; + //out.flush(); } protected int overrun(int itemSize, int nItems) { if (itemSize > bufSize) - throw new Exception("JavaOutStream overrun: max itemSize exceeded"); + throw new Exception("TLSOutStream overrun: max itemSize exceeded"); flush(); @@ -70,7 +71,26 @@ public class JavaOutStream extends OutStream { return nItems; } - private java.io.OutputStream jos; + protected int writeTLS(byte[] data, int dataPtr, int length) + { + int n = 0; + + try { + n = manager.write(data, dataPtr, length); + } catch (java.io.IOException e) { + throw new Exception(e.toString()); + } + //if (n == GNUTLS_E_INTERRUPTED || n == GNUTLS_E_AGAIN) + // return 0; + + //if (n < 0) + // throw new TLSException("writeTLS", n); + + return n; + } + + private SSLEngineManager manager; + private FdOutStream out; private int start; private int offset; private int bufSize; diff --git a/java/com/tigervnc/rfb/CConnection.java b/java/com/tigervnc/rfb/CConnection.java index 8a477654..0ec44044 100644 --- a/java/com/tigervnc/rfb/CConnection.java +++ b/java/com/tigervnc/rfb/CConnection.java @@ -20,6 +20,7 @@ package com.tigervnc.rfb; import java.util.*; +import com.tigervnc.network.*; import com.tigervnc.rdr.*; abstract public class CConnection extends CMsgHandler { @@ -347,10 +348,6 @@ abstract public class CConnection extends CMsgHandler { int serverPort; boolean useProtocol3_3 = false; boolean clientSecTypeOrder; - public static java.net.Socket sock; - - public static java.net.Socket getSocket() { return sock; } - public static void setSocket(java.net.Socket sock_) { sock = sock_; } static LogWriter vlog = new LogWriter("CConnection"); } diff --git a/java/com/tigervnc/rfb/CSecurityTLS.java b/java/com/tigervnc/rfb/CSecurityTLS.java index 2b8bf355..3d89fd32 100644 --- a/java/com/tigervnc/rfb/CSecurityTLS.java +++ b/java/com/tigervnc/rfb/CSecurityTLS.java @@ -1,5 +1,8 @@ /* - * Copyright (C) 2003 Sun Microsystems, Inc. + * Copyright (C) 2004 Red Hat Inc. + * Copyright (C) 2005 Martin Koegler + * Copyright (C) 2010 m-privacy GmbH + * Copyright (C) 2010-2012 TigerVNC Team * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,6 +23,7 @@ package com.tigervnc.rfb; import javax.net.ssl.*; +import java.security.*; import java.security.cert.*; import java.security.KeyStore; import java.io.File; @@ -31,6 +35,9 @@ import javax.swing.JOptionPane; import com.tigervnc.vncviewer.UserPrefs; import com.tigervnc.rdr.*; +import com.tigervnc.network.*; +import java.util.concurrent.Executors; +import java.nio.channels.SelectionKey; public class CSecurityTLS extends CSecurity { @@ -43,54 +50,24 @@ public class CSecurityTLS extends CSecurity { private void initGlobal() { - try { - SSLSocketFactory sslfactory; - SSLContext ctx = SSLContext.getInstance("TLS"); - if (anon) { - ctx.init(null, null, null); - } else { - TrustManager[] myTM = new TrustManager[] { - new MyX509TrustManager() - }; - ctx.init (null, myTM, null); - } - sslfactory = ctx.getSocketFactory(); + boolean globalInitDone = false; + + if (!globalInitDone) { try { - ssl = (SSLSocket)sslfactory.createSocket(CConnection.sock, - CConnection.sock.getInetAddress().getHostName(), - CConnection.sock.getPort(), true); - } catch (java.io.IOException e) { + ctx = SSLContext.getInstance("TLS"); + } catch(NoSuchAlgorithmException e) { throw new Exception(e.toString()); } - if (anon) { - String[] supported; - ArrayList<String> enabled = new ArrayList<String>(); - - supported = ssl.getSupportedCipherSuites(); - - for (int i = 0; i < supported.length; i++) - if (supported[i].matches("TLS_DH_anon.*")) - enabled.add(supported[i]); - - ssl.setEnabledCipherSuites(enabled.toArray(new String[0])); - } else { - ssl.setEnabledCipherSuites(ssl.getSupportedCipherSuites()); - } - - ssl.setEnabledProtocols(new String[]{"SSLv3","TLSv1"}); - ssl.addHandshakeCompletedListener(new MyHandshakeListener()); - } - catch (java.security.GeneralSecurityException e) - { - vlog.error ("TLS handshake failed " + e.toString ()); - return; + globalInitDone = true; } } public CSecurityTLS(boolean _anon) { anon = _anon; + session = null; + setDefaults(); cafile = x509ca.getData(); crlfile = x509crl.getData(); @@ -116,40 +93,98 @@ public class CSecurityTLS extends CSecurity { x509crl.setDefaultStr(crlDefault); } +// FIXME: +// Need to shutdown the connection cleanly + +// FIXME? +// add a finalizer method that calls shutdown + public boolean processMsg(CConnection cc) { - is = cc.getInStream(); + is = (FdInStream)cc.getInStream(); + os = (FdOutStream)cc.getOutStream(); + client = cc; initGlobal(); - if (!is.checkNoWait(1)) - return false; - - if (is.readU8() == 0) { - int result = is.readU32(); - String reason; - if (result == Security.secResultFailed || - result == Security.secResultTooMany) - reason = is.readString(); - else - reason = new String("Authentication failure (protocol error)"); - throw new AuthFailureException(reason); + if (session == null) { + if (!is.checkNoWait(1)) + return false; + + if (is.readU8() == 0) { + int result = is.readU32(); + String reason; + if (result == Security.secResultFailed || + result == Security.secResultTooMany) + reason = is.readString(); + else + reason = new String("Authentication failure (protocol error)"); + throw new AuthFailureException(reason); + } + + setParam(); + } - // SSLSocket.getSession blocks until the handshake is complete - session = ssl.getSession(); - if (!session.isValid()) - throw new Exception("TLS Handshake failed!"); + try { + manager = new SSLEngineManager(engine, is, os); + } catch(java.lang.Exception e) { + System.out.println(e.toString()); + } try { - cc.setStreams(new JavaInStream(ssl.getInputStream()), - new JavaOutStream(ssl.getOutputStream())); - } catch (java.io.IOException e) { - throw new Exception("Failed to set streams"); + manager.doHandshake(); + } catch (java.lang.Exception e) { + throw new Exception(e.toString()); } + //checkSession(); + + cc.setStreams(new TLSInStream(is, manager), + new TLSOutStream(os, manager)); return true; } + private void setParam() { + + if (anon) { + try { + ctx.init(null, null, null); + } catch(KeyManagementException e) { + throw new AuthFailureException(e.toString()); + } + } else { + try { + TrustManager[] myTM = new TrustManager[] { + new MyX509TrustManager() + }; + ctx.init (null, myTM, null); + } catch (java.security.GeneralSecurityException e) { + throw new AuthFailureException(e.toString()); + } + } + SSLSocketFactory sslfactory = ctx.getSocketFactory(); + engine = ctx.createSSLEngine(client.getServerName(), + client.getServerPort()); + engine.setUseClientMode(true); + + if (anon) { + String[] supported; + ArrayList<String> enabled = new ArrayList<String>(); + + supported = engine.getSupportedCipherSuites(); + + for (int i = 0; i < supported.length; i++) + if (supported[i].matches("TLS_DH_anon.*")) + enabled.add(supported[i]); + + engine.setEnabledCipherSuites(enabled.toArray(new String[0])); + } else { + engine.setEnabledCipherSuites(engine.getSupportedCipherSuites()); + } + + engine.setEnabledProtocols(new String[]{"SSLv3","TLSv1"}); + } + class MyHandshakeListener implements HandshakeCompletedListener { public void handshakeCompleted(HandshakeCompletedEvent e) { vlog.info("Handshake succesful!"); @@ -233,16 +268,20 @@ public class CSecurityTLS extends CSecurity { public final String description() { return anon ? "TLS Encryption without VncAuth" : "X509 Encryption without VncAuth"; } - - //protected void setParam(); //protected void checkSession(); - protected CConnection cc; + protected CConnection client; - private boolean anon; + + + private SSLContext ctx; private SSLSession session; + private SSLEngine engine; + private SSLEngineManager manager; + private boolean anon; + private String cafile, crlfile; - private InStream is; - private SSLSocket ssl; + private FdInStream is; + private FdOutStream os; static LogWriter vlog = new LogWriter("CSecurityTLS"); } diff --git a/java/com/tigervnc/vncviewer/CConn.java b/java/com/tigervnc/vncviewer/CConn.java index ae03b524..a1aa0d43 100644 --- a/java/com/tigervnc/vncviewer/CConn.java +++ b/java/com/tigervnc/vncviewer/CConn.java @@ -1,4 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright (C) 2012 TigerVNC Team * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -45,7 +46,9 @@ import java.util.jar.Attributes; import java.util.jar.Manifest; import javax.swing.*; import javax.swing.ImageIcon; +import java.net.InetSocketAddress; import java.net.URL; +import java.net.SocketException; import java.util.*; import com.tigervnc.rdr.*; @@ -53,6 +56,8 @@ import com.tigervnc.rfb.*; import com.tigervnc.rfb.Exception; import com.tigervnc.rfb.Point; import com.tigervnc.rfb.Rect; +import com.tigervnc.network.Socket; +import com.tigervnc.network.TcpSocket; class ViewportFrame extends JFrame { @@ -68,7 +73,7 @@ class ViewportFrame extends JFrame }); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { - cc.close(); + cc.deleteWindow(); } }); addComponentListener(new ComponentAdapter() { @@ -141,12 +146,12 @@ class ViewportFrame extends JFrame } public class CConn extends CConnection - implements UserPasswdGetter, UserMsgBox, OptionsDialogCallback + implements UserPasswdGetter, UserMsgBox, OptionsDialogCallback, FdInStreamBlockCallback { //////////////////////////////////////////////////////////////////// // The following methods are all called from the RFB thread - public CConn(VncViewer viewer_, java.net.Socket sock_, + public CConn(VncViewer viewer_, Socket sock_, String vncServerName, boolean reverse) { serverHost = null; serverPort = 0; sock = sock_; viewer = viewer_; @@ -184,7 +189,7 @@ public class CConn extends CConnection initMenu(); if (sock != null) { - String name = sock.getRemoteSocketAddress()+"::"+sock.getPort(); + String name = sock.getPeerEndpoint(); vlog.info("Accepted connection from "+name); } else { if (vncServerName != null) { @@ -201,26 +206,17 @@ public class CConn extends CConnection } try { - sock = new java.net.Socket(serverHost, serverPort); - } catch (java.io.IOException e) { + sock = new TcpSocket(serverHost, serverPort); + } catch (java.lang.Exception e) { throw new Exception(e.toString()); } vlog.info("connected to host "+serverHost+" port "+serverPort); } - sameMachine = (sock.getLocalSocketAddress() == sock.getRemoteSocketAddress()); - try { - sock.setTcpNoDelay(true); - sock.setTrafficClass(0x10); - setServerName(serverHost); - jis = new JavaInStream(sock.getInputStream()); - jos = new JavaOutStream(sock.getOutputStream()); - } catch (java.net.SocketException e) { - throw new Exception(e.toString()); - } catch (java.io.IOException e) { - throw new Exception(e.toString()); - } - setStreams(jis, jos); + sameMachine = sock.sameMachine(); + sock.inStream().setBlockCallback(this); + setServerName(serverHost); + setStreams(sock.inStream(), sock.outStream()); initialiseProtocol(); } @@ -236,8 +232,20 @@ public class CConn extends CConnection if (viewport != null) viewport.dispose(); viewport = null; + System.exit(1); } + // blockCallback() is called when reading from the socket would block. + public void blockCallback() { + try { + synchronized(this) { + wait(1); + } + } catch (java.lang.InterruptedException e) { + throw new Exception(e.toString()); + } + } + // getUserPasswd() is called by the CSecurity object when it needs us to read // a password from the user. @@ -342,18 +350,14 @@ public class CConn extends CConnection public void clientRedirect(int port, String host, String x509subject) { try { - getSocket().close(); + sock.close(); setServerPort(port); - sock = new java.net.Socket(host, port); - sock.setTcpNoDelay(true); - sock.setTrafficClass(0x10); - setSocket(sock); + sock = new TcpSocket(host, port); vlog.info("Redirected to "+host+":"+port); - setStreams(new JavaInStream(sock.getInputStream()), - new JavaOutStream(sock.getOutputStream())); + setStreams(sock.inStream(), sock.outStream()); initialiseProtocol(); - } catch (java.io.IOException e) { - e.printStackTrace(); + } catch (java.lang.Exception e) { + throw new Exception(e.toString()); } } @@ -455,14 +459,14 @@ public class CConn extends CConnection // avoid skewing the bandwidth estimation as a result of the server // being slow or the network having high latency public void beginRect(Rect r, int encoding) { - ((JavaInStream)getInStream()).startTiming(); + sock.inStream().startTiming(); if (encoding != Encodings.encodingCopyRect) { lastServerEncoding = encoding; } } public void endRect(Rect r, int encoding) { - ((JavaInStream)getInStream()).stopTiming(); + sock.inStream().stopTiming(); } public void fillRect(Rect r, int p) { @@ -579,8 +583,8 @@ public class CConn extends CConnection // with something more intelligent at the server end. // private void autoSelectFormatAndEncoding() { - long kbitsPerSecond = ((JavaInStream)getInStream()).kbitsPerSecond(); - long timeWaited = ((JavaInStream)getInStream()).timeWaited(); + long kbitsPerSecond = sock.inStream().kbitsPerSecond(); + long timeWaited = sock.inStream().timeWaited(); boolean newFullColour = fullColour; int newQualityLevel = cp.qualityLevel; @@ -673,12 +677,8 @@ public class CConn extends CConnection // close() closes the socket, thus waking up the RFB thread. public void close() { - try { - shuttingDown = true; - sock.close(); - } catch (java.io.IOException e) { - e.printStackTrace(); - } + shuttingDown = true; + sock.shutdown(); } // Menu callbacks. These are guaranteed only to be called after serverInit() @@ -718,13 +718,13 @@ public class CConn extends CConnection void showInfo() { JOptionPane.showMessageDialog(viewport, "Desktop name: "+cp.name()+"\n" - +"Host: "+serverHost+":"+sock.getPort()+"\n" + +"Host: "+serverHost+":"+serverPort+"\n" +"Size: "+cp.width+"x"+cp.height+"\n" +"Pixel format: "+desktop.getPF().print()+"\n" +"(server default "+serverPF.print()+")\n" +"Requested encoding: "+Encodings.encodingName(currentEncoding)+"\n" +"Last used encoding: "+Encodings.encodingName(lastServerEncoding)+"\n" - +"Line speed estimate: "+((JavaInStream)getInStream()).kbitsPerSecond()+" kbit/s"+"\n" + +"Line speed estimate: "+sock.inStream().kbitsPerSecond()+" kbit/s"+"\n" +"Protocol version: "+cp.majorVersion+"."+cp.minorVersion+"\n" +"Security method: "+Security.secTypeName(csecurity.getType()) +" ["+csecurity.description()+"]", @@ -1276,8 +1276,6 @@ public class CConn extends CConnection } // the following never change so need no synchronization: - JavaInStream jis; - JavaOutStream jos; // viewer object is only ever accessed by the GUI thread so needs no @@ -1319,6 +1317,7 @@ public class CConn extends CConnection public String serverHost; public int serverPort; + public Socket sock; public int menuKey; PixelFormat serverPF; ViewportFrame viewport; diff --git a/java/com/tigervnc/vncviewer/VncViewer.java b/java/com/tigervnc/vncviewer/VncViewer.java index 97b9427c..c080f98c 100644 --- a/java/com/tigervnc/vncviewer/VncViewer.java +++ b/java/com/tigervnc/vncviewer/VncViewer.java @@ -34,14 +34,13 @@ import java.awt.Image; import java.io.InputStream; import java.io.IOException; import java.lang.Character; -import java.net.ServerSocket; -import java.net.Socket; import java.util.jar.Attributes; import java.util.jar.Manifest; import javax.swing.*; import com.tigervnc.rdr.*; import com.tigervnc.rfb.*; +import com.tigervnc.network.*; public class VncViewer extends java.applet.Applet implements Runnable { @@ -193,31 +192,32 @@ public class VncViewer extends java.applet.Applet implements Runnable public void run() { CConn cc = null; Socket sock = null; + if (listenMode.getValue()) { int port = 5500; - ServerSocket listener = null; + if (vncServerName.getValue() != null && Character.isDigit(vncServerName.getValue().charAt(0))) port = Integer.parseInt(vncServerName.getValue()); + TcpListener listener = null; try { - listener = new ServerSocket(port); - } catch (IOException e) { - System.out.println("Could not listen on port: "+port); - System.exit(-1); + listener = new TcpListener(null, port); + } catch (java.lang.Exception e) { + System.out.println(e.toString()); + System.exit(1); } vlog.info("Listening on port "+port); - try { + while (true) { sock = listener.accept(); - listener.close(); - } catch (IOException e) { - System.out.println("Accept failed: "+port); - System.exit(-1); + if (sock != null) + break; + //listener.close(); } - } + try { cc = new CConn(this, sock, vncServerName.getValue(), false); while (true) |