blob: a3db165c2fa5fd7d3e66fa105c22faf8dced01cd [file] [log] [blame]
// 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;
}
}