/* 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. */ package com.iiordanov.tigervnc.rfb; import java.util.*; import com.iiordanov.bVNC.RemoteCanvas; import com.iiordanov.tigervnc.rdr.*; abstract public class CConnection extends CMsgHandler { public CConnection(RemoteCanvas v) { viewer = v; security = new SecurityClient(viewer); } // setStreams() sets the streams to be used for the connection. These must // be set before initialiseProtocol() and processMsg() are called. The // CSecurity object may call setStreams() again to provide alternative // streams over which the RFB protocol is sent (i.e. encrypting/decrypting // streams). Ownership of the streams remains with the caller // (i.e. SConnection will not delete them). public void setStreams(InStream is_, OutStream os_) { is = is_; os = os_; } // initialiseProtocol() should be called once the streams and security // types are set. Subsequently, processMsg() should be called whenever // there is data to read on the InStream. public void initialiseProtocol() { state_ = RFBSTATE_PROTOCOL_VERSION; } // processMsg() should be called whenever there is data to read on the // InStream. You must have called initialiseProtocol() first. public void processMsg() { switch (state_) { case RFBSTATE_NORMAL: reader_.readMsg(); break; case RFBSTATE_PROTOCOL_VERSION: processVersionMsg(); break; case RFBSTATE_SECURITY_TYPES: processSecurityTypesMsg(); break; case RFBSTATE_SECURITY: processSecurityMsg(); break; case RFBSTATE_SECURITY_RESULT: processSecurityResultMsg(); break; case RFBSTATE_INITIALISATION: processInitMsg(); break; case RFBSTATE_UNINITIALISED: throw new Exception("CConnection.processMsg: not initialised yet?"); default: throw new Exception("CConnection.processMsg: invalid state"); } } private void processVersionMsg() { vlog.debug("reading protocol version"); Boolean done = new Boolean(true); if (!cp.readVersion(is, done)) { state_ = RFBSTATE_INVALID; throw new Exception("reading version failed: not an RFB server?"); } if (!done.booleanValue()) return; vlog.info("Server supports RFB protocol version "+cp.majorVersion+"."+ cp.minorVersion); // The only official RFB protocol versions are currently 3.3, 3.7 and 3.8 if (cp.beforeVersion(3,3)) { String msg = ("Server gave unsupported RFB protocol version "+ cp.majorVersion+"."+cp.minorVersion); vlog.error(msg); state_ = RFBSTATE_INVALID; throw new Exception(msg); } else if (useProtocol3_3 || cp.beforeVersion(3,7)) { cp.setVersion(3,3); } else if (cp.afterVersion(3,8)) { cp.setVersion(3,8); } cp.writeVersion(os); state_ = RFBSTATE_SECURITY_TYPES; vlog.info("Using RFB protocol version "+ cp.majorVersion+"."+cp.minorVersion); } private void processSecurityTypesMsg() { vlog.info("processing security types message"); int secType = Security.secTypeInvalid; List<Integer> secTypes = new ArrayList<Integer>(); secTypes = Security.GetEnabledSecTypes(); //for (Iterator i = secTypes.iterator(); i.hasNext(); ) // vlog.info(((Integer)i.next()).toString()); if (cp.isVersion(3,3)) { // legacy 3.3 server may only offer "vnc authentication" or "none" secType = is.readU32(); if (secType == Security.secTypeInvalid) { throwConnFailedException(); } else if (secType == Security.secTypeNone || secType == Security.secTypeVncAuth) { Iterator i; for (i = secTypes.iterator(); i.hasNext(); ) { int refType = (Integer)i.next(); if (refType == secType) { secType = refType; break; } if (!i.hasNext()) secType = Security.secTypeInvalid; } } else { vlog.error("Unknown 3.3 security type "+secType); throw new Exception("Unknown 3.3 security type"); } } else { // 3.7 server will offer us a list int nServerSecTypes = is.readU8(); if (nServerSecTypes == 0) throwConnFailedException(); for (int i = 0; i < nServerSecTypes; i++) { int serverSecType = is.readU8(); vlog.info("Server offers security type "+ Security.secTypeName(serverSecType)+"("+serverSecType+")"); /* * Use the first type sent by server which matches client's type. * It means server's order specifies priority. */ if (secType == Security.secTypeInvalid) { for (Iterator j = secTypes.iterator(); j.hasNext(); ) { int refType = (Integer)j.next(); if (refType == serverSecType) { secType = refType; break; } } } } // Inform the server of our decision if (secType != Security.secTypeInvalid) { os.writeU8(secType); os.flush(); vlog.info("Choosing security type "+Security.secTypeName(secType)+ "("+secType+")"); } } if (secType == Security.secTypeInvalid) { state_ = RFBSTATE_INVALID; vlog.error("No matching security types"); throw new Exception("No matching security types"); } state_ = RFBSTATE_SECURITY; csecurity = security.GetCSecurity(secType); processSecurityMsg(); } private void processSecurityMsg() { vlog.debug("processing security message"); if (csecurity.processMsg(this)) { state_ = RFBSTATE_SECURITY_RESULT; processSecurityResultMsg(); } } private void processSecurityResultMsg() { vlog.debug("processing security result message"); int result; if (cp.beforeVersion(3,8) && csecurity.getType() == Security.secTypeNone) { result = Security.secResultOK; } else { if (!is.checkNoWait(1)) return; result = is.readU32(); } switch (result) { case Security.secResultOK: securityCompleted(); return; case Security.secResultFailed: vlog.debug("auth failed"); break; case Security.secResultTooMany: vlog.debug("auth failed - too many tries"); break; default: throw new Exception("Unknown security result from server"); } String reason; if (cp.beforeVersion(3,8)) reason = "Authentication failure"; else reason = is.readString(); state_ = RFBSTATE_INVALID; throw new AuthFailureException(reason); } private void processInitMsg() { vlog.debug("reading server initialisation"); reader_.readServerInit(); } private void throwConnFailedException() { state_ = RFBSTATE_INVALID; String reason; reason = is.readString(); throw new ConnFailedException(reason); } private void securityCompleted() { state_ = RFBSTATE_INITIALISATION; reader_ = new CMsgReaderV3(this, is, viewer); writer_ = new CMsgWriterV3(cp, os); vlog.debug("Authentication success!"); authSuccess(); writer_.writeClientInit(shared); } // Methods to initialise the connection // setServerName() is used to provide a unique(ish) name for the server to // which we are connected. This might be the result of getPeerEndpoint on // a TcpSocket, for example, or a host specified by DNS name & port. // The serverName is used when verifying the Identity of a host (see RA2). public void setServerName(String name) { serverName = name; } public void setServerPort(int port) { serverPort = port; } public void initSecTypes() { nSecTypes = 0; } // setShared sets the value of the shared flag which will be sent to the // server upon initialisation. public void setShared(boolean s) { shared = s; } // setProtocol3_3 configures whether or not the CConnection should // only ever support protocol version 3.3 public void setProtocol3_3(boolean s) { useProtocol3_3 = s; } // Methods to be overridden in a derived class // getCSecurity() gets the CSecurity object for the given type. The type // is guaranteed to be one of the secTypes passed in to addSecType(). The // CSecurity object's destroy() method will be called by the CConnection // from its destructor. //abstract public CSecurity getCSecurity(int secType); // getCurrentCSecurity() gets the CSecurity instance used for this // connection. //public CSecurity getCurrentCSecurity() { return security; } // setClientSecTypeOrder() determines whether the client should obey the // server's security type preference, by picking the first server security // type that the client supports, or whether it should pick the first type // that the server supports, from the client-supported list of types. public void setClientSecTypeOrder( boolean csto ) { clientSecTypeOrder = csto; } // authSuccess() is called when authentication has succeeded. public void authSuccess() {} // serverInit() is called when the ServerInit message is received. The // derived class must call on to CConnection::serverInit(). public void serverInit() { state_ = RFBSTATE_NORMAL; vlog.debug("initialisation done"); } public void continueAfterInit () { continueAfterInit = true; } // Other methods public CMsgReaderV3 reader() { return reader_; } public CMsgWriterV3 writer() { return writer_; } public InStream getInStream() { return is; } public OutStream getOutStream() { return os; } public String getServerName() { return serverName; } public int getServerPort() { return serverPort; } public static final int RFBSTATE_UNINITIALISED = 0; public static final int RFBSTATE_PROTOCOL_VERSION = 1; public static final int RFBSTATE_SECURITY_TYPES = 2; public static final int RFBSTATE_SECURITY = 3; public static final int RFBSTATE_SECURITY_RESULT = 4; public static final int RFBSTATE_INITIALISATION = 5; public static final int RFBSTATE_NORMAL = 6; public static final int RFBSTATE_INVALID = 7; public int state() { return state_; } protected void setState(int s) { state_ = s; } private void throwAuthFailureException() { String reason; vlog.debug("state="+state()+", ver="+cp.majorVersion+"."+cp.minorVersion); if (state() == RFBSTATE_SECURITY_RESULT && !cp.beforeVersion(3,8)) { reason = is.readString(); } else { reason = "Authentication failure"; } state_ = RFBSTATE_INVALID; vlog.error(reason); throw new AuthFailureException(reason); } protected RemoteCanvas viewer; InStream is = null; OutStream os = null; CMsgReaderV3 reader_ = null; CMsgWriterV3 writer_ = null; boolean shared = false; public CSecurity csecurity; public SecurityClient security; public static final int maxSecTypes = 8; int nSecTypes; int[] secTypes; int state_ = RFBSTATE_UNINITIALISED; String serverName; int serverPort; boolean useProtocol3_3 = false; boolean clientSecTypeOrder; boolean continueAfterInit = false; 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"); }