/* 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 rfb; @SuppressWarnings({"unchecked", "deprecation", "serial", "fallthrough"}) abstract public class CConnection extends CMsgHandler { public CConnection() { state_ = RFBSTATE_UNINITIALISED; secTypes = new int[maxSecTypes]; } // Methods to initialise the connection public void setServerName(String name) { serverName = name; } // 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(rdr.InStream is_, rdr.OutStream os_) { is = is_; os = os_; } public void initSecTypes() { nSecTypes = 0; } // addSecType() should be called once for each security type which the // client supports. The order in which they're added is such that the // first one is most preferred. public void addSecType(int secType) { if (nSecTypes == maxSecTypes) throw new Exception("too many security types"); secTypes[nSecTypes++] = secType; } // 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 b) { useProtocol3_3 = b; } // 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_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_NORMAL: reader_.readMsg(); break; case RFBSTATE_UNINITIALISED: throw new Exception("CConnection.processMsg: not initialised yet?"); default: throw new Exception("CConnection.processMsg: invalid state"); } } // 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"); } // Other methods public CMsgReader reader() { return reader_; } public CMsgWriter writer() { return writer_; } public rdr.InStream getInStream() { return is; } public rdr.OutStream getOutStream() { return os; } public String getServerName() { return serverName; } 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; } void processVersionMsg() { vlog.debug("reading protocol version"); if (!cp.readVersion(is)) { state_ = RFBSTATE_INVALID; throw new Exception("reading version failed: not an RFB server?"); } 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); } void processSecurityTypesMsg() { vlog.debug("processing security types message"); int secType = SecTypes.invalid; if (cp.majorVersion == 3 && cp.minorVersion == 3) { // legacy 3.3 server may only offer "vnc authentication" or "none" secType = is.readU32(); if (secType == SecTypes.invalid) { throwConnFailedException(); } else if (secType == SecTypes.none || secType == SecTypes.vncAuth) { int j; for (j = 0; j < nSecTypes; j++) if (secTypes[j] == secType) break; if (j == nSecTypes) secType = SecTypes.invalid; } 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(); int secTypePos = nSecTypes; for (int i = 0; i < nServerSecTypes; i++) { int serverSecType = is.readU8(); vlog.debug("Server offers security type "+ SecTypes.name(serverSecType)+"("+serverSecType+")"); // If we haven't already chosen a secType, try this one if (secType == SecTypes.invalid || clientSecTypeOrder ) { for (int j = 0; j < nSecTypes; j++) { if (secTypes[j] == serverSecType && j < secTypePos) { secType = secTypes[j]; secTypePos = j; break; } } // Continue reading the remaining server secTypes, but ignore them } } if (secType != SecTypes.invalid) { os.writeU8(secType); os.flush(); vlog.debug("Choosing security type "+SecTypes.name(secType)+ "("+secType+")"); } } if (secType == SecTypes.invalid) { state_ = RFBSTATE_INVALID; vlog.error("No matching security types"); throw new Exception("No matching security types"); } state_ = RFBSTATE_SECURITY; security = getCSecurity(secType); processSecurityMsg(); } void processSecurityMsg() { vlog.debug("processing security message"); int rc = security.processMsg(this); if (rc == 0) throwAuthFailureException(); if (rc == 1) { state_ = RFBSTATE_SECURITY_RESULT; processSecurityResultMsg(); } } void processSecurityResultMsg() { vlog.debug("processing security result message"); int result; if (cp.beforeVersion(3,8) && security.getType() == SecTypes.none) { result = SecTypes.resultOK; } else { //if (!is->checkNoWait(1)) return; result = is.readU32(); } switch (result) { case SecTypes.resultOK: securityCompleted(); break; case SecTypes.resultFailed: vlog.debug("auth failed"); throwAuthFailureException(); case SecTypes.resultTooMany: vlog.debug("auth failed - too many tries"); throwAuthFailureException(); default: vlog.error("unknown security result"); throwAuthFailureException(); } } void processInitMsg() { vlog.debug("reading server initialisation"); reader_.readServerInit(); } 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); } void throwConnFailedException() { state_ = RFBSTATE_INVALID; String reason = is.readString(); throw new ConnFailedException(reason); } void securityCompleted() { state_ = RFBSTATE_INITIALISATION; reader_ = new CMsgReaderV3(this, is); writer_ = new CMsgWriterV3(cp, os); vlog.debug("Authentication success!"); authSuccess(); writer_.writeClientInit(shared); } rdr.InStream is; rdr.OutStream os; CMsgReader reader_; CMsgWriter writer_; boolean shared; CSecurity security; public static final int maxSecTypes = 8; int nSecTypes; int[] secTypes; int state_; String serverName; boolean useProtocol3_3; boolean clientSecTypeOrder; static LogWriter vlog = new LogWriter("CConnection"); }