package com.laytonsmith.core.functions;
/**
*
*/
public class Federation {
public static String docs() {
return "The Federation class of functions allows for servers to connect to other servers, and run code on the remote machine.";
}
// @api
// @noboilerplate
// @seealso(federation_remote_allow.class)
// @hide("Until this is finished, it is hidden.")
// public static class federation_remote_connect extends AbstractFunction {
//
// @Override
// public Exceptions.ExceptionType[] thrown() {
// return new ExceptionType[]{ExceptionType.CastException, ExceptionType.FormatException};
// }
//
// @Override
// public boolean isRestricted() {
// return true;
// }
//
// @Override
// public Boolean runAsync() {
// return null;
// }
//
// @Override
// public Construct exec(final Target t, final Environment environment, Construct... args) throws ConfigRuntimeException {
// final CArray connection_data = Static.AssertType(CArray.class, args, 0, this, t);
// final CClosure remote_callback = Static.AssertType(CClosure.class, args, 1, this, t);
// final CClosure local_callback;
// if (args.length >= 3) {
// local_callback = Static.AssertType(CClosure.class, args, 2, this, t);
// } else {
// local_callback = null;
// }
// final String host;
// final String server_name;
// final int timeout;
// final String password;
// final File remote_public_key;
// final int master_port;
// if (connection_data.containsKey("host")) {
// host = connection_data.get("host").val();
// } else {
// host = "localhost";
// }
// if (connection_data.containsKey("server_name")) {
// server_name = connection_data.get("server_name").val();
// } else {
// throw new ConfigRuntimeException("", ExceptionType.FormatException, t);
// }
// if (connection_data.containsKey("timeout")) {
// timeout = Static.getInt32(connection_data.get("timeout"), t);
// } else {
// timeout = 30;
// }
// if (connection_data.containsKey("password")) {
// password = connection_data.get("password").val();
// } else {
// password = null;
// }
// if (connection_data.containsKey("remote_public_key")) {
// remote_public_key = Static.GetFileFromArgument(connection_data.get("remote_public_key").val(), environment, t, null);
// } else {
// remote_public_key = null;
// }
// if (connection_data.containsKey("master_port")) {
// master_port = Static.getInt32(connection_data.get("master_port"), t);
// } else {
// master_port = com.laytonsmith.core.federation.Federation.MASTER_PORT;
// }
// final DaemonManager dm = environment.getEnv(GlobalEnv.class).GetDaemonManager();
// // All the arguments are now parsed. Kick off a new thread.
//
// new Thread(new Runnable() {
//
// @Override
// public void run() {
// Socket s;
// // Search through the existing connections, and try to find it. If it's already connected, we'll reuse that socket.
// synchronized (FederationConnection.connectionEstablishmentLock) {
// if (federationConnections.containsKey(server_name)) {
// s = federationConnections.get(server_name).socket;
// } else {
// s = new Socket();
// try {
// s.connect(new InetSocketAddress(host, master_port), timeout);
// } catch (SocketTimeoutException ex) {
// if (local_callback != null) {
// StaticLayer.GetConvertor().runOnMainThreadLater(dm, new Runnable() {
//
// @Override
// public void run() {
// local_callback.execute(CNull.NULL, CBoolean.TRUE, CNull.NULL);
// }
// });
// }
// return;
// } catch (IOException ex) {
// //Could not connect for some other reason
// if (local_callback != null) {
// final CArray exception = ObjectGenerator.GetGenerator().exception(new ConfigRuntimeException(ex.getMessage(), ExceptionType.IOException, t, ex), t);
// StaticLayer.GetConvertor().runOnMainThreadLater(dm, new Runnable() {
//
// @Override
// public void run() {
// local_callback.execute(CNull.NULL, CBoolean.TRUE, exception);
// }
// });
// }
// return;
// }
// // Now the socket is connected, but we need to handshake with the server, and possibly connect to another
// // server, if the master isn't the right connection.
// try(
// PrintWriter out = new PrintWriter(s.getOutputStream(), true);
// BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));
// ){
// // As always, first the version we're talking in.
// out.println(FederationVersion.V1_0_0.getVersionString());
// // Then the hello
// out.println("HELLO");
// //
// } catch (IOException ex) {
// Logger.getLogger(Federation.class.getName()).log(Level.SEVERE, null, ex);
// }
// }
// }
// // Ok, the socket is valid, connected, and the handshake is running. We can start writing the data out.
// }
// }, Implementation.GetServerType().getBranding() + "-Remote-Connect (" + server_name + ")").start();
//
// return new CVoid(t);
// }
//
// @Override
// public String getName() {
// return "federation_remote_connect";
// }
//
// @Override
// public Integer[] numArgs() {
// return new Integer[]{2, 3};
// }
//
// @Override
// public String docs() {
// return "void {connection_data, remote_callback, [local_callback(remoteReturn, timeoutError, exception)]} Connects to the specified server if not already background connected, and runs"
// + " the specified remote_callback closure on the remote server. ----"
// + " The connection data is an array of connection parameters, which are used to find the server and connect. Connections"
// + " may be cached and kept alive for future commands. The connection data is an array with the following parameters:\n"
// + "\n"
// + "{|\n"
// + "|-\n"
// + "! Key !! Type !! Default (if optional) !! Description\n"
// + "|-\n"
// + "| host || string || \"localhost\" || The host machine. This should be an IP address or a hostname.\n"
// + "|-\n"
// + "| server_name || string || <required> || The server name. This will be specified via the {{function|federation_remote_allow}}"
// + " function.\n"
// + "|-\n"
// + "| timeout || int || 30 || The timeout, in seconds, before the connection attempt will fail. If the server does not respond"
// + " in this time period, the local_callback will be called with an error (if provided).\n"
// + "|-\n"
// + "| password || string || null || If the remote requires a password (local or not) this should be set.\n"
// + "|-\n"
// + "| private_key || string || null || The file system path to the RSA private key that will be used to attempt"
// + " to connect, if the remote server expects this server to provide one. This path is not subject to the base-dir"
// + " restriction.\n"
// + "|-\n"
// + "| remote_public_key || string || null || The file system path to the RSA public key of the remote server. If using"
// + " public/private keypairs, this ensures that you are connecting to the server you think. This is an optional level"
// + " of security, but is recommended for remote host connections. This path is not subject to the base-dir restriction.\n"
// + "|-\n"
// + "| master_port || int || " + MASTER_PORT + " || The master port of the remote. This value must match the remote for the connection"
// + " to succeed.\n"
// + "|}"
// + "\n"
// + "The remote callback is a closure, which is run with the current variable environment, but other environment factors (current"
// + " player, current event, current interval/timeout, etc.) are lost. The remote callback is run as the console/system user on the"
// + " remote system, though it is checked that the current script could elevate to an administrative user, as if {{function|sudo}} "
// + " were being run, before"
// + " the connection will succeed. No parameters are sent to the remote callback, though if the remote callback returns a value,"
// + " it is retrieved from the remote system, and handed off to the local_callback (if provided). The local_callback is run once"
// + " the remote callback finishes (or a connection timeout occurs). It should accept three parameters, the mixed remoteReturn,"
// + " the boolean timeoutError, and the array exception. If there is a timeout error, remoteReturn will be null,"
// + " timeoutError will be true, and exception will be null. If the connection could otherwise not succeed (invalid host, etc),"
// + " then remoteReturn will be null, timeoutError will be true, and exception will have an exception."
// + " If the connection succeeded timeoutError will always be false."
// + " If the connection succeeded, and the closure did not cause an exception, and it returned a value, that value will be"
// + " provided as remoteReturn, and exception will be null; if the closure caused an exception, remoteReturn will be null,"
// + " and exception will contain the exception that was generated on the remote. If the remote connection succeeded, but was"
// + " refused by the remote server, remoteReturn will be null, and timeoutError will be false, but exception will not be null."
// + " The connection could be refused for several reasons, see {{function|federation_remote_allow}} for further details on"
// + " how to allow connections.";
// }
//
// @Override
// public Version since() {
// return CHVersion.V3_3_1;
// }
//
// @Override
// public ExampleScript[] examples() throws ConfigCompileException {
// return new ExampleScript[]{
// new ExampleScript("Basic usage, connecting to another local server",
// "federation_remote_connect(array(\n"
// + "\thost: 'localhost',\n"
// + "\tserver_name: 'myServer2',\n"
// + "\tpassword: 'server2password'\n"
// + "), closure(){\n"
// + "return(all_players())\n"
// + "}, closure(@remoteReturn, @timeoutError, @exception){\n"
// + "\tif(@exception){\n"
// + "\t\tthrow(@exception);\n"
// + "\t}\n"
// + "\tif(@timeoutError){\n"
// + "\t\tmsg('A timeout error occured.');\n"
// + "\t\treturn();\n"
// + "\t}\n"
// + "\t// else the remoteReturn value is usable\n"
// + "\tif(@remoteReturn !== null){\n"
// + "\t\tmsg(@remoteReturn);\n"
// + "\t}"
// + "}",
// "(Assuming the connection succeeded, then a list of all players on the other server will print out.)"),};
// }
//
// }
//
// @api
// @noboilerplate
// @seealso({federation_remote_connect.class, federation_remote_revoke.class})
// @hide("Until this is finished, it is hidden.")
// public static class federation_remote_allow extends AbstractFunction {
//
// @Override
// public Exceptions.ExceptionType[] thrown() {
// return new ExceptionType[]{ExceptionType.CastException, ExceptionType.FormatException, ExceptionType.RangeException};
// }
//
// @Override
// public boolean isRestricted() {
// return true;
// }
//
// @Override
// public Boolean runAsync() {
// return null;
// }
//
// @Override
// public Construct exec(Target t, final Environment environment, Construct... args) throws ConfigRuntimeException {
// final String server_name;
// final String password;
// final File authorized_keys;
// final String allow_from;
// final int master_port;
//
// if (!(args[0] instanceof CArray)) {
// throw new Exceptions.CastException("Expecting argument 1 to be an array.", t);
// }
//
// CArray connection_details = (CArray) args[0];
// if (connection_details.containsKey("server_name")) {
// server_name = connection_details.get("server_name").val();
// } else {
// throw new Exceptions.FormatException("server_name is a required key in " + getName(), t);
// }
// if (connection_details.containsKey("password")) {
// password = connection_details.get("password").val();
// } else {
// password = null;
// }
// if (connection_details.containsKey("authorized_keys")) {
// authorized_keys = Static.GetFileFromArgument(connection_details.get("authorized_keys").val(), environment, t, null);
// } else {
// authorized_keys = null;
// }
// if (connection_details.containsKey("allow_from")) {
// allow_from = connection_details.get("allow_from").val();
// } else {
// throw new Exceptions.FormatException("allow_from is a required key in " + getName(), t);
// }
// if (connection_details.containsKey("master_port")) {
// master_port = Static.getInt32(connection_details.get("master_port"), t);
// } else {
// master_port = MASTER_PORT;
// }
// if (master_port < 0 || master_port > 65535) {
// throw new Exceptions.RangeException("master_port must be between 0 and 65535.", t);
// }
//
// if (federationServers.containsKey(server_name)) {
// // Quick check to see if the server is already registered on this server. We still can't assume that it's globally
// // unregistered though, we have to check in the registration database.
// throw new Exceptions.FormatException("A server with the name \"" + server_name + "\" is already registered.", t);
// }
//
// final PersistenceNetwork pn = environment.getEnv(GlobalEnv.class).GetPersistenceNetwork();
// final DaemonManager dm = environment.getEnv(GlobalEnv.class).GetDaemonManager();
// // Argument parsing and validation is now done.
// final boolean is_master;
// try {
// if (pn.hasKey(new String[]{"federation", server_name})) {
// FederationRegistration reg = FederationRegistration.fromJSON(pn.get(new String[]{"federation", server_name}));
// if (reg.updatedSince(DEAD_SERVER_TIMEOUT * 1000)) {
// // There's already a server that has reported in in the last X seconds, so we need
// // to error out.
// throw new Exceptions.FormatException("A server with the name \"" + server_name + "\" is already registered.", t);
// }
// }
// is_master = available(master_port);
// RegisterServer(pn, dm, server_name, master_port, is_master);
// } catch (DataSourceException | IllegalArgumentException | IOException ex) {
// throw new ConfigRuntimeException(ex.getMessage(), ExceptionType.IOException, t, ex);
// } catch (ReadOnlyException ex) {
// HandleReadOnlyException(ex);
// return null; //Won't happen.
// }
//
// // Now we definitively know that we are the only server on the system named server_name, so let's register it real quick.
// // Add the server to our local connection map
// final FederationServer server = new FederationServer(server_name, password, authorized_keys, allow_from, master_port);
// federationServers.put(server_name, server);
//
// synchronized (serverCountLock) {
// if (serverCount == 0) {
// dm.activateThread(null);
// }
// serverCount++;
// }
//
// if (is_master) {
// // We have to do this on the main thread, otherwise it runs a higher risk of becoming unavailable due
// // to threading issues.
// StartMasterSocket(pn, dm, server_name, master_port);
// } else {
// // At this stage, we're going to handle everything else async, since all the user
// // parameters have been validated, and everything after this will be considered
// // a programming error.
// new Thread(new Runnable() {
//
// @Override
// public void run() {
// while (true) {
// try {
// // Else, we need to ask the server for a port, then listen on that port.
// int port;
// Socket masterSocket = new Socket("localhost", master_port);
// try (
// OutputStream os = masterSocket.getOutputStream();
// InputStream is = new BufferedInputStream(masterSocket.getInputStream());
// PrintWriter out = new PrintWriter(os, true);
// BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));) {
// out.println(FederationVersion.V1_0_0.getVersionString());
// out.println("GET PORT");
// port = Integer.parseInt(reader.readLine());
// }
// // Update the registration's data on this server.
// RegisterServer(pn, dm, server_name, port, false);
// // Close the connection to the master now, and open up a new connection on the port it gave us.
// ServerSocket socket = new ServerSocket(port);
// server.setServerSocket(socket);
// // We're also going to set up a heartbeat thread, which will update our heartbeat every X seconds in the registration table.
// AddShutdownHook(pn, dm, socket, server_name);
// AddHeartbeatThread(pn, dm, server_name, socket);
// while (!socket.isClosed()) {
// Socket s = socket.accept();
// HandleConnection(pn, s);
// }
// } catch (ConnectException ex) {
// // The master socket has been denied, so let's try to start up the master again.
// ServerSocket s = StartMasterSocket(pn, dm, server_name, master_port);
// // If the master socket is now us, we can break.
// if (s.getLocalPort() == master_port) {
// break;
// } else {
// // Otherwise restart ourselves.
// continue;
// }
// } catch (IOException | DataSourceException ex) {
// Logger.getLogger(Federation.class.getName()).log(Level.SEVERE, null, ex);
// } catch (ReadOnlyException ex) {
// try {
// HandleReadOnlyException(ex);
// } catch (ConfigRuntimeException ee) {
// ConfigRuntimeException.HandleUncaughtException(ee, environment);
// }
// }
// break;
// }
// }
//
// }, Implementation.GetServerType().getBranding() + "-Federation-Allow-" + server_name).start();
// }
// return new CVoid(t);
// }
//
// @Override
// public String getName() {
// return "federation_remote_allow";
// }
//
// @Override
// public Integer[] numArgs() {
// return new Integer[]{1};
// }
//
// @Override
// public String docs() {
// return "void {connection_details} Allows a remote connection to be established to this server, using {{function|federation_remote_connect}}"
// + " ---- "
// + "The connection_details is an array which expects the following parameters:\n\n"
// + "{|\n"
// + "|-\n"
// + "! Key !! Type !! Default (if optional) !! Description\n"
// + "|-\n"
// + "| server_name || string || <required> || The name that this server should register as. If the"
// + " server name is already registered on this host, it will cause a FormatException to be thrown.\n"
// + "|-\n"
// + "| password || string || null || The server password. Any remotes trying to connect to this server"
// + " must provide the password in order to successfully connect. This method is less secure than using"
// + " PKI (public/private keypairs) but can be used in addition for an added layer of security."
// + "|-\n"
// + "| authorized_keys || string || null || The path to the server's authorized keys list. If provided, the remote"
// + " is required to also specify their private key, and the connections will succeed only if the remote is added"
// + " to this file. This file should follow the same format as SSH's authorized keys file, meaning if the remote"
// + " could ssh into the server, this connection would also work, though it is not required that the file be the"
// + " same as SSH's file. This file path is not subject to the base-dir restriction.\n"
// + "|-\n"
// + "| allow_from || string || <required> || A comma separated list of remotes' IP addresses that are allowed to connect."
// + " The typical default is probably \"localhost\", but that must be specified specifically. If the string is \"*\", then"
// + " connections from all remotes will be allowed. If this is the case, it is extremely highly recommended to use the"
// + " authorized_keys mechanism. If this is set to \"*\", and authorized_keys is null, a warning is issued.\n"
// + "|-\n"
// + "| master_port || int || " + MASTER_PORT + " || The master port that will be bound to if this server determines it is the"
// + " master server for that port. In general, this should be the same for all servers, but in order for a remote"
// + " to connect to this server, it must be using the same master port.\n"
// + "|}\n\n"
// + "A server may register multiple unique server names. Each server name may contain various connection restrictions, and"
// + " the connection restrictions will be followed for the server name that the remote connects to.\n\n"
// + "Until a call to federation_remote_allow() is made, the server will not be listening for connections from remotes. Once"
// + " it is called, the server will begin listening for connections, though it will actively deny any connections that don't"
// + " meet the connection criteria. The first server to start up a listener will begin listening on port " + MASTER_PORT + " (or whichever is"
// + " specified as the master port), and will"
// + " act as the master server for that machine. It will direct requests that are meant for other machines to them as they"
// + " come in.";
// }
//
// @Override
// public Version since() {
// return CHVersion.V3_3_1;
// }
//
// }
//
// @api
// @noboilerplate
// @seealso({federation_remote_allow.class, federation_remote_connect.class})
// @hide("This is hidden until finished.")
// public static class federation_remote_revoke extends AbstractFunction {
//
// @Override
// public ExceptionType[] thrown() {
// return new ExceptionType[]{ExceptionType.FormatException};
// }
//
// @Override
// public boolean isRestricted() {
// return true;
// }
//
// @Override
// public Boolean runAsync() {
// return null;
// }
//
// @Override
// public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
// String server_name = args[0].val();
//
// for (FederationServer server : federationServers.values()) {
// if (server.server_name.equals(server_name)) {
// try {
// // Simply closing the socket should shutdown all the appropriate threads correctly.
// server.getServerSocket().close();
// } catch (IOException ex) {
// throw new ConfigRuntimeException(ex.getMessage(), ExceptionType.IOException, t, ex);
// }
// break;
// }
// }
// federationServers.remove(server_name);
// synchronized (serverCountLock) {
// serverCount--;
// if (serverCount == 0) {
// environment.getEnv(GlobalEnv.class).GetDaemonManager().deactivateThread(null);
// }
// }
//
// return new CVoid(t);
// }
//
// @Override
// public String getName() {
// return "federation_remote_revoke";
// }
//
// @Override
// public Integer[] numArgs() {
// return new Integer[]{1};
// }
//
// @Override
// public String docs() {
// return "void {serverName} Given a previously registered server name in this server, this disconnects that connection"
// + " from all remotes, and prevents future requests from occuring. This \"closes\" the connections allowed by"
// + " {{function|federation_remote_allow}}. ---- If the server name doesn't exist on this server, (or has been"
// + " previously disconnected) a FormatException is thrown. If this was the last connection on this server, the"
// + " listener is shutdown as well, and future calls from remotes will timeout.";
// }
//
// @Override
// public Version since() {
// return CHVersion.V3_3_1;
// }
//
// }
}