// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package rdpclient; import java.net.InetAddress; import java.net.UnknownHostException; import rdpclient.adapter.AwtRdpKeyboardAdapter; import rdpclient.adapter.AwtRdpMouseAdapter; import rdpclient.hyperv.ClientPreConnectionBlob; import rdpclient.ntlmssp.ClientNtlmsspNegotiate; import rdpclient.ntlmssp.ClientNtlmsspPubKeyAuth; import rdpclient.ntlmssp.ClientNtlmsspUserCredentials; import rdpclient.ntlmssp.NtlmState; import rdpclient.ntlmssp.ServerNtlmsspChallenge; import rdpclient.ntlmssp.ServerNtlmsspPubKeyPlus1; import rdpclient.rdp.ClientConfirmActivePDU; import rdpclient.rdp.ClientFastPathPDU; import rdpclient.rdp.ClientInfoPDU; import rdpclient.rdp.ClientMCSAttachUserRequest; import rdpclient.rdp.ClientMCSChannelJoinRequestServerMCSChannelConfirmPDUs; import rdpclient.rdp.ClientMCSConnectInitial; import rdpclient.rdp.ClientMCSErectDomainRequest; import rdpclient.rdp.ClientTpkt; import rdpclient.rdp.ClientX224ConnectionRequestPDU; import rdpclient.rdp.ClientX224DataPDU; import rdpclient.rdp.RdpConstants; import rdpclient.rdp.RdpState; import rdpclient.rdp.ServerBitmapUpdate; import rdpclient.rdp.ServerDemandActivePDU; import rdpclient.rdp.ServerFastPath; import rdpclient.rdp.ServerIOChannelRouter; import rdpclient.rdp.ServerLicenseErrorPDUValidClient; import rdpclient.rdp.ServerMCSAttachUserConfirmPDU; import rdpclient.rdp.ServerMCSConnectResponse; import rdpclient.rdp.ServerMCSPDU; import rdpclient.rdp.ServerPaletteUpdate; import rdpclient.rdp.ServerX224ConnectionConfirmPDU; import rdpclient.rdp.ServerX224DataPdu; import streamer.PipelineImpl; import streamer.Queue; import streamer.ssl.SSLState; import streamer.ssl.UpgradeSocketToSSL; import common.AwtKeyEventSource; import common.AwtMouseEventSource; import common.BufferedImageCanvas; import common.ScreenDescription; import common.adapter.AwtCanvasAdapter; public class RdpClient extends PipelineImpl { AwtMouseEventSource mouseEventSource = null; AwtKeyEventSource keyEventSource = null; /** * Name of last OneTimePacket in handshake sequence. */ private static final String HANDSHAKE_END = "server_valid_client"; /** * Create new RDP or HyperV cli * * @param id * id of this element * @param userName * user name * @param password * password * @param pcb * pre-connection blob for HyperV server or null/empty string to * disable. Usually, HyperV VM ID, e.g. * "39418F90-6D03-468E-B796-91C60DD6653A". * @param screen * screen description to fill * @param canvas * canvas to draw on * @param sslState */ public RdpClient(String id, String serverHostName, String domain, String userName, String password, String pcb, ScreenDescription screen, BufferedImageCanvas canvas, SSLState sslState) { super(id); assembleRDPPipeline(serverHostName, domain, userName, password, pcb, screen, canvas, sslState); } // /* DEBUG */ // @Override // protected HashMap<String, streamer.Element> initElementMap(String id) { // HashMap<String, streamer.Element> map = new HashMap<String, streamer.Element>(); // map.put("IN", new ServerPacketSniffer("server <")); // map.put("OUT", new ClientPacketSniffer("> client")); // return map; // } /** * Assemble connection sequence and main pipeline. * * Connection sequence for RDP w/o NLA: cookie(TPKT) SSL x224(TPKT) * main(FastPath). * * Connection sequence for RDP w NLA: cookie(TPKT) SSL credssp x224(TPKT) * main(FastPath). * * Connection sequence for HyperV w NLA: pcb SSL credssp cookie(TPKT) * x224(TPKT) main(FastPath). */ protected void assembleRDPPipeline(String serverHostName, String domain, String userName, String password, String pcb, ScreenDescription screen, BufferedImageCanvas canvas, SSLState sslState) { // If preconnection blob with VM ID is specified, then we are connecting to // HyperV server boolean hyperv = (pcb != null && !pcb.isEmpty()); // HyperV server requires NLA (CredSSP/SPNEGO/NTLMSSP) to connect, because // it cannot display login screen boolean credssp = hyperv || (password != null && !password.isEmpty()); String workstation; try { workstation = InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { workstation = "workstation"; } // // Handshake chain // RdpState state = new RdpState(); NtlmState ntlmState = new NtlmState(); int[] channelsToJoin = new int[] {RdpConstants.CHANNEL_IO, // RdpConstants.CHANNEL_RDPRDR, // RDPRDR channel is not used in current // version // RdpConstants .CHANNEL_CLIPRDR // Clipboard channel is refused to join :-/ }; // Add elements // If pre-connection blob is specified, then add element to send it as // first packet if (hyperv) { add(new ClientPreConnectionBlob("pcb", pcb)); } // If password is specified, then use CredSSP/NTLM (NTLMSSP) int protocol = RdpConstants.RDP_NEG_REQ_PROTOCOL_SSL; if (credssp) { protocol = RdpConstants.RDP_NEG_REQ_PROTOCOL_HYBRID; add( new ClientNtlmsspNegotiate("client_ntlmssp_nego", ntlmState), new ServerNtlmsspChallenge("server_ntlmssp_challenge", ntlmState), new ClientNtlmsspPubKeyAuth("client_ntlmssp_auth", ntlmState, sslState, serverHostName, domain, workstation, userName, password), new ServerNtlmsspPubKeyPlus1("server_ntlmssp_confirm", ntlmState), new ClientNtlmsspUserCredentials("client_ntlmssp_finish", ntlmState) ); } add(new ClientX224ConnectionRequestPDU("client_connection_req", userName, protocol), new ServerX224ConnectionConfirmPDU("server_connection_conf"), new UpgradeSocketToSSL("upgrade_to_ssl"), new ClientMCSConnectInitial("client_initial_conference_create"), new ServerMCSConnectResponse("server_initial_conference_create"), new ClientMCSErectDomainRequest("client_erect_domain"), new ClientMCSAttachUserRequest("client_atach_user"), new ServerMCSAttachUserConfirmPDU("server_atach_user_confirm", state), new ClientMCSChannelJoinRequestServerMCSChannelConfirmPDUs("client_channel_join_rdprdr", channelsToJoin, state), new ClientInfoPDU("client_info_req", userName), new ServerLicenseErrorPDUValidClient("server_valid_client"), new ServerFastPath("server_fastpath"), // new ServerTpkt("server_tpkt"), new ServerX224DataPdu("server_x224_data"), // These TPKT and X224 wrappers are connected directly to OUT for // handshake sequence new ClientTpkt("client_tpkt_ot"), new ClientX224DataPDU("client_x224_data_ot") ); // If HyperV VM ID is set, then insert element which will send VM ID as // first packet of connection, before other packets if (hyperv) { // HyperV: pcb SSL credssp cookie x224 main. link("IN", // Pre Connection Blob "pcb", // Main (will be used after connection seq) or tpkt (to X224) "server_fastpath >tpkt", // SSL "upgrade_to_ssl", // CredSSP "client_ntlmssp_nego", "server_ntlmssp_challenge", "client_ntlmssp_auth", "server_ntlmssp_confirm", "client_ntlmssp_finish", // Cookie "client_connection_req", "server_connection_conf", // X224 "client_initial_conference_create"); for (String element : new String[] {"pcb", "client_ntlmssp_nego", "server_ntlmssp_challenge", "client_ntlmssp_auth", "server_ntlmssp_confirm", "client_ntlmssp_finish"}) { link(element + " >otout", element + "< OUT"); } } else { // RDP: cookie SSL (credssp) x224 main. link("IN", // Main or tpkt "server_fastpath >tpkt", // Cookie "client_connection_req", "server_connection_conf", // SSL "upgrade_to_ssl"); if (credssp) { // SSL link("upgrade_to_ssl", // CredSSP "client_ntlmssp_nego", "server_ntlmssp_challenge", "client_ntlmssp_auth", "server_ntlmssp_confirm", "client_ntlmssp_finish", // X224 "client_initial_conference_create"); for (String element : new String[] {"client_ntlmssp_nego", "server_ntlmssp_challenge", "client_ntlmssp_auth", "server_ntlmssp_confirm", "client_ntlmssp_finish"}) { link(element + " >otout", element + "< OUT"); } } else { link( // SSL "upgrade_to_ssl", // X224 "client_initial_conference_create"); } } link( // X224 "client_initial_conference_create", "server_initial_conference_create", "client_erect_domain", "server_x224_data", "client_atach_user", "server_atach_user_confirm", "client_channel_join_rdprdr", "client_info_req", "server_valid_client" ); // Chain for direct handshake responses (without involving of queue) link("client_x224_data_ot", "client_tpkt_ot", "client_tpkt_ot< OUT"); // Connect one time outputs to client TPKT input String tpkt_peers[] = new String[] {"client_connection_req", "server_connection_conf", "upgrade_to_ssl", "client_x224_data_ot"}; for (String element : tpkt_peers) { link(element + " >otout", element + "< client_tpkt_ot"); } // Connect one time outputs to client X224 input String x224_peers[] = new String[] {"client_initial_conference_create", "server_initial_conference_create", "client_erect_domain", "client_atach_user", "server_atach_user_confirm", "client_channel_join_rdprdr", "client_info_req", "server_valid_client"}; for (String element : x224_peers) { link(element + " >otout", element + "< client_x224_data_ot"); } // // Transition // add( // To transfer packets between input threads and output thread. new Queue("queue"), // Slow path: MultiChannel Support new ServerMCSPDU("server_mcs") ); // Last element of handshake sequence will wake up queue and and socket // output pull loop, which will switch links, between socket output and // queue, from push mode to pull mode. link(HANDSHAKE_END + " >queue", "queue", "OUT"); // Transition from handshake sequence for slow path packets link(HANDSHAKE_END, "server_mcs"); // // Main network // mouseEventSource = new AwtMouseEventSource("mouse"); keyEventSource = new AwtKeyEventSource("keyboard"); // Subscribe packet sender to various events canvas.addMouseListener(mouseEventSource); canvas.addMouseMotionListener(mouseEventSource); canvas.addKeyListener(keyEventSource); // Add elements add( new ServerIOChannelRouter("server_io_channel", state), new ServerDemandActivePDU("server_demand_active", screen, state), new ClientConfirmActivePDU("client_confirm_active", screen, state), new ServerBitmapUpdate("server_bitmap_update"), new AwtCanvasAdapter("canvas_adapter", canvas, screen), new ServerPaletteUpdate("server_palette", screen), keyEventSource, new AwtRdpKeyboardAdapter("keyboard_adapter"), mouseEventSource, new AwtRdpMouseAdapter("mouse_adapter"), // These FastPath, TPKT, and X224 wrappers are connected to queue new ClientTpkt("client_tpkt_queue"), new ClientX224DataPDU("client_x224_data_queue"), new ClientFastPathPDU("client_fastpath_queue")); // Server packet handlers link("server_mcs >channel_1003", "server_io_channel"); link("server_fastpath >bitmap", "fastpath< server_bitmap_update", "server_bitmap_update< canvas_adapter"); link("server_io_channel >bitmap", "slowpath< server_bitmap_update"); link("server_fastpath >palette", "fastpath< server_palette"); link("server_io_channel >palette", "slowpath< server_palette"); link("server_io_channel >demand_active", "slowpath< server_demand_active"); // link("server_demand_active >confirm_active", "client_confirm_active", // "confirm_active< client_channel_1003"); link("server_demand_active >confirm_active", "client_confirm_active", "confirm_active< client_x224_data_queue"); // Link mouse and keyboard to socket via adapters and send them using // FastPath protocol link(mouseEventSource.getId(), "mouse_adapter", "mouse_adapter< client_fastpath_queue"); link(keyEventSource.getId(), "keyboard_adapter", "keyboard_adapter< client_fastpath_queue"); // Link packet wrappers to outgoing queue link("client_fastpath_queue", "client_fastpath_queue< queue"); link("client_x224_data_queue", "client_tpkt_queue", "client_tpkt_queue< queue"); } public AwtMouseEventSource getMouseEventSource() { return mouseEventSource; } public AwtKeyEventSource getKeyEventSource() { return keyEventSource; } }