| // 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; |
| } |
| } |