/* TShock, a server mod for Terraria Copyright (C) 2011 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 System; using System.Data; using System.IO; using System.Collections.Generic; using System.Linq; using MySql.Data.MySqlClient; using System.Text.RegularExpressions; namespace TShockAPI.DB { public class UserManager { private IDbConnection database; public UserManager(IDbConnection db) { database = db; var table = new SqlTable("Users", new SqlColumn("ID", MySqlDbType.Int32) {Primary = true, AutoIncrement = true}, new SqlColumn("Username", MySqlDbType.VarChar, 32) {Unique = true}, new SqlColumn("Password", MySqlDbType.VarChar, 128), new SqlColumn("Usergroup", MySqlDbType.Text), new SqlColumn("IP", MySqlDbType.VarChar, 16) ); var creator = new SqlTableCreator(db, db.GetSqlType() == SqlType.Sqlite ? (IQueryBuilder) new SqliteQueryCreator() : new MysqlQueryCreator()); creator.EnsureExists(table); } /// /// Adds a given username to the database /// /// User user public void AddUser(User user) { if (!TShock.Groups.GroupExists(user.Group)) throw new GroupNotExistsException(user.Group); int ret; try { ret = database.Query("INSERT INTO Users (Username, Password, UserGroup, IP) VALUES (@0, @1, @2, @3);", user.Name, TShock.Utils.HashPassword(user.Password), user.Group, user.Address); } catch (Exception ex) { // Detect duplicate user using a regexp as Sqlite doesn't have well structured exceptions if (Regex.IsMatch(ex.Message, "Username.*not unique")) throw new UserExistsException(user.Name); throw new UserManagerException("AddUser SQL returned an error (" + ex.Message + ")", ex); } if (1 > ret) throw new UserExistsException(user.Name); } /// /// Removes a given username from the database /// /// User user public void RemoveUser(User user) { try { int affected = -1; if (!string.IsNullOrEmpty(user.Address)) { affected = database.Query("DELETE FROM Users WHERE IP=@0", user.Address); } else { affected = database.Query("DELETE FROM Users WHERE Username=@0", user.Name); } if (affected < 1) throw new UserNotExistException(string.IsNullOrEmpty(user.Address) ? user.Name : user.Address); } catch (Exception ex) { throw new UserManagerException("RemoveUser SQL returned an error", ex); } } /// /// Sets the Hashed Password for a given username /// /// User user /// string password public void SetUserPassword(User user, string password) { try { if ( database.Query("UPDATE Users SET Password = @0 WHERE Username = @1;", TShock.Utils.HashPassword(password), user.Name) == 0) throw new UserNotExistException(user.Name); } catch (Exception ex) { throw new UserManagerException("SetUserPassword SQL returned an error", ex); } } /// /// Sets the group for a given username /// /// User user /// string group public void SetUserGroup(User user, string group) { try { Group grp = TShock.Groups.GetGroupByName(group); if (null == grp) throw new GroupNotExistsException(group); if (database.Query("UPDATE Users SET UserGroup = @0 WHERE Username = @1;", group, user.Name) == 0) throw new UserNotExistException(user.Name); // Update player group reference for any logged in player foreach (var player in TShock.Players.Where(p => null != p && p.UserAccountName == user.Name)) { player.Group = grp; } } catch (Exception ex) { throw new UserManagerException("SetUserGroup SQL returned an error", ex); } } public int GetUserID(string username) { try { using (var reader = database.QueryReader("SELECT * FROM Users WHERE Username=@0", username)) { if (reader.Read()) { return reader.Get("ID"); } } } catch (Exception ex) { Log.ConsoleError("FetchHashedPasswordAndGroup SQL returned an error: " + ex); } return -1; } /// /// Returns a Group for a ip from the database /// /// string ip public Group GetGroupForIP(string ip) { try { using (var reader = database.QueryReader("SELECT * FROM Users WHERE IP=@0", ip)) { if (reader.Read()) { string group = reader.Get("UserGroup"); return TShock.Utils.GetGroup(group); } } } catch (Exception ex) { Log.ConsoleError("GetGroupForIP SQL returned an error: " + ex); } return TShock.Utils.GetGroup(TShock.Config.DefaultGuestGroupName); } public Group GetGroupForIPExpensive(string ip) { try { using (var reader = database.QueryReader("SELECT IP, UserGroup FROM Users")) { while (reader.Read()) { if (TShock.Utils.GetIPv4Address(reader.Get("IP")) == ip) { return TShock.Utils.GetGroup(reader.Get("UserGroup")); } } } } catch (Exception ex) { Log.ConsoleError("GetGroupForIP SQL returned an error: " + ex); } return TShock.Utils.GetGroup(TShock.Config.DefaultGuestGroupName); } public User GetUserByName(string name) { try { return GetUser(new User {Name = name}); } catch (UserManagerException) { return null; } } public User GetUserByID(int id) { try { return GetUser(new User {ID = id}); } catch (UserManagerException) { return null; } } public User GetUserByIP(string ip) { try { return GetUser(new User {Address = ip}); } catch (UserManagerException) { return null; } } public User GetUser(User user) { try { QueryResult result; if (0 != user.ID) result = database.QueryReader("SELECT * FROM Users WHERE ID=@0", user.ID); else if (string.IsNullOrEmpty(user.Address)) result = database.QueryReader("SELECT * FROM Users WHERE Username=@0", user.Name); else result = database.QueryReader("SELECT * FROM Users WHERE IP=@0", user.Address); if (result.Read()) { user = LoadUserFromResult(user, result); // Check for multiple matches if (!result.Read()) return user; if (0 != user.ID) throw new UserManagerException(String.Format("Multiple users found for id '{0}'", user.ID)); else if (string.IsNullOrEmpty(user.Address)) throw new UserManagerException(String.Format("Multiple users found for name '{0}'", user.Name)); else throw new UserManagerException(String.Format("Multiple users found for ip '{0}'", user.Address)); } } catch (Exception ex) { throw new UserManagerException("GetUser SQL returned an error (" + ex.Message + ")", ex); } throw new UserNotExistException(string.IsNullOrEmpty(user.Address) ? user.Name : user.Address); } public List GetUsers() { try { List users = new List(); using (var reader = database.QueryReader("SELECT * FROM Users")) { while (reader.Read()) { users.Add(LoadUserFromResult(new User(), reader)); } return users; } } catch (Exception ex) { Log.Error(ex.ToString()); } return null; } private User LoadUserFromResult(User user, QueryResult result) { user.ID = result.Get("ID"); user.Group = result.Get("Usergroup"); user.Password = result.Get("Password"); user.Name = result.Get("Username"); user.Address = result.Get("IP"); return user; } } public class User { public int ID { get; set; } public string Name { get; set; } public string Password { get; set; } public string Group { get; set; } public string Address { get; set; } public User(string ip, string name, string pass, string group) { Address = ip; Name = name; Password = pass; Group = group; } public User() { Address = ""; Name = ""; Password = ""; Group = ""; } } [Serializable] public class UserManagerException : Exception { public UserManagerException(string message) : base(message) { } public UserManagerException(string message, Exception inner) : base(message, inner) { } } [Serializable] public class UserExistsException : UserManagerException { public UserExistsException(string name) : base("User '" + name + "' already exists") { } } [Serializable] public class UserNotExistException : UserManagerException { public UserNotExistException(string name) : base("User '" + name + "' does not exist") { } } [Serializable] public class GroupNotExistsException : UserManagerException { public GroupNotExistsException(string group) : base("Group '" + group + "' does not exist") { } } }