/*
TShock, a server mod for Terraria
Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Terraria;
using Terraria.ID;
using Terraria.Localization;
using TShockAPI.DB;
using TerrariaApi.Server;
using TShockAPI.Hooks;
using Terraria.GameContent.Events;
using Microsoft.Xna.Framework;
using OTAPI.Tile;
using TShockAPI.Localization;
using System.Text.RegularExpressions;
namespace TShockAPI
{
public delegate void CommandDelegate(CommandArgs args);
public class CommandArgs : EventArgs
{
public string Message { get; private set; }
public TSPlayer Player { get; private set; }
public bool Silent { get; private set; }
///
/// Parameters passed to the arguement. Does not include the command name.
/// IE '/kick "jerk face"' will only have 1 argument
///
public List Parameters { get; private set; }
public Player TPlayer
{
get { return Player.TPlayer; }
}
public CommandArgs(string message, TSPlayer ply, List args)
{
Message = message;
Player = ply;
Parameters = args;
Silent = false;
}
public CommandArgs(string message, bool silent, TSPlayer ply, List args)
{
Message = message;
Player = ply;
Parameters = args;
Silent = silent;
}
}
public class Command
{
///
/// Gets or sets whether to allow non-players to use this command.
///
public bool AllowServer { get; set; }
///
/// Gets or sets whether to do logging of this command.
///
public bool DoLog { get; set; }
///
/// Gets or sets the help text of this command.
///
public string HelpText { get; set; }
///
/// Gets or sets an extended description of this command.
///
public string[] HelpDesc { get; set; }
///
/// Gets the name of the command.
///
public string Name { get { return Names[0]; } }
///
/// Gets the names of the command.
///
public List Names { get; protected set; }
///
/// Gets the permissions of the command.
///
public List Permissions { get; protected set; }
private CommandDelegate commandDelegate;
public CommandDelegate CommandDelegate
{
get { return commandDelegate; }
set
{
if (value == null)
throw new ArgumentNullException();
commandDelegate = value;
}
}
public Command(List permissions, CommandDelegate cmd, params string[] names)
: this(cmd, names)
{
Permissions = permissions;
}
public Command(string permissions, CommandDelegate cmd, params string[] names)
: this(cmd, names)
{
Permissions = new List { permissions };
}
public Command(CommandDelegate cmd, params string[] names)
{
if (cmd == null)
throw new ArgumentNullException("cmd");
if (names == null || names.Length < 1)
throw new ArgumentException("names");
AllowServer = true;
CommandDelegate = cmd;
DoLog = true;
HelpText = "No help available.";
HelpDesc = null;
Names = new List(names);
Permissions = new List();
}
public bool Run(string msg, bool silent, TSPlayer ply, List parms)
{
if (!CanRun(ply))
return false;
try
{
CommandDelegate(new CommandArgs(msg, silent, ply, parms));
}
catch (Exception e)
{
ply.SendErrorMessage("Command failed, check logs for more details.");
TShock.Log.Error(e.ToString());
}
return true;
}
public bool Run(string msg, TSPlayer ply, List parms)
{
return Run(msg, false, ply, parms);
}
public bool HasAlias(string name)
{
return Names.Contains(name);
}
public bool CanRun(TSPlayer ply)
{
if (Permissions == null || Permissions.Count < 1)
return true;
foreach (var Permission in Permissions)
{
if (ply.HasPermission(Permission))
return true;
}
return false;
}
}
public static class Commands
{
public static List ChatCommands = new List();
public static ReadOnlyCollection TShockCommands = new ReadOnlyCollection(new List());
///
/// The command specifier, defaults to "/"
///
public static string Specifier
{
get { return string.IsNullOrWhiteSpace(TShock.Config.CommandSpecifier) ? "/" : TShock.Config.CommandSpecifier; }
}
///
/// The silent command specifier, defaults to "."
///
public static string SilentSpecifier
{
get { return string.IsNullOrWhiteSpace(TShock.Config.CommandSilentSpecifier) ? "." : TShock.Config.CommandSilentSpecifier; }
}
private delegate void AddChatCommand(string permission, CommandDelegate command, params string[] names);
public static void InitCommands()
{
List tshockCommands = new List(100);
Action add = (cmd) =>
{
tshockCommands.Add(cmd);
ChatCommands.Add(cmd);
};
add(new Command(AuthToken, "auth")
{
AllowServer = false,
HelpText = "Used to authenticate as superadmin when first setting up TShock."
});
add(new Command(Permissions.user, ManageUsers, "user")
{
DoLog = false,
HelpText = "Manages user accounts."
});
#region Account Commands
add(new Command(Permissions.canlogin, AttemptLogin, "login")
{
AllowServer = false,
DoLog = false,
HelpText = "Logs you into an account."
});
add(new Command(Permissions.canlogout, Logout, "logout")
{
AllowServer = false,
DoLog = false,
HelpText = "Logs you out of your current account."
});
add(new Command(Permissions.canchangepassword, PasswordUser, "password")
{
AllowServer = false,
DoLog = false,
HelpText = "Changes your account's password."
});
add(new Command(Permissions.canregister, RegisterUser, "register")
{
AllowServer = false,
DoLog = false,
HelpText = "Registers you an account."
});
add(new Command(Permissions.checkaccountinfo, ViewAccountInfo, "accountinfo", "ai")
{
HelpText = "Shows information about a user."
});
#endregion
#region Admin Commands
add(new Command(Permissions.ban, Ban, "ban")
{
HelpText = "Manages player bans."
});
add(new Command(Permissions.broadcast, Broadcast, "broadcast", "bc", "say")
{
HelpText = "Broadcasts a message to everyone on the server."
});
add(new Command(Permissions.logs, DisplayLogs, "displaylogs")
{
HelpText = "Toggles whether you receive server logs."
});
add(new Command(Permissions.managegroup, Group, "group")
{
HelpText = "Manages groups."
});
add(new Command(Permissions.manageitem, ItemBan, "itemban")
{
HelpText = "Manages item bans."
});
add(new Command(Permissions.manageprojectile, ProjectileBan, "projban")
{
HelpText = "Manages projectile bans."
});
add(new Command(Permissions.managetile, TileBan, "tileban")
{
HelpText = "Manages tile bans."
});
add(new Command(Permissions.manageregion, Region, "region")
{
HelpText = "Manages regions."
});
add(new Command(Permissions.kick, Kick, "kick")
{
HelpText = "Removes a player from the server."
});
add(new Command(Permissions.mute, Mute, "mute", "unmute")
{
HelpText = "Prevents a player from talking."
});
add(new Command(Permissions.savessc, OverrideSSC, "overridessc", "ossc")
{
HelpText = "Overrides serverside characters for a player, temporarily."
});
add(new Command(Permissions.savessc, SaveSSC, "savessc")
{
HelpText = "Saves all serverside characters."
});
add(new Command(Permissions.uploaddata, UploadJoinData, "uploadssc")
{
HelpText = "Upload the account information when you joined the server as your Server Side Character data."
});
add(new Command(Permissions.settempgroup, TempGroup, "tempgroup")
{
HelpText = "Temporarily sets another player's group."
});
add(new Command(Permissions.su, SubstituteUser, "su")
{
HelpText = "Temporarily elevates you to Super Admin."
});
add(new Command(Permissions.su, SubstituteUserDo, "sudo")
{
HelpText = "Executes a command as the super admin."
});
add(new Command(Permissions.userinfo, GrabUserUserInfo, "userinfo", "ui")
{
HelpText = "Shows information about a player."
});
#endregion
#region Annoy Commands
add(new Command(Permissions.annoy, Annoy, "annoy")
{
HelpText = "Annoys a player for an amount of time."
});
add(new Command(Permissions.annoy, Confuse, "confuse")
{
HelpText = "Confuses a player for an amount of time."
});
add(new Command(Permissions.annoy, Rocket, "rocket")
{
HelpText = "Rockets a player upwards. Requires SSC."
});
add(new Command(Permissions.annoy, FireWork, "firework")
{
HelpText = "Spawns fireworks at a player."
});
#endregion
#region Configuration Commands
add(new Command(Permissions.maintenance, CheckUpdates, "checkupdates")
{
HelpText = "Checks for TShock updates."
});
add(new Command(Permissions.maintenance, Off, "off", "exit", "stop")
{
HelpText = "Shuts down the server while saving."
});
add(new Command(Permissions.maintenance, OffNoSave, "off-nosave", "exit-nosave", "stop-nosave")
{
HelpText = "Shuts down the server without saving."
});
add(new Command(Permissions.cfgreload, Reload, "reload")
{
HelpText = "Reloads the server configuration file."
});
add(new Command(Permissions.cfgpassword, ServerPassword, "serverpassword")
{
HelpText = "Changes the server password."
});
add(new Command(Permissions.maintenance, GetVersion, "version")
{
HelpText = "Shows the TShock version."
});
add(new Command(Permissions.whitelist, Whitelist, "whitelist")
{
HelpText = "Manages the server whitelist."
});
#endregion
#region Item Commands
add(new Command(Permissions.give, Give, "give", "g")
{
HelpText = "Gives another player an item."
});
add(new Command(Permissions.item, Item, "item", "i")
{
AllowServer = false,
HelpText = "Gives yourself an item."
});
#endregion
#region NPC Commands
add(new Command(Permissions.butcher, Butcher, "butcher")
{
HelpText = "Kills hostile NPCs or NPCs of a certain type."
});
add(new Command(Permissions.renamenpc, RenameNPC, "renamenpc")
{
HelpText = "Renames an NPC."
});
add(new Command(Permissions.invade, Invade, "invade")
{
HelpText = "Starts an NPC invasion."
});
add(new Command(Permissions.maxspawns, MaxSpawns, "maxspawns")
{
HelpText = "Sets the maximum number of NPCs."
});
add(new Command(Permissions.spawnboss, SpawnBoss, "spawnboss", "sb")
{
AllowServer = false,
HelpText = "Spawns a number of bosses around you."
});
add(new Command(Permissions.spawnmob, SpawnMob, "spawnmob", "sm")
{
AllowServer = false,
HelpText = "Spawns a number of mobs around you."
});
add(new Command(Permissions.spawnrate, SpawnRate, "spawnrate")
{
HelpText = "Sets the spawn rate of NPCs."
});
add(new Command(Permissions.clearangler, ClearAnglerQuests, "clearangler")
{
HelpText = "Resets the list of users who have completed an angler quest that day."
});
#endregion
#region TP Commands
add(new Command(Permissions.home, Home, "home")
{
AllowServer = false,
HelpText = "Sends you to your spawn point."
});
add(new Command(Permissions.spawn, Spawn, "spawn")
{
AllowServer = false,
HelpText = "Sends you to the world's spawn point."
});
add(new Command(Permissions.tp, TP, "tp")
{
AllowServer = false,
HelpText = "Teleports a player to another player."
});
add(new Command(Permissions.tpothers, TPHere, "tphere")
{
AllowServer = false,
HelpText = "Teleports a player to yourself."
});
add(new Command(Permissions.tpnpc, TPNpc, "tpnpc")
{
AllowServer = false,
HelpText = "Teleports you to an npc."
});
add(new Command(Permissions.tppos, TPPos, "tppos")
{
AllowServer = false,
HelpText = "Teleports you to tile coordinates."
});
add(new Command(Permissions.getpos, GetPos, "pos")
{
AllowServer = false,
HelpText = "Returns the user's or specified user's current position."
});
add(new Command(Permissions.tpallow, TPAllow, "tpallow")
{
AllowServer = false,
HelpText = "Toggles whether other people can teleport you."
});
#endregion
#region World Commands
add(new Command(Permissions.toggleexpert, ToggleExpert, "expert", "expertmode")
{
HelpText = "Toggles expert mode."
});
add(new Command(Permissions.antibuild, ToggleAntiBuild, "antibuild")
{
HelpText = "Toggles build protection."
});
add(new Command(Permissions.bloodmoon, Bloodmoon, "bloodmoon")
{
HelpText = "Sets a blood moon."
});
add(new Command(Permissions.grow, Grow, "grow")
{
AllowServer = false,
HelpText = "Grows plants at your location."
});
add(new Command(Permissions.dropmeteor, DropMeteor, "dropmeteor")
{
HelpText = "Drops a meteor somewhere in the world."
});
add(new Command(Permissions.eclipse, Eclipse, "eclipse")
{
HelpText = "Sets an eclipse."
});
add(new Command(Permissions.halloween, ForceHalloween, "forcehalloween")
{
HelpText = "Toggles halloween mode (goodie bags, pumpkins, etc)."
});
add(new Command(Permissions.xmas, ForceXmas, "forcexmas")
{
HelpText = "Toggles christmas mode (present spawning, santa, etc)."
});
add(new Command(Permissions.fullmoon, Fullmoon, "fullmoon")
{
HelpText = "Sets a full moon."
});
add(new Command(Permissions.hardmode, Hardmode, "hardmode")
{
HelpText = "Toggles the world's hardmode status."
});
add(new Command(Permissions.editspawn, ProtectSpawn, "protectspawn")
{
HelpText = "Toggles spawn protection."
});
add(new Command(Permissions.sandstorm, Sandstorm, "sandstorm")
{
HelpText = "Toggles sandstorms."
});
add(new Command(Permissions.rain, Rain, "rain")
{
HelpText = "Toggles the rain."
});
add(new Command(Permissions.worldsave, Save, "save")
{
HelpText = "Saves the world file."
});
add(new Command(Permissions.worldspawn, SetSpawn, "setspawn")
{
AllowServer = false,
HelpText = "Sets the world's spawn point to your location."
});
add(new Command(Permissions.dungeonposition, SetDungeon, "setdungeon")
{
AllowServer = false,
HelpText = "Sets the dungeon's position to your location."
});
add(new Command(Permissions.worldsettle, Settle, "settle")
{
HelpText = "Forces all liquids to update immediately."
});
add(new Command(Permissions.time, Time, "time")
{
HelpText = "Sets the world time."
});
add(new Command(Permissions.wind, Wind, "wind")
{
HelpText = "Changes the wind speed."
});
add(new Command(Permissions.worldinfo, WorldInfo, "world")
{
HelpText = "Shows information about the current world."
});
#endregion
#region Other Commands
add(new Command(Permissions.buff, Buff, "buff")
{
AllowServer = false,
HelpText = "Gives yourself a buff for an amount of time."
});
add(new Command(Permissions.clear, Clear, "clear")
{
HelpText = "Clears item drops or projectiles."
});
add(new Command(Permissions.buffplayer, GBuff, "gbuff", "buffplayer")
{
HelpText = "Gives another player a buff for an amount of time."
});
add(new Command(Permissions.godmode, ToggleGodMode, "godmode")
{
HelpText = "Toggles godmode on a player."
});
add(new Command(Permissions.heal, Heal, "heal")
{
HelpText = "Heals a player in HP and MP."
});
add(new Command(Permissions.kill, Kill, "kill")
{
HelpText = "Kills another player."
});
add(new Command(Permissions.cantalkinthird, ThirdPerson, "me")
{
HelpText = "Sends an action message to everyone."
});
add(new Command(Permissions.canpartychat, PartyChat, "party", "p")
{
AllowServer = false,
HelpText = "Sends a message to everyone on your team."
});
add(new Command(Permissions.whisper, Reply, "reply", "r")
{
HelpText = "Replies to a PM sent to you."
});
add(new Command(Rests.RestPermissions.restmanage, ManageRest, "rest")
{
HelpText = "Manages the REST API."
});
add(new Command(Permissions.slap, Slap, "slap")
{
HelpText = "Slaps a player, dealing damage."
});
add(new Command(Permissions.serverinfo, ServerInfo, "serverinfo")
{
HelpText = "Shows the server information."
});
add(new Command(Permissions.warp, Warp, "warp")
{
HelpText = "Teleports you to a warp point or manages warps."
});
add(new Command(Permissions.whisper, Whisper, "whisper", "w", "tell")
{
HelpText = "Sends a PM to a player."
});
add(new Command(Permissions.createdumps, CreateDumps, "dump-reference-data")
{
HelpText = "Creates a reference tables for Terraria data types and the TShock permission system in the server folder."
});
#endregion
add(new Command(Aliases, "aliases")
{
HelpText = "Shows a command's aliases."
});
add(new Command(Help, "help")
{
HelpText = "Lists commands or gives help on them."
});
add(new Command(Motd, "motd")
{
HelpText = "Shows the message of the day."
});
add(new Command(ListConnectedPlayers, "playing", "online", "who")
{
HelpText = "Shows the currently connected players."
});
add(new Command(Rules, "rules")
{
HelpText = "Shows the server's rules."
});
TShockCommands = new ReadOnlyCollection(tshockCommands);
}
public static bool HandleCommand(TSPlayer player, string text)
{
string cmdText = text.Remove(0, 1);
string cmdPrefix = text[0].ToString();
bool silent = false;
if (cmdPrefix == SilentSpecifier)
silent = true;
int index = -1;
for (int i = 0; i < cmdText.Length; i++)
{
if (IsWhiteSpace(cmdText[i]))
{
index = i;
break;
}
}
string cmdName;
if (index == 0) // Space after the command specifier should not be supported
{
player.SendErrorMessage("Invalid command entered. Type {0}help for a list of valid commands.", Specifier);
return true;
}
else if (index < 0)
cmdName = cmdText.ToLower();
else
cmdName = cmdText.Substring(0, index).ToLower();
List args;
if (index < 0)
args = new List();
else
args = ParseParameters(cmdText.Substring(index));
IEnumerable cmds = ChatCommands.FindAll(c => c.HasAlias(cmdName));
if (Hooks.PlayerHooks.OnPlayerCommand(player, cmdName, cmdText, args, ref cmds, cmdPrefix))
return true;
if (cmds.Count() == 0)
{
if (player.AwaitingResponse.ContainsKey(cmdName))
{
Action call = player.AwaitingResponse[cmdName];
player.AwaitingResponse.Remove(cmdName);
call(new CommandArgs(cmdText, player, args));
return true;
}
player.SendErrorMessage("Invalid command entered. Type {0}help for a list of valid commands.", Specifier);
return true;
}
foreach (Command cmd in cmds)
{
if (!cmd.CanRun(player))
{
TShock.Utils.SendLogs(string.Format("{0} tried to execute {1}{2}.", player.Name, Specifier, cmdText), Color.PaleVioletRed, player);
player.SendErrorMessage("You do not have access to this command.");
if (player.HasPermission(Permissions.su))
{
player.SendInfoMessage("You can use '{0}sudo {0}{1}' to override this check.", Specifier, cmdText);
}
}
else if (!cmd.AllowServer && !player.RealPlayer)
{
player.SendErrorMessage("You must use this command in-game.");
}
else
{
if (cmd.DoLog)
TShock.Utils.SendLogs(string.Format("{0} executed: {1}{2}.", player.Name, silent ? SilentSpecifier : Specifier, cmdText), Color.PaleVioletRed, player);
cmd.Run(cmdText, silent, player, args);
}
}
return true;
}
///
/// Parses a string of parameters into a list. Handles quotes.
///
///
///
private static List ParseParameters(string str)
{
var ret = new List();
var sb = new StringBuilder();
bool instr = false;
for (int i = 0; i < str.Length; i++)
{
char c = str[i];
if (c == '\\' && ++i < str.Length)
{
if (str[i] != '"' && str[i] != ' ' && str[i] != '\\')
sb.Append('\\');
sb.Append(str[i]);
}
else if (c == '"')
{
instr = !instr;
if (!instr)
{
ret.Add(sb.ToString());
sb.Clear();
}
else if (sb.Length > 0)
{
ret.Add(sb.ToString());
sb.Clear();
}
}
else if (IsWhiteSpace(c) && !instr)
{
if (sb.Length > 0)
{
ret.Add(sb.ToString());
sb.Clear();
}
}
else
sb.Append(c);
}
if (sb.Length > 0)
ret.Add(sb.ToString());
return ret;
}
private static bool IsWhiteSpace(char c)
{
return c == ' ' || c == '\t' || c == '\n';
}
#region Account commands
private static void AttemptLogin(CommandArgs args)
{
if (args.Player.LoginAttempts > TShock.Config.MaximumLoginAttempts && (TShock.Config.MaximumLoginAttempts != -1))
{
TShock.Log.Warn(String.Format("{0} ({1}) had {2} or more invalid login attempts and was kicked automatically.",
args.Player.IP, args.Player.Name, TShock.Config.MaximumLoginAttempts));
TShock.Utils.Kick(args.Player, "Too many invalid login attempts.");
return;
}
if (args.Player.IsLoggedIn)
{
args.Player.SendErrorMessage("You are already logged in, and cannot login again.");
return;
}
UserAccount account = TShock.UserAccounts.GetUserAccountByName(args.Player.Name);
string password = "";
bool usingUUID = false;
if (args.Parameters.Count == 0 && !TShock.Config.DisableUUIDLogin)
{
if (PlayerHooks.OnPlayerPreLogin(args.Player, args.Player.Name, ""))
return;
usingUUID = true;
}
else if (args.Parameters.Count == 1)
{
if (PlayerHooks.OnPlayerPreLogin(args.Player, args.Player.Name, args.Parameters[0]))
return;
password = args.Parameters[0];
}
else if (args.Parameters.Count == 2 && TShock.Config.AllowLoginAnyUsername)
{
if (String.IsNullOrEmpty(args.Parameters[0]))
{
args.Player.SendErrorMessage("Bad login attempt.");
return;
}
if (PlayerHooks.OnPlayerPreLogin(args.Player, args.Parameters[0], args.Parameters[1]))
return;
account = TShock.UserAccounts.GetUserAccountByName(args.Parameters[0]);
password = args.Parameters[1];
}
else
{
args.Player.SendErrorMessage("Syntax: {0}login - Logs in using your UUID and character name", Specifier);
args.Player.SendErrorMessage(" {0}login - Logs in using your password and character name", Specifier);
args.Player.SendErrorMessage(" {0}login - Logs in using your username and password", Specifier);
args.Player.SendErrorMessage("If you forgot your password, there is no way to recover it.");
return;
}
try
{
if (account == null)
{
args.Player.SendErrorMessage("A user account by that name does not exist.");
}
else if (account.VerifyPassword(password) ||
(usingUUID && account.UUID == args.Player.UUID && !TShock.Config.DisableUUIDLogin &&
!String.IsNullOrWhiteSpace(args.Player.UUID)))
{
args.Player.PlayerData = TShock.CharacterDB.GetPlayerData(args.Player, account.ID);
var group = TShock.Utils.GetGroup(account.Group);
args.Player.Group = group;
args.Player.tempGroup = null;
args.Player.Account = account;
args.Player.IsLoggedIn = true;
args.Player.IgnoreActionsForInventory = "none";
if (Main.ServerSideCharacter)
{
if (args.Player.HasPermission(Permissions.bypassssc))
{
args.Player.PlayerData.CopyCharacter(args.Player);
TShock.CharacterDB.InsertPlayerData(args.Player);
}
args.Player.PlayerData.RestoreCharacter(args.Player);
}
args.Player.LoginFailsBySsi = false;
if (args.Player.HasPermission(Permissions.ignorestackhackdetection))
args.Player.IgnoreActionsForCheating = "none";
if (args.Player.HasPermission(Permissions.usebanneditem))
args.Player.IgnoreActionsForDisabledArmor = "none";
args.Player.SendSuccessMessage("Authenticated as " + account.Name + " successfully.");
TShock.Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user: " + account.Name + ".");
if ((args.Player.LoginHarassed) && (TShock.Config.RememberLeavePos))
{
if (TShock.RememberedPos.GetLeavePos(args.Player.Name, args.Player.IP) != Vector2.Zero)
{
Vector2 pos = TShock.RememberedPos.GetLeavePos(args.Player.Name, args.Player.IP);
args.Player.Teleport((int)pos.X * 16, (int)pos.Y * 16);
}
args.Player.LoginHarassed = false;
}
TShock.UserAccounts.SetUserAccountUUID(account, args.Player.UUID);
Hooks.PlayerHooks.OnPlayerPostLogin(args.Player);
}
else
{
if (usingUUID && !TShock.Config.DisableUUIDLogin)
{
args.Player.SendErrorMessage("UUID does not match this character!");
}
else
{
args.Player.SendErrorMessage("Invalid password!");
}
TShock.Log.Warn(args.Player.IP + " failed to authenticate as user: " + account.Name + ".");
args.Player.LoginAttempts++;
}
}
catch (Exception ex)
{
args.Player.SendErrorMessage("There was an error processing your request.");
TShock.Log.Error(ex.ToString());
}
}
private static void Logout(CommandArgs args)
{
if (!args.Player.IsLoggedIn)
{
args.Player.SendErrorMessage("You are not logged in.");
return;
}
args.Player.Logout();
args.Player.SendSuccessMessage("You have been successfully logged out of your account.");
if (Main.ServerSideCharacter)
{
args.Player.SendWarningMessage("Server side characters are enabled. You need to be logged in to play.");
}
}
private static void PasswordUser(CommandArgs args)
{
try
{
if (args.Player.IsLoggedIn && args.Parameters.Count == 2)
{
string password = args.Parameters[0];
if (args.Player.Account.VerifyPassword(password))
{
try
{
args.Player.SendSuccessMessage("You changed your password!");
TShock.UserAccounts.SetUserAccountPassword(args.Player.Account, args.Parameters[1]); // SetUserPassword will hash it for you.
TShock.Log.ConsoleInfo(args.Player.IP + " named " + args.Player.Name + " changed the password of account " +
args.Player.Account.Name + ".");
}
catch (ArgumentOutOfRangeException)
{
args.Player.SendErrorMessage("Password must be greater than or equal to " + TShock.Config.MinimumPasswordLength + " characters.");
}
}
else
{
args.Player.SendErrorMessage("You failed to change your password!");
TShock.Log.ConsoleError(args.Player.IP + " named " + args.Player.Name + " failed to change password for account: " +
args.Player.Account.Name + ".");
}
}
else
{
args.Player.SendErrorMessage("Not logged in or invalid syntax! Proper syntax: {0}password ", Specifier);
}
}
catch (UserAccountManagerException ex)
{
args.Player.SendErrorMessage("Sorry, an error occured: " + ex.Message + ".");
TShock.Log.ConsoleError("PasswordUser returned an error: " + ex);
}
}
private static void RegisterUser(CommandArgs args)
{
try
{
var account = new UserAccount();
string echoPassword = "";
if (args.Parameters.Count == 1)
{
account.Name = args.Player.Name;
echoPassword = args.Parameters[0];
try
{
account.CreateBCryptHash(args.Parameters[0]);
}
catch (ArgumentOutOfRangeException)
{
args.Player.SendErrorMessage("Password must be greater than or equal to " + TShock.Config.MinimumPasswordLength + " characters.");
return;
}
}
else if (args.Parameters.Count == 2 && TShock.Config.AllowRegisterAnyUsername)
{
account.Name = args.Parameters[0];
echoPassword = args.Parameters[1];
try
{
account.CreateBCryptHash(args.Parameters[1]);
}
catch (ArgumentOutOfRangeException)
{
args.Player.SendErrorMessage("Password must be greater than or equal to " + TShock.Config.MinimumPasswordLength + " characters.");
return;
}
}
else
{
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}register ", Specifier);
return;
}
account.Group = TShock.Config.DefaultRegistrationGroupName; // FIXME -- we should get this from the DB. --Why?
account.UUID = args.Player.UUID;
if (TShock.UserAccounts.GetUserAccountByName(account.Name) == null && account.Name != TSServerPlayer.AccountName) // Cheap way of checking for existance of a user
{
args.Player.SendSuccessMessage("Account \"{0}\" has been registered.", account.Name);
args.Player.SendSuccessMessage("Your password is {0}.", echoPassword);
TShock.UserAccounts.AddUserAccount(account);
TShock.Log.ConsoleInfo("{0} registered an account: \"{1}\".", args.Player.Name, account.Name);
}
else
{
args.Player.SendErrorMessage("Sorry, " + account.Name + " was already taken by another person.");
args.Player.SendErrorMessage("Please try a different username.");
TShock.Log.ConsoleInfo(args.Player.Name + " failed to register an existing account: " + account.Name);
}
}
catch (UserAccountManagerException ex)
{
args.Player.SendErrorMessage("Sorry, an error occured: " + ex.Message + ".");
TShock.Log.ConsoleError("RegisterUser returned an error: " + ex);
}
}
private static void ManageUsers(CommandArgs args)
{
// This guy needs to be here so that people don't get exceptions when they type /user
if (args.Parameters.Count < 1)
{
args.Player.SendErrorMessage("Invalid user syntax. Try {0}user help.", Specifier);
return;
}
string subcmd = args.Parameters[0];
// Add requires a username, password, and a group specified.
if (subcmd == "add" && args.Parameters.Count == 4)
{
var account = new UserAccount();
account.Name = args.Parameters[1];
try
{
account.CreateBCryptHash(args.Parameters[2]);
}
catch (ArgumentOutOfRangeException)
{
args.Player.SendErrorMessage("Password must be greater than or equal to " + TShock.Config.MinimumPasswordLength + " characters.");
return;
}
account.Group = args.Parameters[3];
try
{
TShock.UserAccounts.AddUserAccount(account);
args.Player.SendSuccessMessage("Account " + account.Name + " has been added to group " + account.Group + "!");
TShock.Log.ConsoleInfo(args.Player.Name + " added Account " + account.Name + " to group " + account.Group);
}
catch (GroupNotExistsException)
{
args.Player.SendErrorMessage("Group " + account.Group + " does not exist!");
}
catch (UserAccountExistsException)
{
args.Player.SendErrorMessage("User " + account.Name + " already exists!");
}
catch (UserAccountManagerException e)
{
args.Player.SendErrorMessage("User " + account.Name + " could not be added, check console for details.");
TShock.Log.ConsoleError(e.ToString());
}
}
// User deletion requires a username
else if (subcmd == "del" && args.Parameters.Count == 2)
{
var account = new UserAccount();
account.Name = args.Parameters[1];
try
{
TShock.UserAccounts.RemoveUserAccount(account);
args.Player.SendSuccessMessage("Account removed successfully.");
TShock.Log.ConsoleInfo(args.Player.Name + " successfully deleted account: " + args.Parameters[1] + ".");
}
catch (UserAccountNotExistException)
{
args.Player.SendErrorMessage("The user " + account.Name + " does not exist! Deleted nobody!");
}
catch (UserAccountManagerException ex)
{
args.Player.SendErrorMessage(ex.Message);
TShock.Log.ConsoleError(ex.ToString());
}
}
// Password changing requires a username, and a new password to set
else if (subcmd == "password" && args.Parameters.Count == 3)
{
var account = new UserAccount();
account.Name = args.Parameters[1];
try
{
TShock.UserAccounts.SetUserAccountPassword(account, args.Parameters[2]);
TShock.Log.ConsoleInfo(args.Player.Name + " changed the password of account " + account.Name);
args.Player.SendSuccessMessage("Password change succeeded for " + account.Name + ".");
}
catch (UserAccountNotExistException)
{
args.Player.SendErrorMessage("User " + account.Name + " does not exist!");
}
catch (UserAccountManagerException e)
{
args.Player.SendErrorMessage("Password change for " + account.Name + " failed! Check console!");
TShock.Log.ConsoleError(e.ToString());
}
catch (ArgumentOutOfRangeException)
{
args.Player.SendErrorMessage("Password must be greater than or equal to " + TShock.Config.MinimumPasswordLength + " characters.");
}
}
// Group changing requires a username or IP address, and a new group to set
else if (subcmd == "group" && args.Parameters.Count == 3)
{
var account = new UserAccount();
account.Name = args.Parameters[1];
try
{
TShock.UserAccounts.SetUserGroup(account, args.Parameters[2]);
TShock.Log.ConsoleInfo(args.Player.Name + " changed account " + account.Name + " to group " + args.Parameters[2] + ".");
args.Player.SendSuccessMessage("Account " + account.Name + " has been changed to group " + args.Parameters[2] + "!");
}
catch (GroupNotExistsException)
{
args.Player.SendErrorMessage("That group does not exist!");
}
catch (UserAccountNotExistException)
{
args.Player.SendErrorMessage("User " + account.Name + " does not exist!");
}
catch (UserAccountManagerException e)
{
args.Player.SendErrorMessage("User " + account.Name + " could not be added. Check console for details.");
TShock.Log.ConsoleError(e.ToString());
}
}
else if (subcmd == "help")
{
args.Player.SendInfoMessage("Use command help:");
args.Player.SendInfoMessage("{0}user add username password group -- Adds a specified user", Specifier);
args.Player.SendInfoMessage("{0}user del username -- Removes a specified user", Specifier);
args.Player.SendInfoMessage("{0}user password username newpassword -- Changes a user's password", Specifier);
args.Player.SendInfoMessage("{0}user group username newgroup -- Changes a user's group", Specifier);
}
else
{
args.Player.SendErrorMessage("Invalid user syntax. Try {0}user help.", Specifier);
}
}
#endregion
#region Stupid commands
private static void ServerInfo(CommandArgs args)
{
args.Player.SendInfoMessage("Memory usage: " + Process.GetCurrentProcess().WorkingSet64);
args.Player.SendInfoMessage("Allocated memory: " + Process.GetCurrentProcess().VirtualMemorySize64);
args.Player.SendInfoMessage("Total processor time: " + Process.GetCurrentProcess().TotalProcessorTime);
args.Player.SendInfoMessage("WinVer: " + Environment.OSVersion);
args.Player.SendInfoMessage("Proc count: " + Environment.ProcessorCount);
args.Player.SendInfoMessage("Machine name: " + Environment.MachineName);
}
private static void WorldInfo(CommandArgs args)
{
args.Player.SendInfoMessage("World name: " + (TShock.Config.UseServerName ? TShock.Config.ServerName : Main.worldName));
args.Player.SendInfoMessage("World size: {0}x{1}", Main.maxTilesX, Main.maxTilesY);
args.Player.SendInfoMessage("World ID: " + Main.worldID);
}
#endregion
#region Player Management Commands
private static void GrabUserUserInfo(CommandArgs args)
{
if (args.Parameters.Count < 1)
{
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}userinfo ", Specifier);
return;
}
var players = TShock.Utils.FindPlayer(args.Parameters[0]);
if (players.Count < 1)
args.Player.SendErrorMessage("Invalid player.");
else if (players.Count > 1)
TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
else
{
var message = new StringBuilder();
message.Append("IP Address: ").Append(players[0].IP);
if (players[0].Account != null && players[0].IsLoggedIn)
message.Append(" | Logged in as: ").Append(players[0].Account.Name).Append(" | Group: ").Append(players[0].Group.Name);
args.Player.SendSuccessMessage(message.ToString());
}
}
private static void ViewAccountInfo(CommandArgs args)
{
if (args.Parameters.Count < 1)
{
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}accountinfo ", Specifier);
return;
}
string username = String.Join(" ", args.Parameters);
if (!string.IsNullOrWhiteSpace(username))
{
var account = TShock.UserAccounts.GetUserAccountByName(username);
if (account != null)
{
DateTime LastSeen;
string Timezone = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).Hours.ToString("+#;-#");
if (DateTime.TryParse(account.LastAccessed, out LastSeen))
{
LastSeen = DateTime.Parse(account.LastAccessed).ToLocalTime();
args.Player.SendSuccessMessage("{0}'s last login occured {1} {2} UTC{3}.", account.Name, LastSeen.ToShortDateString(),
LastSeen.ToShortTimeString(), Timezone);
}
if (args.Player.Group.HasPermission(Permissions.advaccountinfo))
{
List KnownIps = JsonConvert.DeserializeObject>(account.KnownIps?.ToString() ?? string.Empty);
string ip = KnownIps?[KnownIps.Count - 1] ?? "N/A";
DateTime Registered = DateTime.Parse(account.Registered).ToLocalTime();
args.Player.SendSuccessMessage("{0}'s group is {1}.", account.Name, account.Group);
args.Player.SendSuccessMessage("{0}'s last known IP is {1}.", account.Name, ip);
args.Player.SendSuccessMessage("{0}'s register date is {1} {2} UTC{3}.", account.Name, Registered.ToShortDateString(), Registered.ToShortTimeString(), Timezone);
}
}
else
args.Player.SendErrorMessage("User {0} does not exist.", username);
}
else args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}accountinfo ", Specifier);
}
private static void Kick(CommandArgs args)
{
if (args.Parameters.Count < 1)
{
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}kick [reason]", Specifier);
return;
}
if (args.Parameters[0].Length == 0)
{
args.Player.SendErrorMessage("Missing player name.");
return;
}
string plStr = args.Parameters[0];
var players = TShock.Utils.FindPlayer(plStr);
if (players.Count == 0)
{
args.Player.SendErrorMessage("Invalid player!");
}
else if (players.Count > 1)
{
TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
}
else
{
string reason = args.Parameters.Count > 1
? String.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1))
: "Misbehaviour.";
if (!TShock.Utils.Kick(players[0], reason, !args.Player.RealPlayer, false, args.Player.Name))
{
args.Player.SendErrorMessage("You can't kick another admin!");
}
}
}
private static void Ban(CommandArgs args)
{
string subcmd = args.Parameters.Count == 0 ? "help" : args.Parameters[0].ToLower();
switch (subcmd)
{
case "add":
#region Add Ban
{
if (args.Parameters.Count < 2)
{
args.Player.SendErrorMessage("Invalid command. Format: {0}ban add [time] [reason]", Specifier);
args.Player.SendErrorMessage("Example: {0}ban add Shank 10d Hacking and cheating", Specifier);
args.Player.SendErrorMessage("Example: {0}ban add Ash", Specifier);
args.Player.SendErrorMessage("Use the time 0 (zero) for a permanent ban.");
return;
}
// Used only to notify if a ban was successful and who the ban was about
bool success = false;
string targetGeneralizedName = "";
// Effective ban target assignment
List players = TShock.Utils.FindPlayer(args.Parameters[1]);
UserAccount offlineUserAccount = TShock.UserAccounts.GetUserAccountByName(args.Parameters[1]);
// Storage variable to determine if the command executor is the server console
// If it is, we assume they have full control and let them override permission checks
bool callerIsServerConsole = false;
if (args.Player is TSServerPlayer)
{
callerIsServerConsole = true;
}
// The ban reason the ban is going to have
string banReason = "Unknown.";
// The default ban length
// 0 is permanent ban, otherwise temp ban
int banLengthInSeconds = 0;
// Figure out if param 2 is a time or 0 or garbage
if (args.Parameters.Count >= 3)
{
bool parsedOkay = false;
if (!(args.Parameters[2] == "0"))
{
parsedOkay = TShock.Utils.TryParseTime(args.Parameters[2], out banLengthInSeconds);
} else {
parsedOkay = true;
}
if (!parsedOkay)
{
args.Player.SendErrorMessage("Invalid time format. Example: 10d+5h+3m-2s.");
args.Player.SendErrorMessage("Use 0 (zero) for a permanent ban.");
return;
}
}
// If a reason exists, use the given reason.
if (args.Parameters.Count > 3)
{
banReason = String.Join(" ", args.Parameters.Skip(3));
}
// Bad case: Players contains more than 1 person so we can't ban them
if (players.Count > 1)
{
TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
return;
}
// Good case: Online ban for matching character.
if (players.Count == 1)
{
TSPlayer target = players[0];
if (target.HasPermission(Permissions.immunetoban) && !callerIsServerConsole)
{
args.Player.SendErrorMessage("Permission denied. Target {0} is immune to ban.", target.Name);
return;
}
targetGeneralizedName = target.Name;
success = TShock.Bans.AddBan2(target.IP, target.Name, target.UUID, target.Account.Name, banReason, false, args.Player.Account.Name,
banLengthInSeconds == 0 ? "" : DateTime.UtcNow.AddSeconds(banLengthInSeconds).ToString("s"));
// Since this is an online ban, we need to dc the player and tell them now.
if (success)
{
if (banLengthInSeconds == 0)
{
target.Disconnect(String.Format("Permanently banned for {0}", banReason));
}
else
{
target.Disconnect(String.Format("Banned for {0} seconds for {1}", banLengthInSeconds, banReason));
}
}
}
// Case: Players & user are invalid, could be IP?
// Note: Order matters. If this method is above the online player check,
// This enables you to ban an IP even if the player exists in the database as a player.
// You'll get two bans for the price of one, in theory, because both IP and user named IP will be banned.
// ??? edge cases are weird, but this is going to happen
// The only way around this is to either segregate off the IP code or do something else.
if (players.Count == 0)
{
// If the target is a valid IP...
string pattern = @"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
Regex r = new Regex(pattern, RegexOptions.IgnoreCase);
if (r.IsMatch(args.Parameters[1])) {
targetGeneralizedName = "IP: " + args.Parameters[1];
success = TShock.Bans.AddBan2(args.Parameters[1], "", "", "", banReason,
false, args.Player.Account.Name, banLengthInSeconds == 0 ? "" : DateTime.UtcNow.AddSeconds(banLengthInSeconds).ToString("s"));
if (success && offlineUserAccount != null)
{
args.Player.SendSuccessMessage("Target IP {0} was banned successfully.", targetGeneralizedName);
args.Player.SendErrorMessage("Note: An account named with this IP address also exists.");
args.Player.SendErrorMessage("Note: It will also be banned.");
}
} else {
// Apparently there is no way to not IP ban someone
// This means that where we would normally just ban a "character name" here
// We can't because it requires some IP as a primary key.
if (offlineUserAccount == null)
{
args.Player.SendErrorMessage("Unable to ban target {0}.", args.Parameters[1]);
args.Player.SendErrorMessage("Target is not a valid IP address, a valid online player, or a known offline user.");
return;
}
}
}
// Case: Offline ban
if (players.Count == 0 && offlineUserAccount != null)
{
// Catch: we don't know an offline player's last login character name
// This means that we're banning their *user name* on the assumption that
// user name == character name
// (which may not be true)
// This needs to be fixed in a future implementation.
targetGeneralizedName = offlineUserAccount.Name;
if (TShock.Groups.GetGroupByName(offlineUserAccount.Group).HasPermission(Permissions.immunetoban) &&
!callerIsServerConsole)
{
args.Player.SendErrorMessage("Permission denied. Target {0} is immune to ban.", targetGeneralizedName);
return;
}
if (offlineUserAccount.KnownIps == null)
{
args.Player.SendErrorMessage("Unable to ban target {0} because they have no valid IP to ban.", targetGeneralizedName);
return;
}
string lastIP = JsonConvert.DeserializeObject>(offlineUserAccount.KnownIps).Last();
success =
TShock.Bans.AddBan2(lastIP,
"", offlineUserAccount.UUID, offlineUserAccount.Name, banReason, false, args.Player.Account.Name,
banLengthInSeconds == 0 ? "" : DateTime.UtcNow.AddSeconds(banLengthInSeconds).ToString("s"));
}
if (success)
{
args.Player.SendSuccessMessage("{0} was successfully banned.", targetGeneralizedName);
args.Player.SendInfoMessage("Length: {0}", banLengthInSeconds == 0 ? "Permanent." : banLengthInSeconds + " seconds.");
args.Player.SendInfoMessage("Reason: {0}", banReason);
if (!args.Silent)
{
if (banLengthInSeconds == 0)
{
TSPlayer.All.SendErrorMessage("{0} was permanently banned by {1} for: {2}",
targetGeneralizedName, args.Player.Account.Name, banReason);
}
else
{
TSPlayer.All.SendErrorMessage("{0} was temp banned for {1} seconds by {2} for: {3}",
targetGeneralizedName, banLengthInSeconds, args.Player.Account.Name, banReason);
}
}
}
else
{
args.Player.SendErrorMessage("{0} was NOT banned due to a database error or other system problem.", targetGeneralizedName);
args.Player.SendErrorMessage("If this player is online, they have NOT been kicked.");
args.Player.SendErrorMessage("Check the system logs for details.");
}
return;
}
#endregion
case "del":
#region Delete ban
{
if (args.Parameters.Count != 2)
{
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}ban del ", Specifier);
return;
}
string plStr = args.Parameters[1];
Ban ban = TShock.Bans.GetBanByName(plStr, false);
if (ban != null)
{
if (TShock.Bans.RemoveBan(ban.Name, true))
args.Player.SendSuccessMessage("Unbanned {0} ({1}).", ban.Name, ban.IP);
else
args.Player.SendErrorMessage("Failed to unban {0} ({1}), check logs.", ban.Name, ban.IP);
}
else
args.Player.SendErrorMessage("No bans for {0} exist.", plStr);
}
#endregion
return;
case "delip":
#region Delete IP ban
{
if (args.Parameters.Count != 2)
{
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}ban delip ", Specifier);
return;
}
string ip = args.Parameters[1];
Ban ban = TShock.Bans.GetBanByIp(ip);
if (ban != null)
{
if (TShock.Bans.RemoveBan(ban.IP, false))
args.Player.SendSuccessMessage("Unbanned IP {0} ({1}).", ban.IP, ban.Name);
else
args.Player.SendErrorMessage("Failed to unban IP {0} ({1}), check logs.", ban.IP, ban.Name);
}
else
args.Player.SendErrorMessage("IP {0} is not banned.", ip);
}
#endregion
return;
case "help":
#region Help
{
int pageNumber;
if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber))
return;
var lines = new List
{
"add