package com.forgeessentials.remote; import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.net.UnknownHostException; import java.security.GeneralSecurityException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Random; import java.util.Set; import net.minecraft.server.MinecraftServer; import net.minecraftforge.common.config.Configuration; import net.minecraftforge.permission.PermissionLevel; import com.forgeessentials.api.APIRegistry; import com.forgeessentials.api.UserIdent; import com.forgeessentials.api.permissions.Zone; import com.forgeessentials.api.remote.FERemoteHandler; import com.forgeessentials.api.remote.RemoteHandler; import com.forgeessentials.api.remote.RemoteManager; import com.forgeessentials.core.ForgeEssentials; import com.forgeessentials.core.misc.FECommandManager; import com.forgeessentials.core.misc.Translator; import com.forgeessentials.core.moduleLauncher.FEModule; import com.forgeessentials.core.moduleLauncher.config.ConfigLoader.ConfigLoaderBase; import com.forgeessentials.data.v2.DataManager; import com.forgeessentials.remote.command.CommandRemote; import com.forgeessentials.util.events.FEModuleEvent.FEModuleInitEvent; import com.forgeessentials.util.events.FEModuleEvent.FEModulePreInitEvent; import com.forgeessentials.util.events.FEModuleEvent.FEModuleServerInitEvent; import com.forgeessentials.util.events.FEModuleEvent.FEModuleServerStopEvent; import com.forgeessentials.util.output.LoggingHandler; import com.google.gson.Gson; import cpw.mods.fml.common.discovery.ASMDataTable; import cpw.mods.fml.common.discovery.ASMDataTable.ASMData; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.eventhandler.SubscribeEvent; @FEModule(name = "Remote", parentMod = ForgeEssentials.class, canDisable = true) public class ModuleRemote extends ConfigLoaderBase implements RemoteManager { public static class PasskeyMap extends HashMap<UserIdent, String> { private static final long serialVersionUID = -8268113844467318789L; /* default */ }; private static final String CONFIG_CAT = "Remote"; private static String certificateFilename = "FeRemote.jks"; private static String certificatePassword = "feremote"; public static final char[] PASSKEY_CHARS; public static final String PERM = RemoteHandler.PERM_REMOTE; public static final String PERM_CONTROL = PERM + ".control"; public static int passkeyLength = 6; static { // Build a character set with only easily distinguishable characters Set<Character> chars = new HashSet<Character>(); for (char c = 'a'; c <= 'z'; c++) chars.add(c); for (char c = 'A'; c <= 'Z'; c++) chars.add(c); for (char c = '0'; c <= '9'; c++) chars.add(c); chars.remove('o'); chars.remove('O'); chars.remove('0'); chars.remove('I'); PASSKEY_CHARS = new char[chars.size()]; int idx = 0; for (Character c : chars) PASSKEY_CHARS[idx++] = c; } /* ------------------------------------------------------------ */ @FEModule.Instance protected static ModuleRemote instance; protected int port; protected String hostname; protected boolean localhostOnly; protected boolean useSSL; protected Server server; protected Map<String, RemoteHandler> handlers = new HashMap<>(); protected PasskeyMap passkeys = new PasskeyMap(); private static ASMDataTable asmdata; /* ------------------------------------------------------------ */ @SubscribeEvent public void getASMDataTable(FEModulePreInitEvent e) { asmdata = ((FMLPreInitializationEvent) e.getFMLEvent()).getAsmData(); } /** * Register remote module and basic handlers */ @SuppressWarnings("deprecation") @SubscribeEvent public void load(FEModuleInitEvent e) { APIRegistry.remoteManager = this; APIRegistry.perms.registerPermission(PERM, PermissionLevel.OP, "Allows login to remote module"); APIRegistry.perms.registerPermission(PERM_CONTROL, PermissionLevel.OP, "Allows to start / stop remote server and control users (regen passkeys, kick, block)"); registerRemoteHandlers(); FECommandManager.registerCommand(new CommandRemote()); } private void registerRemoteHandlers() { for (ASMData asm : asmdata.getAll(FERemoteHandler.class.getName())) { try { Class<?> clazz = Class.forName(asm.getClassName()); if (RemoteHandler.class.isAssignableFrom(clazz)) { RemoteHandler handler = (RemoteHandler) clazz.newInstance(); FERemoteHandler annot = handler.getClass().getAnnotation(FERemoteHandler.class); registerHandler(handler, annot.id()); } } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { LoggingHandler.felog.debug("Could not load FERemoteHandler " + asm.getClassName()); } } } /** * Initialize passkeys, server and commands */ @SubscribeEvent public void serverStarting(FEModuleServerInitEvent e) { loadPasskeys(); startServer(); } /** * Stop remote server when the MC-server stops */ @SubscribeEvent public void serverStopping(FEModuleServerStopEvent e) { stopServer(); } /* * (non-Javadoc) * * @see * com.forgeessentials.core.moduleLauncher.config.IConfigLoader#load(net.minecraftforge.common.config.Configuration, * boolean) */ @Override public void load(Configuration config, boolean isReload) { localhostOnly = config.get(CONFIG_CAT, "localhostOnly", true, "Allow connections from the web").getBoolean(); hostname = config.get(CONFIG_CAT, "hostname", "localhost", "Hostname of your server. Used for QR code generation.").getString(); port = config.get(CONFIG_CAT, "port", 27020, "Port to connect remotes to").getInt(); useSSL = config.get(CONFIG_CAT, "use_ssl", false, "Protect the communication against network sniffing by encrypting traffic with SSL (You don't really need it - believe me)").getBoolean(); passkeyLength = config.get(CONFIG_CAT, "passkey_length", 6, "Length of the randomly generated passkeys").getInt(); if (MinecraftServer.getServer() != null && MinecraftServer.getServer().isServerRunning()) startServer(); } /* ------------------------------------------------------------ */ /** * @return the server */ public Server getServer() { return server; } /** * Starts up the remote server */ public void startServer() { if (server != null) return; try { String bindAddress = localhostOnly ? "localhost" : "0.0.0.0"; if (useSSL) { try { InputStream is = ClassLoader.getSystemResourceAsStream(certificateFilename); if (is != null) { SSLContextHelper sslCtxHelper = new SSLContextHelper(); sslCtxHelper.loadSSLCertificate(is, certificatePassword, certificatePassword); server = new Server(port, bindAddress, sslCtxHelper.getSSLCtx()); } else LoggingHandler.felog.error("[remote] Unable to load SSL certificate: File not found"); } catch (IOException | GeneralSecurityException e1) { LoggingHandler.felog.error("[remote] Unable to load SSL certificate: " + e1.getMessage()); } } else { server = new Server(port, bindAddress); } } catch (IOException e1) { LoggingHandler.felog.error("[remote] Unable to start remote-server: " + e1.getMessage()); } } /** * Stops the remote server */ public void stopServer() { if (server != null) { server.close(); server = null; } } /* * (non-Javadoc) * * @see com.forgeessentials.api.remote.RemoteManager#registerHandler(com.forgeessentials.api.remote.RemoteHandler) */ @Override public void registerHandler(RemoteHandler handler, String id) { if (handlers.containsKey(id)) throw new IllegalArgumentException(Translator.format( "Tried to register handler \"%s\" with ID \"%s\", but handler \"%s\" is already registered with that ID.", handler.getClass().getName(), id, handlers.get(id).getClass().getName())); handlers.put(id, handler); String perm = handler.getPermission(); if (perm != null && APIRegistry.perms.getServerZone().getRootZone().getGroupPermission(Zone.GROUP_DEFAULT, perm) == null) APIRegistry.perms.registerPermission(perm, PermissionLevel.OP); } /* * (non-Javadoc) * * @see com.forgeessentials.api.remote.RemoteManager#getHandler(java.lang.String) */ @Override public RemoteHandler getHandler(String id) { return handlers.get(id); } /** * Get all registered remote-handlers */ public Map<String, RemoteHandler> getHandlers() { return handlers; } /** * Remote server port */ public int getPort() { return port; } /* ------------------------------------------------------------ */ /** * Generates a new random passkey */ public String generatePasskey() { StringBuilder passkey = new StringBuilder(); Random rnd = new Random(); for (int i = 0; i < passkeyLength; i++) passkey.append(PASSKEY_CHARS[rnd.nextInt(PASSKEY_CHARS.length)]); return passkey.toString(); } /** * Get stored passkey for user or generate a new one and save it * * @param userIdent */ public String getPasskey(UserIdent userIdent) { if (passkeys.containsKey(userIdent)) return passkeys.get(userIdent); String passkey = generatePasskey(); setPasskey(userIdent, passkey); return passkey; } /** * Set and save a new passkey for a user * * @param userIdent * @param passkey */ public void setPasskey(UserIdent userIdent, String passkey) { if (passkey == null) passkeys.remove(userIdent); else passkeys.put(userIdent, passkey); DataManager.getInstance().save(passkeys, "passkeys"); } /** * Load the passkeys from data-backend */ public void loadPasskeys() { passkeys = DataManager.getInstance().load(PasskeyMap.class, "passkeys"); if (passkeys == null) passkeys = new PasskeyMap(); } /* ------------------------------------------------------------ */ /** * Get Gson instance used for remote module */ @Override public Gson getGson() { return DataManager.getInstance().getGson(); } /** * Tries to get the hostname */ public String getHostName() { try { return InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { return "localhost"; } } /** * Generates a remote-connect string for the given user * * @param userIdent */ public String getConnectString(UserIdent userIdent) { if (!userIdent.hasUuid()) return null; return String.format("%s@%s:%d|%s", userIdent.getUsernameOrUuid(), (useSSL ? "ssl:" : "") + hostname, port, getPasskey(userIdent)); } /** * Get the instance of ModuleRemote */ public static ModuleRemote getInstance() { return instance; } }