blob: 6cf3d7b3cb8f968b269a02456c1695479643a42a [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 org.apache.guacamole.protocol;
import java.util.List;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
import org.apache.guacamole.net.DelegatingGuacamoleSocket;
import org.apache.guacamole.net.GuacamoleSocket;
/**
* A GuacamoleSocket which pre-configures the connection based on a given
* GuacamoleConfiguration, completing the initial protocol handshake before
* accepting data for read or write.
*
* This is useful for forcing a connection to the Guacamole proxy server with
* a specific configuration while disallowing the client that will be using
* this GuacamoleSocket from manually controlling the initial protocol
* handshake.
*/
public class ConfiguredGuacamoleSocket extends DelegatingGuacamoleSocket {
/**
* The configuration to use when performing the Guacamole protocol
* handshake.
*/
private GuacamoleConfiguration config;
/**
* The unique identifier associated with this connection, as determined
* by the "ready" instruction received from the Guacamole proxy.
*/
private String id;
/**
* The protocol version that will be used to communicate with guacd. The
* default is 1.0.0, and, if the server does not provide a specific version
* it will be assumed that it operates at this version and certain features
* may be unavailable.
*/
private GuacamoleProtocolVersion protocolVersion =
GuacamoleProtocolVersion.VERSION_1_0_0;
/**
* Waits for the instruction having the given opcode, returning that
* instruction once it has been read. If the instruction is never read,
* an exception is thrown.
*
* @param reader The reader to read instructions from.
* @param opcode The opcode of the instruction we are expecting.
* @return The instruction having the given opcode.
* @throws GuacamoleException If an error occurs while reading, or if
* the expected instruction is not read.
*/
private GuacamoleInstruction expect(GuacamoleReader reader, String opcode)
throws GuacamoleException {
// Wait for an instruction
GuacamoleInstruction instruction = reader.readInstruction();
if (instruction == null)
throw new GuacamoleServerException("End of stream while waiting for \"" + opcode + "\".");
// Ensure instruction has expected opcode
if (!instruction.getOpcode().equals(opcode))
throw new GuacamoleServerException("Expected \"" + opcode + "\" instruction but instead received \"" + instruction.getOpcode() + "\".");
return instruction;
}
/**
* Creates a new ConfiguredGuacamoleSocket which uses the given
* GuacamoleConfiguration to complete the initial protocol handshake over
* the given GuacamoleSocket. A default GuacamoleClientInformation object
* is used to provide basic client information.
*
* @param socket The GuacamoleSocket to wrap.
* @param config The GuacamoleConfiguration to use to complete the initial
* protocol handshake.
* @throws GuacamoleException If an error occurs while completing the
* initial protocol handshake.
*/
public ConfiguredGuacamoleSocket(GuacamoleSocket socket,
GuacamoleConfiguration config) throws GuacamoleException {
this(socket, config, new GuacamoleClientInformation());
}
/**
* Creates a new ConfiguredGuacamoleSocket which uses the given
* GuacamoleConfiguration and GuacamoleClientInformation to complete the
* initial protocol handshake over the given GuacamoleSocket.
*
* @param socket The GuacamoleSocket to wrap.
* @param config The GuacamoleConfiguration to use to complete the initial
* protocol handshake.
* @param info The GuacamoleClientInformation to use to complete the initial
* protocol handshake.
* @throws GuacamoleException If an error occurs while completing the
* initial protocol handshake.
*/
public ConfiguredGuacamoleSocket(GuacamoleSocket socket,
GuacamoleConfiguration config,
GuacamoleClientInformation info) throws GuacamoleException {
super(socket);
this.config = config;
// Get reader and writer
GuacamoleReader reader = socket.getReader();
GuacamoleWriter writer = socket.getWriter();
// Get protocol / connection ID
String select_arg = config.getConnectionID();
if (select_arg == null)
select_arg = config.getProtocol();
// Send requested protocol or connection ID
writer.writeInstruction(new GuacamoleInstruction("select", select_arg));
// Wait for server args
GuacamoleInstruction args = expect(reader, "args");
// Build args list off provided names and config
List<String> arg_names = args.getArgs();
String[] arg_values = new String[arg_names.size()];
for (int i=0; i<arg_names.size(); i++) {
// Retrieve argument name
String arg_name = arg_names.get(i);
// Check for valid protocol version as first argument
if (i == 0) {
GuacamoleProtocolVersion version = GuacamoleProtocolVersion.parseVersion(arg_name);
if (version != null) {
// Use the lowest common version supported
if (version.atLeast(GuacamoleProtocolVersion.LATEST))
version = GuacamoleProtocolVersion.LATEST;
// Respond with the version selected
arg_values[i] = version.toString();
protocolVersion = version;
continue;
}
}
// Get defined value for name
String value = config.getParameter(arg_name);
// If value defined, set that value
if (value != null) arg_values[i] = value;
// Otherwise, leave value blank
else arg_values[i] = "";
}
// Send size
writer.writeInstruction(
new GuacamoleInstruction(
"size",
Integer.toString(info.getOptimalScreenWidth()),
Integer.toString(info.getOptimalScreenHeight()),
Integer.toString(info.getOptimalResolution())
)
);
// Send supported audio formats
writer.writeInstruction(
new GuacamoleInstruction(
"audio",
info.getAudioMimetypes().toArray(new String[0])
));
// Send supported video formats
writer.writeInstruction(
new GuacamoleInstruction(
"video",
info.getVideoMimetypes().toArray(new String[0])
));
// Send supported image formats
writer.writeInstruction(
new GuacamoleInstruction(
"image",
info.getImageMimetypes().toArray(new String[0])
));
// Send client timezone, if supported and available
if (GuacamoleProtocolCapability.TIMEZONE_HANDSHAKE.isSupported(protocolVersion)) {
String timezone = info.getTimezone();
if (timezone != null)
writer.writeInstruction(new GuacamoleInstruction("timezone", info.getTimezone()));
}
// Send args
writer.writeInstruction(new GuacamoleInstruction("connect", arg_values));
// Wait for ready, store ID
GuacamoleInstruction ready = expect(reader, "ready");
List<String> ready_args = ready.getArgs();
if (ready_args.isEmpty())
throw new GuacamoleServerException("No connection ID received");
id = ready.getArgs().get(0);
}
/**
* Returns the GuacamoleConfiguration used to configure this
* ConfiguredGuacamoleSocket.
*
* @return The GuacamoleConfiguration used to configure this
* ConfiguredGuacamoleSocket.
*/
public GuacamoleConfiguration getConfiguration() {
return config;
}
/**
* Returns the unique ID associated with the Guacamole connection
* negotiated by this ConfiguredGuacamoleSocket. The ID is provided by
* the "ready" instruction returned by the Guacamole proxy.
*
* @return The ID of the negotiated Guacamole connection.
*/
public String getConnectionID() {
return id;
}
/**
* Returns the version of the Guacamole protocol associated with the
* Guacamole connection negotiated by this ConfiguredGuacamoleSocket. This
* version is the lowest version common to both ConfiguredGuacamoleSocket
* and the relevant Guacamole proxy instance (guacd).
*
* @return
* The protocol version that this ConfiguredGuacamoleSocket will use to
* communicate with guacd.
*/
public GuacamoleProtocolVersion getProtocolVersion() {
return protocolVersion;
}
@Override
public String getProtocol() {
return getConfiguration().getProtocol();
}
}