package com.laytonsmith.core; import com.laytonsmith.PureUtilities.Preferences; import com.laytonsmith.PureUtilities.Preferences.Preference; import com.laytonsmith.PureUtilities.TermColors; import java.io.File; import java.io.IOException; import java.util.ArrayList; public final class Prefs { private Prefs() { } /** * Given a preference name, returns the preference value. * * @param name * @return */ public static Object pref(PNames name) { if (prefs == null) { //Uh oh. throw new RuntimeException("Preferences have not been initialized!"); } return prefs.getPreference(name.config()); } private static Preferences prefs; private static Thread watcherThread; /** * A list of preferences that are known to MethodScript. Additional preferences may be set in the preferences table, * but they will not be listed here. */ public static enum PNames { DEBUG_MODE("debug-mode"), SHOW_WARNINGS("show-warnings"), CONSOLE_LOG_COMMANDS("console-log-commands"), SCRIPT_NAME("script-name"), ENABLE_INTERPRETER("enable-interpreter"), BASE_DIR("base-dir"), PLAY_DIRTY("play-dirty"), CASE_SENSITIVE("case-sensitive"), MAIN_FILE("main-file"), ALLOW_DEBUG_LOGGING("allow-debug-logging"), DEBUG_LOG_FILE("debug-log-file"), STANDARD_LOG_FILE("standard-log-file"), ALLOW_PROFILING("allow-profiling"), PROFILING_FILE("profiling-file"), SHOW_SPLASH_SCREEN("show-splash-screen"), USE_COLORS("use-colors"), HALT_ON_FAILURE("halt-on-failure"), USE_SUDO_FALLBACK("use-sudo-fallback"), ALLOW_SHELL_COMMANDS("allow-shell-commands"), ALLOW_DYNAMIC_SHELL("allow-dynamic-shell"), SCREAM_ERRORS("scream-errors"), INTERPRETER_TIMEOUT("interpreter-timeout"); private String name; private PNames(String name) { this.name = name; } /** * Returns the name that will be listed in the config, not the name of the enum. * * @return */ public String config() { return name; } } /** * Initializes the global Prefs to this file. A file change watcher is registered at this point as well, and changes * to the file specified will result in the preferences immediately updated. * * @param f * @throws IOException */ public static void init(final File f) throws IOException { ArrayList<Preferences.Preference> a = new ArrayList<>(); a.add(new Preference(PNames.DEBUG_MODE.config(), "false", Preferences.Type.BOOLEAN, "Whether or not to display" + " debug information in the console")); a.add(new Preference(PNames.SHOW_WARNINGS.config(), "true", Preferences.Type.BOOLEAN, "Whether or not to display" + " warnings in the console, while compiling")); a.add(new Preference(PNames.CONSOLE_LOG_COMMANDS.config(), "true", Preferences.Type.BOOLEAN, "Whether or not to" + " display the original command in the console when it is run")); a.add(new Preference(PNames.SCRIPT_NAME.config(), "aliases.msa", Preferences.Type.STRING, "The path to the" + " default config file, relative to the CommandHelper plugin folder")); a.add(new Preference(PNames.ENABLE_INTERPRETER.config(), "false", Preferences.Type.BOOLEAN, "Whether or not to" + " enable the /interpreter command. Note that even with this enabled, a player must still have the" + " commandhelper.interpreter permission, but" + " setting it to false prevents all players from accessing the interpreter regardless of their" + " permissions.")); a.add(new Preference(PNames.BASE_DIR.config(), "", Preferences.Type.STRING, "The base directory that scripts" + " can read and write to. If left blank, then the default of the server directory will be used. " + "This setting affects functions like include and read.")); a.add(new Preference(PNames.PLAY_DIRTY.config(), "false", Preferences.Type.BOOLEAN, "Makes CommandHelper play" + " dirty and break all sorts of programming rules, so that other plugins can't interfere with the" + " operations that you defined. Note that doing this essentially makes CommandHelper have absolute" + " say over commands. Use this setting only if you can't get another plugin to cooperate with CH," + " because it is a global setting.")); a.add(new Preference(PNames.CASE_SENSITIVE.config(), "false", Preferences.Type.BOOLEAN, "Makes command matching" + " be case sensitive. If set to false, if your config defines /cmd, but the user runs /CMD, it will" + " trigger the command anyways.")); a.add(new Preference(PNames.MAIN_FILE.config(), "main.ms", Preferences.Type.STRING, "The path to the main file," + " relative to the CommandHelper folder")); a.add(new Preference(PNames.ALLOW_DEBUG_LOGGING.config(), "false", Preferences.Type.BOOLEAN, "If set to false," + " the Debug class of functions will do nothing.")); a.add(new Preference(PNames.DEBUG_LOG_FILE.config(), "logs/debug/%Y-%M-%D-debug.log", Preferences.Type.STRING, "The path to the debug output log file. Six variables are available, %Y, %M, and %D, %h, %m, %s, which" + " are replaced with the current year, month, day, hour, minute and second respectively. It is" + " highly recommended that you use at least year, month, and day if you are for whatever" + " reason leaving logging on, otherwise the file size would get excessively large. The path" + " is relative to the CommandHelper directory and is not bound by the base-dir restriction." + " The logger preferences file is created in the same directory this file is in as well, and" + " is named loggerPreferences.txt")); a.add(new Preference(PNames.STANDARD_LOG_FILE.config(), "logs/%Y-%M-%D-commandhelper.log", Preferences.Type.STRING, "The path the standard log files that the log() function writes to. Six" + " variables are available, %Y, %M, and %D, %h, %m, %s, which are replaced with the current" + " year, month, day, hour, minute and second respectively. It is highly recommended that you" + " use at least year, month, and day if you are actively logging things, otherwise the file" + " size would get excessively large. The path is relative to the CommandHelper directory and" + " is not bound by the base-dir restriction.")); a.add(new Preference(PNames.ALLOW_PROFILING.config(), "false", Preferences.Type.BOOLEAN, "If set to false, the" + " Profiling class of functions will do nothing.")); a.add(new Preference(PNames.PROFILING_FILE.config(), "logs/profiling/%Y-%M-%D-profiling.log", Preferences.Type.STRING, "The path to the profiling logs. These logs are perf4j formatted logs. Consult" + " the documentation for more information.")); a.add(new Preference(PNames.SHOW_SPLASH_SCREEN.config(), "true", Preferences.Type.BOOLEAN, "Whether or not to" + " show the splash screen at server startup")); a.add(new Preference(PNames.USE_COLORS.config(), (TermColors.SYSTEM == TermColors.SYS.WINDOWS ? "false" : "true"), Preferences.Type.BOOLEAN, "Whether or" + " not to use console colors. If this is a Windows machine, defaults to false, however, it can" + " be toggled manually, and will then respect your setting.")); a.add(new Preference(PNames.HALT_ON_FAILURE.config(), "false", Preferences.Type.BOOLEAN, "Whether or not to" + " halt compilation of pure mscript files if a compilation failure occurs in any one of the files.")); a.add(new Preference(PNames.USE_SUDO_FALLBACK.config(), "false", Preferences.Type.BOOLEAN, "If true, sudo()" + " will use a less safe fallback method if it fails. See the documentation on the sudo function for" + " more details. If this is true, a warning is issued at startup.")); a.add(new Preference(PNames.ALLOW_SHELL_COMMANDS.config(), "false", Preferences.Type.BOOLEAN, "If true, allows" + " for the shell functions to be used from outside of cmdline mode. WARNING: Enabling these functions" + " can be extremely dangerous if you accidentally allow uncontrolled access to them, and can" + " grant full control of your server if not careful. Leave this set to false unless you really know" + " what you're doing.")); a.add(new Preference(PNames.ALLOW_DYNAMIC_SHELL.config(), "false", Preferences.Type.BOOLEAN, "If true, allows" + " use of the shell() functions from dynamic code sources, i.e" + " interpreter or eval(). This almost certainly should always remain false, and if enabled, enabled" + " only temporarily. If this is true, if an account with" + " interpreter mode is compromised, the attacker could gain access to your entire server, under the" + " user running minecraft, not just the game server.")); a.add(new Preference(PNames.SCREAM_ERRORS.config(), "false", Preferences.Type.BOOLEAN, "Setting this to true" + " allows you to scream errors. Regardless of other settings" + " that you may have unintentionally configured, this will override all ways of suppressing fatal" + " errors, including uncaught exception" + " handlers, error logging turned off, etc. This is meant as a last ditch effort to diagnosing an" + " error. This implicitely turns debug mode" + " on as well, which will cause even more error logging to occur.")); a.add(new Preference(PNames.INTERPRETER_TIMEOUT.config(), "15", Preferences.Type.INT, "Sets the time (in" + " minutes) that interpreter mode is unlocked for when /interpreter-on is run from console. Set to 0" + " (or a negative number)" + " to disable this feature, and allow interpreter mode all the time. It is highly recommended that" + " you leave this set to some number greater than 0, to enhance" + " server security, and require a \"two step\" authentication for interpreter mode.")); prefs = new Preferences("CommandHelper", Static.getLogger(), a); prefs.init(f); if (f == null || f.getParentFile() == null) { //Skip all this, it's running in a special condition, for instance, docgen or tests or something //else. We won't support this feature in that case. return; } // // Set up a watcher on this file to watch for changes to it. Once the changes // // take effect, we want to reparse the prefs // if(watcherThread != null){ // watcherThread.interrupt(); // } // final WatchService watcher = FileSystems.getDefault().newWatchService(); // final Path path = f.getParentFile().toPath(); // path.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY); // watcherThread = new Thread(new Runnable(){ // // @Override // public void run() { // while(true){ // WatchKey key; // try { // key = watcher.take(); // } catch(InterruptedException ex){ // return; // } // if(Thread.currentThread().isInterrupted()){ // return; // } // for(WatchEvent<?> event : key.pollEvents()){ // WatchEvent.Kind kind = event.kind(); // if(kind == StandardWatchEventKinds.OVERFLOW){ // continue; // } // // WatchEvent<Path> ev = ((WatchEvent<Path>)event); // Path name = ev.context(); // Path child = path.resolve(name); // if(child.toFile().equals(f)){ // try { // prefs.init(f); // } catch (IOException ex) { // Logger.getLogger(Prefs.class.getName()).log(Level.SEVERE, null, ex); // } // } // } // key.reset(); // } // } // }, Implementation.GetServerType().getBranding() + "-PrefsWatcher"); // watcherThread.setDaemon(true); // watcherThread.start(); // //The convertor may not have been set up yet, so we need to kick off a new // //thread and try over and over until this is valid. This may not ever // //actually register in cmdline mode, which is fine, this whole operation // //is merely to kill the daemon thread properly. If the whole server dies // //and this doesn't run, that's ok. // new Thread(new Runnable() { // // @Override // @SuppressWarnings("SleepWhileInLoop") // public void run() { // try { // //Not registered yet, sleep // Thread.sleep(500); // } catch (InterruptedException ex1) { // // } // while(true){ // try { // StaticLayer.GetConvertor().addShutdownHook(new Runnable() { // // @Override // public void run() { // if(watcherThread != null){ // watcherThread.interrupt(); // watcherThread = null; // } // } // }); // break; // } catch(Exception ex){ // try { // //Not registered yet, sleep // Thread.sleep(500); // } catch (InterruptedException ex1) { // // } // } // } // } // }, Implementation.GetServerType().getBranding() + "-PrefsWatcherShutdownRegistration").start(); } public static boolean isInitialized() { return prefs != null; } /** * Convenience function to set the term colors based on the UseColors preference. */ public static void SetColors() { if (UseColors()) { TermColors.EnableColors(); } else { TermColors.DisableColors(); } } public static Boolean DebugMode() { return (Boolean) pref(PNames.DEBUG_MODE) || ScreamErrors(); } public static Boolean ShowWarnings() { return (Boolean) pref(PNames.SHOW_WARNINGS); } public static Boolean ConsoleLogCommands() { return (Boolean) pref(PNames.CONSOLE_LOG_COMMANDS); } public static String ScriptName() { return (String) pref(PNames.SCRIPT_NAME); } public static Boolean EnableInterpreter() { return (Boolean) pref(PNames.ENABLE_INTERPRETER); } public static String BaseDir() { return (String) pref(PNames.BASE_DIR); } public static Boolean PlayDirty() { return (Boolean) pref(PNames.PLAY_DIRTY); } public static Boolean CaseSensitive() { return (Boolean) pref(PNames.CASE_SENSITIVE); } public static String MainFile() { return (String) pref(PNames.MAIN_FILE); } public static Boolean AllowDebugLogging() { return (Boolean) pref(PNames.ALLOW_DEBUG_LOGGING); } public static String DebugLogFile() { return (String) pref(PNames.DEBUG_LOG_FILE); } public static String StandardLogFile() { return (String) pref(PNames.STANDARD_LOG_FILE); } public static Boolean AllowProfiling() { return (Boolean) pref(PNames.ALLOW_PROFILING); } public static String ProfilingFile() { return (String) pref(PNames.PROFILING_FILE); } public static Boolean ShowSplashScreen() { return (Boolean) pref(PNames.SHOW_SPLASH_SCREEN); } public static Boolean UseColors() { return (Boolean) pref(PNames.USE_COLORS); } public static Boolean HaltOnFailure() { return (Boolean) pref(PNames.HALT_ON_FAILURE); } public static Boolean UseSudoFallback() { return (Boolean) pref(PNames.USE_SUDO_FALLBACK); } public static Boolean AllowShellCommands() { return (Boolean) pref(PNames.ALLOW_SHELL_COMMANDS); } public static Boolean AllowDynamicShell() { return (Boolean) pref(PNames.ALLOW_DYNAMIC_SHELL); } public static Boolean ScreamErrors() { return (Boolean) pref(PNames.SCREAM_ERRORS); } public static Integer InterpreterTimeout() { Integer i = (Integer) pref(PNames.INTERPRETER_TIMEOUT); if (i < 0) { i = 0; } return i; } }