From 0af23d3608d2f6bc3aafb9769a24c3d405389ec1 Mon Sep 17 00:00:00 2001 From: ManfredAabye Date: Sun, 22 Jun 2025 15:06:06 +0200 Subject: [PATCH] 22062025 --- .../IMoneyManager.cs | 135 + .../MySQLMoneyManager.cs | 1938 ++++++++ .../MySQLSuperManager.cs | 100 + .../Properties/AssemblyInfo.cs | 32 + .../TransactionData.cs | 291 ++ .../prebuild-MySQLMoneyDataWrapper.xml | 39 + .../CurrencyStreamHandler.cs | 61 + .../CustomSimpleStreamHandler.cs | 110 + .../IMoneyDBService.cs | 163 + .../IMoneyServiceCore.cs | 79 + .../LandtoolStreamHandler.cs | 37 + .../MoneyDBService.cs | 1205 +++++ .../MoneyServerBase.cs | 469 ++ .../MoneyXmlRpcModule.cs | 3885 +++++++++++++++++ .../OpenSim.Grid.MoneyServer/Program.cs | 74 + .../Properties/AssemblyInfo.cs | 34 + .../prebuild-MoneyServer.xml | 45 + .../DTLNSLMoneyModule.cs | 2246 ++++++++++ .../NSLCertificateTools.cs | 308 ++ .../OpenSim.Modules.Currency/NSLXmlRpc.cs | 216 + .../Properties/AssemblyInfo.cs | 34 + .../prebuild-OpenSimModulesCurrency.xml | 45 + addon-modules/README | 9 + bin/MoneyServer.dll.config | 29 + bin/MoneyServer.ini.example | 99 + bin/OpenSim.ini.sample | 55 + bin/SineWaveCert.pfx | Bin 0 -> 1755 bytes bin/server_cert.p12 | Bin 0 -> 2685 bytes 28 files changed, 11738 insertions(+) create mode 100644 addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs create mode 100644 addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs create mode 100644 addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs create mode 100644 addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/Properties/AssemblyInfo.cs create mode 100644 addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs create mode 100644 addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/prebuild-MySQLMoneyDataWrapper.xml create mode 100644 addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/CurrencyStreamHandler.cs create mode 100644 addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/CustomSimpleStreamHandler.cs create mode 100644 addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/IMoneyDBService.cs create mode 100644 addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs create mode 100644 addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/LandtoolStreamHandler.cs create mode 100644 addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/MoneyDBService.cs create mode 100644 addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/MoneyServerBase.cs create mode 100644 addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs create mode 100644 addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/Program.cs create mode 100644 addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/Properties/AssemblyInfo.cs create mode 100644 addon-modules/OpenSim-Grid-MoneyServer/prebuild-MoneyServer.xml create mode 100644 addon-modules/OpenSim-Modules-Currency/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs create mode 100644 addon-modules/OpenSim-Modules-Currency/OpenSim.Modules.Currency/NSLCertificateTools.cs create mode 100644 addon-modules/OpenSim-Modules-Currency/OpenSim.Modules.Currency/NSLXmlRpc.cs create mode 100644 addon-modules/OpenSim-Modules-Currency/OpenSim.Modules.Currency/Properties/AssemblyInfo.cs create mode 100644 addon-modules/OpenSim-Modules-Currency/prebuild-OpenSimModulesCurrency.xml create mode 100644 addon-modules/README create mode 100644 bin/MoneyServer.dll.config create mode 100644 bin/MoneyServer.ini.example create mode 100644 bin/OpenSim.ini.sample create mode 100644 bin/SineWaveCert.pfx create mode 100644 bin/server_cert.p12 diff --git a/addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs b/addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs new file mode 100644 index 0000000..def1de6 --- /dev/null +++ b/addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs @@ -0,0 +1,135 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Funktion + Die Datei definiert das C#-Interface IMoneyManager. + Sie legt die Methoden fest, die eine Klasse zur Verwaltung von Geldtransaktionen, Guthaben und Benutzerinformationen implementieren muss. + Typische Aufgaben des Interfaces: + Abfragen und Ändern von Benutzer-Guthaben (getBalance, withdrawMoney, giveMoney) + Hinzufügen und Abfragen von Transaktionen (addTransaction, FetchTransaction) + Verwaltung von Benutzern (addUser, addUserInfo, fetchUserInfo, updateUserInfo) + Validierung und Ablauf von Transaktionen (ValidateTransfer, SetTransExpired) + +NullPointer-Checks & Fehlerquellen + + Interface selbst: + Das Interface enthält keine Implementierung. Es ist lediglich eine Methodendefinition. Das bedeutet: + Es gibt keine Logik, also auch keine Möglichkeit für NullPointer-Fehler innerhalb dieser Datei. + Fehlerquellen oder NullPointer-Probleme hängen ausschließlich von den konkreten Implementierungen dieser Methoden ab. + + Parameter: + Die Methoden nehmen meist Strings, UUIDs, Ints und komplexe Typen wie TransactionData oder UserInfo als Parameter. + In einer späteren Implementierung sollte geprüft werden, ob übergebene Objekte (z.B. user, transaction) null sind, bevor darauf zugegriffen wird. + Rückgabewerte wie TransactionData oder UserInfo könnten null sein, wenn nichts gefunden wird – auch das ist Sache der Implementierung. + + Rückgabewerte: + Methoden, die Objekte zurückgeben (FetchTransaction, fetchUserInfo), haben keine Angaben, ob sie null zurückgeben dürfen. + Das sollte dokumentiert oder in der Implementierung behandelt werden. + +Zusammenfassung + NullPointer-Gefahr: + Im Interface selbst nicht möglich, da keine Ausführung erfolgt. + Fehlerquellen: + Keine im Interface; alle Fehlerquellen und NullPointer-Risiken entstehen erst in den Klassen, die dieses Interface implementieren. + Funktion: + Definition aller nötigen Methoden zur Verwaltung von Geldtransaktionen und Benutzerinformationen im OpenSim-Kontext. + +Fazit: +Die Datei IMoneyManager.cs ist ein reines Methoden-Interface und enthält keine Logik, die zu NullPointer-Fehlern führen kann. +Typische Fehlerquellen und NullPointer sollten in den konkreten Implementierungen der Methoden sorgfältig behandelt werden (z.B. Nullprüfungen auf Parameter, Rückgabewerte). + */ + +#pragma warning disable IDE1006 + +using OpenMetaverse; + +namespace OpenSim.Data.MySQL.MySQLMoneyDataWrapper +{ + public interface IMoneyManager + { + /// Gets the balance. + /// The user identifier. + int getBalance(string userID); + + /// Withdraws the money. + /// The transaction identifier. + /// The sender identifier. + /// The amount. + bool withdrawMoney(UUID transactionID, string senderID, int amount); + + /// Gives the money. + /// The transaction identifier. + /// The receiver identifier. + /// The amount. + bool giveMoney(UUID transactionID, string receiverID, int amount); + + /// Adds the transaction. + /// The transaction. + bool addTransaction(TransactionData transaction); + + /// Updates the transaction status. + /// The transaction identifier. + /// The status. + /// The description. + bool updateTransactionStatus(UUID transactionID, int status, string description); + + /// Fetches the transaction. + /// The transaction identifier. + TransactionData FetchTransaction(UUID transactionID); + + /// Fetches the transaction. + /// The user identifier. + /// The start time. + /// The end time. + /// The index. + /// The ret number. + TransactionData[] FetchTransaction(string userID, int startTime, int endTime, uint index, uint retNum); + + /// Gets the transaction number. + /// The user identifier. + /// The start time. + /// The end time. + int getTransactionNum(string userID, int startTime, int endTime); + + /// Adds the user. + /// The user identifier. + /// The balance. + /// The status. + /// The type. + bool addUser(string userID, int balance, int status, int type); + + /// Sets the trans expired. + /// The dead time. + bool SetTransExpired(int deadTime); + + /// Validates the transfer. + /// The secure code. + /// The transaction identifier. + bool ValidateTransfer(string secureCode, UUID transactionID); + + /// Adds the user information. + /// The user. + bool addUserInfo(UserInfo user); + + /// Fetches the user information. + /// The user identifier. + UserInfo fetchUserInfo(string userID); + + /// Updates the user information. + /// The user. + bool updateUserInfo(UserInfo user); + } +} diff --git a/addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs b/addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs new file mode 100644 index 0000000..db06f4b --- /dev/null +++ b/addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs @@ -0,0 +1,1938 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/, http://www.nsl.tuis.ac.jp/ See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Funktion +Die Klasse MySQLMoneyManager implementiert das Interface IMoneyManager und ist das zentrale Datenbank-Backend für das OpenSim-Money-Modul (MySQL). Sie verwaltet: + + Verbindungen zur MySQL-Datenbank + Kontostände (balances) + Transaktionen (transactions) + Benutzerdaten (userinfo) + Verkaufstatistiken (totalsales) + Methoden zum Ein-/Auszahlen, Übertragen, Anlegen und Aktualisieren von Werten + +Null Pointer Checks & Fehlerquellen +Initialisierung und DB-Verbindung + Die Constructoren prüfen per .Any(p => string.IsNullOrEmpty(p)), ob alle benötigten Parameter gesetzt sind. Bei Fehlwerten gibt es einen klaren Exception-Throw. + Beim Öffnen der Datenbankverbindung wird jeder Fehler per Exception behandelt. + In allen Methoden, die auf die DB-Verbindung zugreifen, wird dbcon verwendet, das zentral initialisiert wird. + +Datenbankoperationen + Try-Catch-Blöcke sind an allen kritischen Stellen vorhanden, etwa beim Table-Setup, bei der Reconnect-Logik und bei Datenbankzugriffen. + Nach jedem Datenbankzugriff wird das Command-Objekt mit Dispose() freigegeben. + Beim Lesen von Datenbankwerten wird mit DBNull geprüft, ob ein Wert gesetzt ist, bevor er als String oder int gecastet wird. + Bei Methoden, die Objekte zurückgeben (z.B. FetchTransaction, fetchUserInfo), wird im Fehlerfall null zurückgegeben und im Catch geloggt. + +Null Pointer Checks + Bei Methoden wie addTransaction werden Objekteigenschaften vor Benutzung auf null geprüft und ggf. mit Defaultwerten gefüllt. + Bei Übergabeobjekten wie UserInfo wird vor dem Zugriff geprüft, ob z.B. Avatar null ist. + Rückgabewerte wie bei fetchUserInfo werden nur dann zurückgegeben, wenn die UserID wirklich gefunden wurde – sonst gibt es null. + Bei Methoden, die Listen zurückgeben (z.B. FetchTransaction(string userID, ...)), wird bei DB-Lesefehlern null oder ein leeres Array zurückgegeben. + +Fehlerquellen + Fehlerhafte oder fehlende DB-Verbindung: Exception und Logging. + SQL-Befehle mit Fehlern: Exception und Logging. + Ungültige Eingabewerte (z.B. null bei Strings/Objekten): Entweder direkter Return, Exception oder Logging. + Potentielle Race-Conditions werden durch lock (dbcon) reduziert. + Ressourcenlecks werden durch konsequentes .Dispose() der Commands/Reader vermieden. + +Positive Beispiele für Fehlerbehandlung + + Parameter-Checks: + C# + +if (requiredParameters.Any(p => string.IsNullOrEmpty(p))) { throw new ArgumentException(...); } + +DB-Reader mit Exception Handling: +C# + +catch (Exception e) { + m_log.Error("[MONEY MANAGER]: Fetching transaction failed 1: " + e.ToString()); + r.Close(); cmd.Dispose(); return null; +} + +Null/Default-Initialisierung: +C# + + if (transaction.ObjectUUID == null) transaction.ObjectUUID = UUID.Zero.ToString(); + if (transaction.ObjectName == null) transaction.ObjectName = string.Empty; + +Zusammenfassung/Fazit + Null Pointer: Das Risiko ist im Code durch Checks, Defaultwerte und Exception-Handling weitgehend minimiert. + Fehlerquellen: Vor allem externe Faktoren (DB-Verbindung, SQL-Syntax). Im Code wird alles sauber abgefangen und geloggt. + Funktion: Umfassende Datenbank-Backend-Klasse für OpenSim-Money, mit Methoden für alle wichtigen Operationen (Balance, Transaktionen, User, Verkauf). + +Empfehlung: +Der Code ist für produktiven Einsatz geeignet, robust gegen NullPointer-Fehler und typische Fehlerquellen. +Bei zukünftigen Erweiterungen empfiehlt sich: Bei allen Methoden, die Objekte oder komplexe Werte zurückgeben, weiterhin konsequent auf null prüfen und Exceptions sauber loggen. + */ + +using System; +using System.Data; +using System.Reflection; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using log4net; +using MySql.Data.MySqlClient; +using OpenMetaverse; +using System.Linq; + +namespace OpenSim.Data.MySQL.MySQLMoneyDataWrapper +{ + public class MySQLMoneyManager : IMoneyManager + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private string Table_of_Balances = "balances"; + private string Table_of_Transactions = "transactions"; + private string Table_of_TotalSales = "totalsales"; + private string Table_of_UserInfo = "userinfo"; + private int balances_rev = 0; + private int userinfo_rev = 0; + + private string connectString; + public MySqlConnection dbcon; + + + public MySQLMoneyManager(string hostname, string database, string username, string password, string cpooling, string port) + { + var requiredParameters = new[] { hostname, database, username, password, cpooling, port }; + + if (requiredParameters.Any(p => string.IsNullOrEmpty(p))) + { + throw new ArgumentException("All connection parameters must be provided."); + } + + string connectionString = $"Server={hostname};Port={port};Database={database};User ID={username};Password={password};Pooling={cpooling};"; + Initialise(connectionString); + } + + public MySQLMoneyManager(string connect) + { + Initialise(connect); + } + + private void Initialise(string connect) + { + // 1. Initialisiere Verbindungen + InitialiseConnection(connect); + + // 2. Überprüfe und erstelle Tabellen + CheckAndCreateTables(); + } + + // 1. Initialisiere Verbindungen + private void InitialiseConnection(string connect) + { + try + { + connectString = connect; + dbcon = new MySqlConnection(connectString); + dbcon.Open(); + } + catch (Exception e) + { + throw new Exception("[MONEY MANAGER]: Error initializing MySql Database: " + e.ToString()); + } + } + + // 2. Überprüfe und erstelle Tabellen + private void CheckAndCreateTables() + { + try + { + Dictionary tableList = CheckTables(); + + InitialiseBalancesTable(tableList); + InitialiseUserInfoTable(tableList); + InitialiseTransactionsTable(tableList); + InitialiseTotalSalesTable(tableList); + } + catch (Exception e) + { + m_log.Error("[MONEY MANAGER]: Error checking or creating tables: " + e.ToString()); + throw new Exception("[MONEY MANAGER]: Error checking or creating tables: " + e.ToString()); + } + } + + private void InitialiseBalancesTable(Dictionary tableList) + { + if (!tableList.ContainsKey(Table_of_Balances)) + { + try + { + CreateBalancesTable(); + } + catch (Exception e) + { + throw new Exception("[MONEY MANAGER]: Error creating balances table: " + e.ToString()); + } + } + else + { + string version = tableList[Table_of_Balances].Trim(); + int nVer = getTableVersionNum(version); + balances_rev = nVer; + switch (nVer) + { + case 1: //Rev.1 + UpdateBalancesTable1(); + UpdateBalancesTable2(); + UpdateBalancesTable3(); + break; + case 2: //Rev.2 + UpdateBalancesTable2(); + UpdateBalancesTable3(); + break; + case 3: //Rev.3 + UpdateBalancesTable3(); + break; + } + } + } + + private void InitialiseUserInfoTable(Dictionary tableList) + { + if (!tableList.ContainsKey(Table_of_UserInfo)) + { + try + { + CreateUserInfoTable(); + } + catch (Exception e) + { + throw new Exception("[MONEY MANAGER]: Error creating userinfo table: " + e.ToString()); + } + } + else + { + string version = tableList[Table_of_UserInfo].Trim(); + int nVer = getTableVersionNum(version); + userinfo_rev = nVer; + switch (nVer) + { + case 1: //Rev.1 + UpdateUserInfoTable1(); + UpdateUserInfoTable2(); + break; + case 2: //Rev.2 + UpdateUserInfoTable2(); + break; + } + } + } + + private void InitialiseTransactionsTable(Dictionary tableList) + { + if (!tableList.ContainsKey(Table_of_Transactions)) + { + try + { + CreateTransactionsTable(); + } + catch (Exception e) + { + throw new Exception("[MONEY MANAGER]: Error creating transactions table: " + e.ToString()); + } + } + else + { + string version = tableList[Table_of_Transactions].Trim(); + int nVer = getTableVersionNum(version); + switch (nVer) + { + case 2: //Rev.2 + UpdateTransactionsTable2(); + UpdateTransactionsTable3(); + UpdateTransactionsTable4(); + UpdateTransactionsTable5(); + UpdateTransactionsTable6(); + UpdateTransactionsTable7(); + UpdateTransactionsTable8(); + UpdateTransactionsTable9(); + UpdateTransactionsTable10(); + UpdateTransactionsTable11(); + break; + case 3: //Rev.3 + UpdateTransactionsTable3(); + UpdateTransactionsTable4(); + UpdateTransactionsTable5(); + UpdateTransactionsTable6(); + UpdateTransactionsTable7(); + UpdateTransactionsTable8(); + UpdateTransactionsTable9(); + UpdateTransactionsTable10(); + UpdateTransactionsTable11(); + break; + case 4: //Rev.4 + UpdateTransactionsTable4(); + UpdateTransactionsTable5(); + UpdateTransactionsTable6(); + UpdateTransactionsTable7(); + UpdateTransactionsTable8(); + UpdateTransactionsTable9(); + UpdateTransactionsTable10(); + UpdateTransactionsTable11(); + break; + case 5: //Rev.5 + UpdateTransactionsTable5(); + UpdateTransactionsTable6(); + UpdateTransactionsTable7(); + UpdateTransactionsTable8(); + UpdateTransactionsTable9(); + UpdateTransactionsTable10(); + UpdateTransactionsTable11(); + break; + case 6: //Rev.6 + UpdateTransactionsTable6(); + UpdateTransactionsTable7(); + UpdateTransactionsTable8(); + UpdateTransactionsTable9(); + UpdateTransactionsTable10(); + UpdateTransactionsTable11(); + break; + case 7: //Rev.7 + UpdateTransactionsTable7(); + UpdateTransactionsTable8(); + UpdateTransactionsTable9(); + UpdateTransactionsTable10(); + UpdateTransactionsTable11(); + break; + case 8: //Rev.8 + UpdateTransactionsTable8(); + UpdateTransactionsTable9(); + UpdateTransactionsTable10(); + UpdateTransactionsTable11(); + break; + case 9: //Rev.9 + UpdateTransactionsTable9(); + UpdateTransactionsTable10(); + UpdateTransactionsTable11(); + break; + case 10: //Rev.10 + UpdateTransactionsTable10(); + UpdateTransactionsTable11(); + break; + case 11: //Rev.11 + UpdateTransactionsTable11(); + break; + } + } + } + + private void InitialiseTotalSalesTable(Dictionary tableList) + { + if (!tableList.ContainsKey(Table_of_TotalSales)) + { + try + { + CreateTotalSalesTable(); + } + catch (Exception e) + { + throw new Exception("[MONEY MANAGER]: Error creating totalsales table: " + e.ToString()); + } + } + else + { + string version = tableList[Table_of_TotalSales].Trim(); + int nVer = getTableVersionNum(version); + switch (nVer) + { + case 1: //Rev.1 + UpdateTotalSalesTable1(); + UpdateTotalSalesTable2(); + break; + case 2: //Rev.2 + UpdateTotalSalesTable2(); + break; + } + } + } + + + + /// Gets the table version number. + /// The version. + private int getTableVersionNum(string version) + { + int nVer = 0; + + Regex _commentPattenRegex = new Regex(@"\w+\.(?\d+)"); + Match m = _commentPattenRegex.Match(version); + if (m.Success) + { + string ver = m.Groups["ver"].Value; + nVer = Convert.ToInt32(ver); + } + return nVer; + } + + /// Creates the balances table. + private void CreateBalancesTable() + { + string sql = string.Empty; + + sql = "CREATE TABLE `" + Table_of_Balances + "` ("; + sql += "`user` varchar(36) NOT NULL,"; + sql += "`balance` int(10) NOT NULL,"; + sql += "`status` tinyint(2) DEFAULT NULL,"; + sql += "`type` tinyint(2) NOT NULL DEFAULT 0,"; + sql += "PRIMARY KEY(`user`))"; + sql += "Engine=InnoDB DEFAULT CHARSET=utf8 "; + + sql += "COMMENT='Rev.4';"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + + + /// Creates the user information table. + private void CreateUserInfoTable() + { + string sql = string.Empty; + + sql = "CREATE TABLE `" + Table_of_UserInfo + "` ("; + sql += "`user` varchar(36) NOT NULL,"; + sql += "`simip` varchar(64) NOT NULL,"; + sql += "`avatar` varchar(50) NOT NULL,"; + sql += "`pass` varchar(36) NOT NULL DEFAULT '',"; + sql += "`type` tinyint(2) NOT NULL DEFAULT 0,"; + sql += "`class` tinyint(2) NOT NULL DEFAULT 0,"; + sql += "`serverurl` varchar(255) NOT NULL DEFAULT '',"; + sql += "PRIMARY KEY(`user`))"; + sql += "Engine=InnoDB DEFAULT CHARSET=utf8 "; + + sql += "COMMENT='Rev.3';"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + + + /// Creates the transactions table. + private void CreateTransactionsTable() + { + string sql = string.Empty; + + sql = "CREATE TABLE `" + Table_of_Transactions + "`("; + sql += "`UUID` varchar(36) NOT NULL,"; + sql += "`sender` varchar(36) NOT NULL,"; + sql += "`receiver` varchar(36) NOT NULL,"; + sql += "`amount` int(10) NOT NULL,"; + sql += "`senderBalance` int(10) NOT NULL DEFAULT -1,"; + sql += "`receiverBalance` int(10) NOT NULL DEFAULT -1,"; + sql += "`objectUUID` varchar(36) DEFAULT NULL,"; + sql += "`objectName` varchar(255) DEFAULT NULL,"; + sql += "`regionHandle` varchar(36) NOT NULL,"; + sql += "`regionUUID` varchar(36) NOT NULL,"; + sql += "`type` int(10) NOT NULL,"; + sql += "`time` int(11) NOT NULL,"; + sql += "`secure` varchar(36) NOT NULL,"; + sql += "`status` tinyint(1) NOT NULL,"; + sql += "`commonName` varchar(128) NOT NULL,"; + sql += "`description` varchar(255) DEFAULT NULL,"; + sql += "PRIMARY KEY(`UUID`))"; + sql += "Engine=InnoDB DEFAULT CHARSET=utf8 "; + + sql += "COMMENT='Rev.12';"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + + + /// Creates the total sales table. + private void CreateTotalSalesTable() + { + string sql = string.Empty; + + sql = "CREATE TABLE `" + Table_of_TotalSales + "` ("; + sql += "`UUID` varchar(36) NOT NULL,"; + sql += "`user` varchar(36) NOT NULL,"; + sql += "`objectUUID` varchar(36) NOT NULL,"; + sql += "`type` int(10) NOT NULL,"; + sql += "`TotalCount` int(10) NOT NULL DEFAULT 0,"; + sql += "`TotalAmount` int(10) NOT NULL DEFAULT 0,"; + sql += "`time` int(11) NOT NULL,"; + sql += "PRIMARY KEY(`UUID`))"; + sql += "Engine=InnoDB DEFAULT CHARSET=utf8 "; + + sql += "COMMENT='Rev.3';"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + + initTotalSalesTable(); + } + + + /// Updates the balances table1. + private void UpdateBalancesTable1() + { + m_log.Info("[MONEY MANAGER]: Converting Balance Table..."); + string sql = string.Empty; + + sql = "SELECT COUNT(*) FROM " + Table_of_Balances; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + int resultCount = int.Parse(cmd.ExecuteScalar().ToString()); + cmd.Dispose(); + + sql = "SELECT * FROM " + Table_of_Balances; + cmd = new MySqlCommand(sql, dbcon); + MySqlDataReader dbReader = cmd.ExecuteReader(); + + int l = 0; + string[,] row = new string[resultCount, dbReader.FieldCount]; + while (dbReader.Read()) + { + for (int i = 0; i < dbReader.FieldCount; i++) + { + row[l, i] = dbReader.GetString(i); + } + l++; + } + dbReader.Close(); + cmd.Dispose(); + + bool updatedb = true; + for (int i = 0; i < resultCount; i++) + { + string uuid = Regex.Replace(row[i, 0], @"@.+$", ""); + if (uuid != row[i, 0]) + { + int amount = int.Parse(row[i, 1]); + int balance = getBalance(uuid); + if (balance >= 0) + { + amount += balance; + updatedb = updateBalance(uuid, amount); + } + else + { + updatedb = addUser(uuid, amount, int.Parse(row[i, 2]), 0); + } + if (!updatedb) break; + } + } + + // Delete + if (updatedb) + { + for (int i = 0; i < resultCount; i++) + { + string uuid = Regex.Replace(row[i, 0], @"@.+$", ""); + if (uuid != row[i, 0]) + { + sql = "DELETE FROM " + Table_of_Balances + " WHERE user = ?uuid"; + cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?uuid", row[i, 0]); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + } + + + sql = "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_Balances + "` "; + sql += "COMMENT = 'Rev.2';"; + sql += "COMMIT;"; + cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + } + + + /// Updates the balances table2. + private void UpdateBalancesTable2() + { + string sql = string.Empty; + + sql = "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_Balances + "` "; + sql += "MODIFY COLUMN `user` varchar(36) NOT NULL,"; + sql += "COMMENT = 'Rev.3';"; + sql += "COMMIT;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + + + /// Updates the balances table3. + private void UpdateBalancesTable3() + { + string sql = string.Empty; + + sql = "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_Balances + "` "; + sql += "ADD `type` tinyint(2) NOT NULL DEFAULT 0 AFTER `status`,"; + sql += "COMMENT = 'Rev.4';"; + sql += "COMMIT;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + + /// Updates the user information table1. + private void UpdateUserInfoTable1() + { + //m_log.Info("[MONEY MANAGER]: Converting UserInfo Table..."); + string sql = string.Empty; + + sql = "SELECT COUNT(*) FROM " + Table_of_UserInfo; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + int resultCount = int.Parse(cmd.ExecuteScalar().ToString()); + cmd.Dispose(); + + sql = "SELECT * FROM " + Table_of_UserInfo; + cmd = new MySqlCommand(sql, dbcon); + MySqlDataReader dbReader = cmd.ExecuteReader(); + + int l = 0; + string[,] row = new string[resultCount, dbReader.FieldCount]; + while (dbReader.Read()) + { + for (int i = 0; i < dbReader.FieldCount; i++) + { + row[l, i] = dbReader.GetString(i); + } + l++; + } + dbReader.Close(); + cmd.Dispose(); + + // UniversalID -> uuid, url, name, pass + bool updatedb = true; + for (int i = 0; i < resultCount; i++) + { + string uuid = Regex.Replace(row[i, 0], @"@.+$", ""); + if (uuid != row[i, 0]) + { + UserInfo userInfo = fetchUserInfo(uuid); + if (userInfo == null) + { + userInfo = new UserInfo(); + userInfo.UserID = uuid; + userInfo.SimIP = row[i, 1]; + userInfo.Avatar = row[i, 2]; + userInfo.PswHash = row[i, 3]; + updatedb = addUserInfo(userInfo); + } + } + } + + // Delete + if (updatedb) + { + for (int i = 0; i < resultCount; i++) + { + string uuid = Regex.Replace(row[i, 0], @"@.+$", ""); + if (uuid != row[i, 0]) + { + sql = "DELETE FROM " + Table_of_UserInfo + " WHERE user = ?uuid"; + cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?uuid", row[i, 0]); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + } + + // + sql = "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_UserInfo + "` "; + sql += "COMMENT = 'Rev.2';"; + sql += "COMMIT;"; + cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + } + + /// Updates the user information table2. + private void UpdateUserInfoTable2() + { + string sql = string.Empty; + + sql = "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_UserInfo + "` "; + sql += "MODIFY COLUMN `user` varchar(36) NOT NULL,"; + sql += "MODIFY COLUMN `pass` varchar(36) NOT NULL DEFAULT '',"; + sql += "ADD `type` tinyint(2) NOT NULL DEFAULT 0 AFTER `pass`,"; + sql += "ADD `class` tinyint(2) NOT NULL DEFAULT 0 AFTER `type`,"; + sql += "ADD `serverurl` varchar(255) NOT NULL DEFAULT '' AFTER `class`,"; + sql += "COMMENT = 'Rev.3';"; + sql += "COMMIT;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + + + // update Transactions Table + + /// + /// update transactions table from Rev.2 to Rev.3 + /// + private void UpdateTransactionsTable2() + { + string sql = string.Empty; + + sql = "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_Transactions + "` "; + sql += "ADD(`objectUUID` varchar(36) DEFAULT NULL AFTER `amount`),"; + sql += "COMMENT = 'Rev.3';"; + sql += "COMMIT;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + + + /// + /// update transactions table from Rev.3 to Rev.4 + /// + private void UpdateTransactionsTable3() + { + string sql = string.Empty; + + sql = "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_Transactions + "` "; + sql += "ADD(`secure` varchar(36) NOT NULL AFTER `time`),"; + sql += "COMMENT = 'Rev.4';"; + sql += "COMMIT;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + + + /// + /// update transactions table from Rev.4 to Rev.5 + /// + private void UpdateTransactionsTable4() + { + string sql = string.Empty; + + sql = "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_Transactions + "` "; + sql += "ADD(`regionHandle` varchar(36) NOT NULL AFTER `objectUUID`),"; + sql += "COMMENT = 'Rev.5';"; + sql += "COMMIT;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + + + /// + /// update transactions table from Rev.5 to Rev.6 + /// + private void UpdateTransactionsTable5() + { + string sql = string.Empty; + + sql = "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_Transactions + "` "; + sql += "ADD(`commonName` varchar(128) NOT NULL AFTER `status`),"; + sql += "COMMENT = 'Rev.6';"; + sql += "COMMIT;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + + + /// + /// update transactions table from Rev.6 to Rev.7 + /// + private void UpdateTransactionsTable6() + { + string sql = string.Empty; + + sql = "SELECT COUNT(*) FROM " + Table_of_Transactions; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + int resultCount = int.Parse(cmd.ExecuteScalar().ToString()); + cmd.Dispose(); + + sql = "SELECT UUID,sender,receiver FROM " + Table_of_Transactions; + cmd = new MySqlCommand(sql, dbcon); + MySqlDataReader dbReader = cmd.ExecuteReader(); + + int l = 0; + string[,] row = new string[resultCount, dbReader.FieldCount]; + while (dbReader.Read()) + { + for (int i = 0; i < dbReader.FieldCount; i++) + { + row[l, i] = dbReader.GetString(i); + } + l++; + } + dbReader.Close(); + cmd.Dispose(); + + sql = "UPDATE " + Table_of_Transactions + " SET sender = ?sender , receiver = ?receiver WHERE UUID = ?uuid;"; + for (int i = 0; i < resultCount; i++) + { + string sender = Regex.Replace(row[i, 1], @"@.+$", ""); + string receiver = Regex.Replace(row[i, 2], @"@.+$", ""); + cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?uuid", row[i, 0]); + cmd.Parameters.AddWithValue("?sender", sender); + cmd.Parameters.AddWithValue("?receiver", receiver); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + + sql = "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_Transactions + "` "; + sql += "COMMENT = 'Rev.7';"; + sql += "COMMIT;"; + cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + + + /// + /// update transactions table from Rev.7 to Rev.8 + /// + private void UpdateTransactionsTable7() + { + string sql = string.Empty; + + sql = "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_Transactions + "` "; + sql += "ADD `objectName` varchar(255) DEFAULT NULL AFTER `objectUUID`,"; + sql += "COMMENT = 'Rev.8';"; + sql += "COMMIT;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + + + /// + /// update transactions table from Rev.8 to Rev.9 + /// + private void UpdateTransactionsTable8() + { + string sql = string.Empty; + + sql = "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_Transactions + "` "; + sql += "ADD `senderBalance` int(10) NOT NULL DEFAULT -1 AFTER `amount`,"; + sql += "ADD `receiverBalance` int(10) NOT NULL DEFAULT -1 AFTER `senderBalance`,"; + sql += "COMMENT = 'Rev.9';"; + sql += "COMMIT;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + + + /// + /// update transactions table from Rev.9 to Rev.10 + /// + private void UpdateTransactionsTable9() + { + string sql = string.Empty; + + sql = "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_Transactions + "` "; + sql += "MODIFY COLUMN `sender` varchar(36) NOT NULL,"; + sql += "MODIFY COLUMN `receiver` varchar(36) NOT NULL,"; + sql += "COMMENT = 'Rev.10';"; + sql += "COMMIT;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + + + /// + /// update transactions table from Rev.10 to Rev.11 + /// change type of BirthGift from 1000 to 900 + /// + private void UpdateTransactionsTable10() + { + string sql = string.Empty; + + sql = "SELECT COUNT(*) FROM `" + Table_of_Transactions + "` WHERE type=1000"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + int resultCount = int.Parse(cmd.ExecuteScalar().ToString()); + cmd.Dispose(); + + if (resultCount > 0) + { + sql = "SELECT UUID FROM `" + Table_of_Transactions + "` WHERE type=1000"; + cmd = new MySqlCommand(sql, dbcon); + MySqlDataReader dbReader = cmd.ExecuteReader(); + + int l = 0; + string[] row = new string[resultCount]; + while (dbReader.Read()) + { + row[l] = dbReader.GetString(0); + l++; + } + dbReader.Close(); + cmd.Dispose(); + + sql = "UPDATE `" + Table_of_Transactions + "` SET type=900 WHERE UUID=?uuid"; + for (int i = 0; i < resultCount; i++) + { + cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?uuid", row[i]); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + } + + sql = "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_Transactions + "` "; + sql += "COMMENT = 'Rev.11';"; + sql += "COMMIT;"; + cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + + + /// + /// update transactions table from Rev.11 to Rev.12 + /// + private void UpdateTransactionsTable11() + { + string sql = string.Empty; + + sql = "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_Transactions + "` "; + sql += "ADD `regionUUID` varchar(36) NOT NULL AFTER `regionHandle`,"; + sql += "COMMENT = 'Rev.12';"; + sql += "COMMIT;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + + + // update Total Sales Table + + /// Updates the total sales table1. + private void UpdateTotalSalesTable1() + { + string sql = string.Empty; + + sql = "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_TotalSales + "` "; + sql += "ADD `time` int(11) NOT NULL AFTER `TotalAmount`,"; + sql += "COMMENT = 'Rev.2';"; + sql += "COMMIT;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + + deleteTotalSalesTable(); + initTotalSalesTable(); + } + + /// Updates the total sales table2. + private void UpdateTotalSalesTable2() + { + string sql = string.Empty; + + sql = "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_TotalSales + "` "; + sql += "MODIFY COLUMN `user` varchar(36) NOT NULL,"; + sql += "COMMENT = 'Rev.3';"; + sql += "COMMIT;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + } + + + /// Checks the tables. + /// + ///
+ ///
+ /// [MONEY MANAGER]: Error checking tables" + e.ToString() + private Dictionary CheckTables() + { + Dictionary tableDic = new Dictionary(); + + lock (dbcon) + { + string sql = string.Empty; + + sql = "SELECT TABLE_NAME,TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=?dbname"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?dbname", dbcon.Database); + + using (MySqlDataReader r = cmd.ExecuteReader()) + { + while (r.Read()) + { + try + { + string tableName = (string)r["TABLE_NAME"]; + string comment = (string)r["TABLE_COMMENT"]; + tableDic.Add(tableName, comment); + } + catch (Exception e) + { + throw new Exception("[MONEY MANAGER]: Error checking tables" + e.ToString()); + } + } + r.Close(); + } + + cmd.Dispose(); + return tableDic; + } + } + + + public void Reconnect() + { + m_log.Info("[MONEY MANAGER]: Reconnecting database"); + lock (dbcon) + { + try + { + dbcon.Close(); + dbcon = new MySqlConnection(connectString); + dbcon.Open(); + m_log.Info("[MONEY MANAGER]: Reconnected database"); + } + catch (Exception e) + { + m_log.Error("[MONEY MANAGER]: Unable to reconnect to database: " + e.ToString()); + } + } + } + + /// + /// Get balance from database. returns -1 if failed. + /// + /// + public int getBalance(string userID) + { + if (userID == UUID.Zero.ToString()) return 999999999; // System + + int retValue = -1; + string sql = string.Empty; + + sql = "SELECT balance FROM " + Table_of_Balances + " WHERE user = ?userid"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?userid", userID); + + using (MySqlDataReader dbReader = cmd.ExecuteReader(CommandBehavior.SingleRow)) + { + try + { + if (dbReader.Read()) + { + retValue = Convert.ToInt32(dbReader["balance"]); + } + } + catch (Exception e) + { + e.ToString(); + m_log.ErrorFormat("[MoneyDB]: MySql failed to fetch balance {0}.", userID); + retValue = -2; + } + + dbReader.Close(); + } + cmd.Dispose(); + + return retValue; + } + + + /// Updates the balance. + /// The user identifier. + /// The amount. + public bool updateBalance(string userID, int amount) + { + if (userID == UUID.Zero.ToString()) return true; // System + + bool bRet = false; + string sql = string.Empty; + + sql = "UPDATE " + Table_of_Balances + " SET balance = ?amount WHERE user = ?userID;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?amount", amount); + cmd.Parameters.AddWithValue("?userID", userID); + + if (cmd.ExecuteNonQuery() > 0) bRet = true; + + cmd.Dispose(); + return bRet; + } + + + /// Adds the user. + /// The user identifier. + /// The balance. + /// The status. + /// The type. + public bool addUser(string userID, int balance, int status, int type) + { + if (userID == UUID.Zero.ToString()) return true; // System + + bool bRet = false; + string sql = string.Empty; + + if (balances_rev >= 4) + { + sql = "INSERT INTO " + Table_of_Balances + " (`user`,`balance`,`status`,`type`) VALUES "; + sql += " (?userID,?balance,?status,?type);"; + } + else + { + sql = "INSERT INTO " + Table_of_Balances + " (`user`,`balance`,`status`) VALUES "; + sql += " (?userID,?balance,?status);"; + } + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + + cmd.Parameters.AddWithValue("?userID", userID); + cmd.Parameters.AddWithValue("?balance", balance); + cmd.Parameters.AddWithValue("?status", status); + if (balances_rev >= 4) + { + cmd.Parameters.AddWithValue("?type", type); + } + + if (cmd.ExecuteNonQuery() > 0) bRet = true; + cmd.Dispose(); + + return bRet; + } + + + /// + /// Here we'll make a withdraw from the sender and update transaction status + /// + /// + /// + /// + public bool withdrawMoney(UUID transactionID, string senderID, int amount) + { + bool bRet = false; + string sql = string.Empty; + MySqlCommand cmd = null; + + // System + if (senderID == UUID.Zero.ToString()) + { + sql = "BEGIN;"; + sql += "UPDATE " + Table_of_Transactions; + sql += " SET senderBalance = 0, status = ?status WHERE UUID = ?tranid;"; + sql += "COMMIT;"; + + cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?status", (int)Status.PENDING_STATUS); //pending + cmd.Parameters.AddWithValue("?tranid", transactionID.ToString()); + } + else + { + sql = "BEGIN;"; + sql += "UPDATE " + Table_of_Transactions + "," + Table_of_Balances; + sql += " SET senderBalance = balance - ?amount, " + Table_of_Transactions + ".status = ?status, balance = balance - ?amount "; + sql += " WHERE UUID = ?tranid AND user = sender AND user = ?userid;"; + sql += "COMMIT;"; + + cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?amount", amount); + cmd.Parameters.AddWithValue("?userid", senderID); + cmd.Parameters.AddWithValue("?status", (int)Status.PENDING_STATUS); //pending + cmd.Parameters.AddWithValue("?tranid", transactionID.ToString()); + } + + if (cmd.ExecuteNonQuery() > 0) bRet = true; + + cmd.Dispose(); + return bRet; + } + + + /// + /// Give money to the receiver and change the transaction status to success. + /// + /// + /// + /// + public bool giveMoney(UUID transactionID, string receiverID, int amount) + { + string sql = string.Empty; + bool bRet = false; + MySqlCommand cmd = null; + + // System + if (receiverID == UUID.Zero.ToString()) + { + sql = "BEGIN;"; + sql += "UPDATE " + Table_of_Transactions; + sql += " SET receiverBalance = 0, status = ?status WHERE UUID = ?tranid;"; + sql += "COMMIT;"; + + cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?status", (int)Status.SUCCESS_STATUS); //Success + cmd.Parameters.AddWithValue("?tranid", transactionID.ToString()); + } + else + { + sql = "BEGIN;"; + sql += "UPDATE " + Table_of_Transactions + "," + Table_of_Balances; + sql += " SET receiverBalance = balance + ?amount, " + Table_of_Transactions + ".status = ?status, balance = balance + ?amount "; + sql += " WHERE UUID = ?tranid AND user = receiver AND user = ?userid;"; + sql += "COMMIT;"; + + cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?amount", amount); + cmd.Parameters.AddWithValue("?userid", receiverID); + cmd.Parameters.AddWithValue("?status", (int)Status.SUCCESS_STATUS); //Success + cmd.Parameters.AddWithValue("?tranid", transactionID.ToString()); + } + + if (cmd.ExecuteNonQuery() > 0) bRet = true; + + cmd.Dispose(); + return bRet; + } + + public bool BuyMoney(UUID transactionID, string userID, int amount) + { + string sql = string.Empty; + bool bRet = false; + MySqlCommand cmd = null; + + try + { + sql = "BEGIN;"; + sql += "UPDATE Transactions, Balances"; + sql += " SET balance = balance + ?amount, Transactions.status = ?status "; + sql += " WHERE Transactions.UUID = ?tranid AND Balances.user = ?userid;"; + sql += "COMMIT;"; + + cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?amount", amount); + cmd.Parameters.AddWithValue("?userid", userID); + cmd.Parameters.AddWithValue("?status", (int)Status.SUCCESS_STATUS); + cmd.Parameters.AddWithValue("?tranid", transactionID.ToString()); + + if (cmd.ExecuteNonQuery() > 0) + { + bRet = true; + } + } + catch (Exception ex) + { + m_log.ErrorFormat("[BuyMoney]: Exception occurred during SQL execution: {0}", ex.Message); + } + finally + { + if (cmd != null) + { + cmd.Dispose(); + } + } + + m_log.InfoFormat("[BuyMoney]: SQL command executed successfully: {0}", bRet); + + return bRet; + } + + + /// Initializes the total sales table. + private void initTotalSalesTable() + { + m_log.Info("[MONEY MANAGER]: Initailising TotalSales Table..."); + string sql = string.Empty; + + sql = "SELECT SQL_CALC_FOUND_ROWS receiver,objectUUID,type,COUNT(*),SUM(amount),MIN(time) FROM " + Table_of_Transactions; + sql += " WHERE sender != receiver AND status = ?status AND sender != ?system"; + sql += " GROUP BY receiver,objectUUID,type;"; + + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?status", (int)Status.SUCCESS_STATUS); + cmd.Parameters.AddWithValue("?system", UUID.Zero.ToString()); + cmd.ExecuteNonQuery(); + + MySqlCommand cmd2 = new MySqlCommand("SELECT FOUND_ROWS();", dbcon); + int lineCount = int.Parse(cmd2.ExecuteScalar().ToString()); + cmd2.Dispose(); + + if (lineCount <= 0) + { + cmd.Dispose(); + return; + } + + MySqlDataReader r = cmd.ExecuteReader(); + int l = 0; + string[,] row = new string[lineCount, r.FieldCount]; + while (r.Read()) + { + for (int i = 0; i < r.FieldCount; i++) + { + row[l, i] = r.GetString(i); + } + l++; + } + r.Close(); + cmd.Dispose(); + + for (int i = 0; i < lineCount; i++) + { + string receiver = (string)row[i, 0]; // receiver + string objUUID = (string)row[i, 1]; // objectUUID + int type = Convert.ToInt32(row[i, 2]); // type + int count = Convert.ToInt32(row[i, 3]); // COUNT(*) + int amount = Convert.ToInt32(row[i, 4]); // SUM(amount) + int tmstamp = Convert.ToInt32(row[i, 5]); // MIN(time) + // + setTotalSale(receiver, objUUID, type, count, amount, tmstamp); + } + } + + + /// Deletes the total sales table. + private void deleteTotalSalesTable() + { + string sql = string.Empty; + + sql = "DELETE FROM " + Table_of_TotalSales; + + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?status", (int)Status.SUCCESS_STATUS); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + + return; + } + + + /// Adds the total sale. + /// The user UUID. + /// The object UUID. + /// The type. + /// The count. + /// The amount. + /// The tmstamp. + public bool addTotalSale(string userUUID, string objectUUID, int type, int count, int amount, int tmstamp) + { + bool bRet = false; + string sql = string.Empty; + UUID uuid = UUID.Random(); + + // Record only transactions involving objects + // if (objectUUID==UUID.Zero.ToString()) return bRet; + + sql = "INSERT INTO " + Table_of_TotalSales; + sql += " (`UUID`,`user`,`objectUUID`,`type`,`TotalCount`,`TotalAmount`,`time`) VALUES"; + sql += " (?ID,?userID,?objID,?type,?count,?amount,?time)"; + + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?ID", uuid.ToString()); + cmd.Parameters.AddWithValue("?userID", userUUID); + cmd.Parameters.AddWithValue("?objID", objectUUID); + cmd.Parameters.AddWithValue("?type", type); + cmd.Parameters.AddWithValue("?count", count); + cmd.Parameters.AddWithValue("?amount", amount); + cmd.Parameters.AddWithValue("?time", tmstamp); + + if (cmd.ExecuteNonQuery() > 0) bRet = true; + + cmd.Dispose(); + return bRet; + } + + + /// Updates the total sale. + /// The sale UUID. + /// The count. + /// The amount. + /// The tmstamp. + public bool updateTotalSale(UUID saleUUID, int count, int amount, int tmstamp) + { + bool bRet = false; + string sql = string.Empty; + + sql = "UPDATE " + Table_of_TotalSales; + sql += " SET TotalCount = TotalCount + ?count, TotalAmount = TotalAmount + ?amount, time = ?time "; + sql += " WHERE UUID = ?uuid;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + + cmd.Parameters.AddWithValue("?uuid", saleUUID.ToString()); + cmd.Parameters.AddWithValue("?count", count); + cmd.Parameters.AddWithValue("?amount", amount); + cmd.Parameters.AddWithValue("?time", tmstamp); + + if (cmd.ExecuteNonQuery() > 0) bRet = true; + + cmd.Dispose(); + return bRet; + + } + + /// Sets the total sale. + /// The user UUID. + /// The object UUID. + /// The type. + /// The count. + /// The amount. + /// The tmstamp. + public bool setTotalSale(string userUUID, string objectUUID, int type, int count, int amount, int tmstamp) + { + bool bRet = false; + string sql = string.Empty; + string uuid = string.Empty; + int dbtm = 0; + + sql = "SELECT UUID,time FROM " + Table_of_TotalSales; + sql += " WHERE user = ?userid AND objectUUID = ?objID AND type = ?type;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + + cmd.Parameters.AddWithValue("?userid", userUUID); + cmd.Parameters.AddWithValue("?objID", objectUUID); + cmd.Parameters.AddWithValue("?type", type); + + using (MySqlDataReader r = cmd.ExecuteReader()) + { + if (r.Read()) + { + try + { + uuid = (string)r["UUID"]; + dbtm = Convert.ToInt32(r["time"]); + } + catch (Exception e) + { + m_log.Error("[MONEY MANAGER]: Get sale data from DB failed: " + e.ToString()); + r.Close(); + cmd.Dispose(); + return false; + } + } + r.Close(); + } + + if (uuid != string.Empty) + { + UUID saleUUID = UUID.Zero; + UUID.TryParse(uuid, out saleUUID); + if (dbtm < tmstamp) tmstamp = dbtm; + bRet = updateTotalSale(saleUUID, count, amount, tmstamp); + } + else + { + bRet = addTotalSale(userUUID, objectUUID, type, count, amount, tmstamp); + } + + cmd.Dispose(); + return bRet; + } + + + // + // transactions + // + /// Adds the transaction. + /// The transaction. + public bool addTransaction(TransactionData transaction) + { + bool bRet = false; + string sql = string.Empty; + + if (transaction.ObjectUUID == null) transaction.ObjectUUID = UUID.Zero.ToString(); + if (transaction.ObjectName == null) transaction.ObjectName = string.Empty; + if (transaction.Description == null) transaction.Description = string.Empty; + + sql = "INSERT INTO " + Table_of_Transactions; + sql += " (`UUID`,`sender`,`receiver`,`amount`,`senderBalance`,`receiverBalance`,`objectUUID`,`objectName`,"; + sql += " `regionHandle`,`regionUUID`,`type`,`time`,`secure`,`status`,`commonName`,`description`) VALUES"; + sql += " (?transID,?sender,?receiver,?amount,?senderBalance,?receiverBalance,?objID,?objName,?regionHandle,?regionUUID,?type,?time,?secure,?status,?cname,?desc)"; + + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?transID", transaction.TransUUID.ToString()); + cmd.Parameters.AddWithValue("?sender", transaction.Sender); + cmd.Parameters.AddWithValue("?receiver", transaction.Receiver); + cmd.Parameters.AddWithValue("?amount", transaction.Amount); + cmd.Parameters.AddWithValue("?senderBalance", -1); + cmd.Parameters.AddWithValue("?receiverBalance", -1); + cmd.Parameters.AddWithValue("?objID", transaction.ObjectUUID); + cmd.Parameters.AddWithValue("?objName", transaction.ObjectName); + cmd.Parameters.AddWithValue("?regionHandle", transaction.RegionHandle); + cmd.Parameters.AddWithValue("?regionUUID", transaction.RegionUUID); + cmd.Parameters.AddWithValue("?type", transaction.Type); + cmd.Parameters.AddWithValue("?time", transaction.Time); + cmd.Parameters.AddWithValue("?secure", transaction.SecureCode); + cmd.Parameters.AddWithValue("?status", transaction.Status); + cmd.Parameters.AddWithValue("?cname", transaction.CommonName); + cmd.Parameters.AddWithValue("?desc", transaction.Description); + + if (cmd.ExecuteNonQuery() > 0) bRet = true; + + cmd.Dispose(); + return bRet; + } + + + /// Updates the transaction status. + /// The transaction identifier. + /// The status. + /// The description. + public bool updateTransactionStatus(UUID transactionID, int status, string description) + { + bool bRet = false; + string sql = string.Empty; + + sql = "UPDATE " + Table_of_Transactions + " SET status = ?status,description = ?desc WHERE UUID = ?tranid;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?status", status); + cmd.Parameters.AddWithValue("?desc", description); + cmd.Parameters.AddWithValue("?tranid", transactionID); + + if (cmd.ExecuteNonQuery() > 0) bRet = true; + + cmd.Dispose(); + return bRet; + } + + + /// Sets the trans expired. + /// The dead time. + public bool SetTransExpired(int deadTime) + { + bool bRet = false; + string sql = string.Empty; + + sql = "UPDATE " + Table_of_Transactions; + sql += " SET status = ?failedstatus,description = ?desc WHERE time <= ?deadTime AND status = ?pendingstatus;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?failedstatus", (int)Status.FAILED_STATUS); + cmd.Parameters.AddWithValue("?desc", "expired"); + cmd.Parameters.AddWithValue("?deadTime", deadTime); + cmd.Parameters.AddWithValue("?pendingstatus", (int)Status.PENDING_STATUS); + + if (cmd.ExecuteNonQuery() > 0) bRet = true; + + cmd.Dispose(); + return bRet; + } + + + /// + /// Validate if the transacion is legal + /// + /// + /// + public bool ValidateTransfer(string secureCode, UUID transactionID) + { + bool bRet = false; + string secure = string.Empty; + string sql = string.Empty; + + sql = "SELECT secure FROM " + Table_of_Transactions + " WHERE UUID = ?transID;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?transID", transactionID.ToString()); + + using (MySqlDataReader r = cmd.ExecuteReader()) + { + if (r.Read()) + { + try + { + secure = (string)r["secure"]; + } + catch (Exception e) + { + m_log.Error("[MONEY MANAGER]: Get transaction from DB failed: " + e.ToString()); + } + if (secureCode == secure) bRet = true; + else bRet = false; + } + r.Close(); + } + + cmd.Dispose(); + return bRet; + } + + + /// Fetches the transaction. + /// The transaction identifier. + public TransactionData FetchTransaction(UUID transactionID) + { + TransactionData transactionData = new TransactionData(); + transactionData.TransUUID = transactionID; + string sql = string.Empty; + + sql = "SELECT * FROM " + Table_of_Transactions + " WHERE UUID = ?transID;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?transID", transactionID.ToString()); + + using (MySqlDataReader r = cmd.ExecuteReader()) + { + if (r.Read()) + { + try + { + transactionData.Sender = (string)r["sender"]; + transactionData.Receiver = (string)r["receiver"]; + transactionData.Amount = Convert.ToInt32(r["amount"]); + transactionData.SenderBalance = Convert.ToInt32(r["senderBalance"]); + transactionData.ReceiverBalance = Convert.ToInt32(r["receiverBalance"]); + transactionData.Type = Convert.ToInt32(r["type"]); + transactionData.Time = Convert.ToInt32(r["time"]); + transactionData.Status = Convert.ToInt32(r["status"]); + transactionData.CommonName = (string)r["commonName"]; + transactionData.RegionHandle = (string)r["regionHandle"]; + transactionData.RegionUUID = (string)r["regionUUID"]; + // + if (r["objectUUID"] is System.DBNull) transactionData.ObjectUUID = UUID.Zero.ToString(); + else transactionData.ObjectUUID = (string)r["objectUUID"]; + if (r["objectName"] is System.DBNull) transactionData.ObjectName = string.Empty; + else transactionData.ObjectName = (string)r["objectName"]; + if (r["description"] is System.DBNull) transactionData.Description = string.Empty; + else transactionData.Description = (string)r["description"]; + } + catch (Exception e) + { + m_log.Error("[MONEY MANAGER]: Fetching transaction failed 1: " + e.ToString()); + r.Close(); + cmd.Dispose(); + return null; + } + + } + r.Close(); + } + + cmd.Dispose(); + return transactionData; + } + + + /// Fetches the transaction. + /// The user identifier. + /// The start time. + /// The end time. + /// The index. + /// The ret number. + public TransactionData[] FetchTransaction(string userID, int startTime, int endTime, uint index, uint retNum) + { + List rows = new List(); + string sql = string.Empty; + + sql = "SELECT * FROM " + Table_of_Transactions + " WHERE time>=?start AND time<=?end "; + sql += "AND (sender=?user OR receiver=?user) ORDER BY time ASC LIMIT ?index,?num;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + + cmd.Parameters.AddWithValue("?start", startTime); + cmd.Parameters.AddWithValue("?end", endTime); + cmd.Parameters.AddWithValue("?user", userID); + cmd.Parameters.AddWithValue("?index", index); + cmd.Parameters.AddWithValue("?num", retNum); + + using (MySqlDataReader r = cmd.ExecuteReader()) + { + for (int i = 0; i < retNum; i++) + { + if (r.Read()) + { + try + { + TransactionData transactionData = new TransactionData(); + string uuid = (string)r["UUID"]; + UUID transUUID; + UUID.TryParse(uuid, out transUUID); + + transactionData.TransUUID = transUUID; + transactionData.Sender = (string)r["sender"]; + transactionData.Receiver = (string)r["receiver"]; + transactionData.Amount = Convert.ToInt32(r["amount"]); + transactionData.SenderBalance = Convert.ToInt32(r["senderBalance"]); + transactionData.ReceiverBalance = Convert.ToInt32(r["receiverBalance"]); + transactionData.Type = Convert.ToInt32(r["type"]); + transactionData.Time = Convert.ToInt32(r["time"]); + transactionData.Status = Convert.ToInt32(r["status"]); + transactionData.CommonName = (string)r["commonName"]; + transactionData.RegionHandle = (string)r["regionHandle"]; + transactionData.RegionUUID = (string)r["regionUUID"]; + // + if (r["objectUUID"] is System.DBNull) transactionData.ObjectUUID = UUID.Zero.ToString(); + else transactionData.ObjectUUID = (string)r["objectUUID"]; + if (r["objectName"] is System.DBNull) transactionData.ObjectName = string.Empty; + else transactionData.ObjectName = (string)r["objectName"]; + if (r["description"] is System.DBNull) transactionData.Description = string.Empty; + else transactionData.Description = (string)r["description"]; + // + rows.Add(transactionData); + } + catch (Exception e) + { + m_log.Error("[MONEY MANAGER]: Fetching transaction failed 2: " + e.ToString()); + r.Close(); + cmd.Dispose(); + return null; + } + } + } + r.Close(); + } + + cmd.Dispose(); + return rows.ToArray(); + } + + + /// Gets the transaction number. + /// The user identifier. + /// The start time. + /// The end time. + public int getTransactionNum(string userID, int startTime, int endTime) + { + int iRet = -1; + string sql = string.Empty; + + sql = "SELECT COUNT(*) AS number FROM " + Table_of_Transactions + " WHERE time>=?start AND time<=?end "; + sql += "AND (sender=?user OR receiver=?user);"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + + cmd.Parameters.AddWithValue("?start", startTime); + cmd.Parameters.AddWithValue("?end", endTime); + cmd.Parameters.AddWithValue("?user", userID); + + using (MySqlDataReader r = cmd.ExecuteReader()) + { + if (r.Read()) + { + try + { + iRet = Convert.ToInt32(r["number"]); + } + catch (Exception e) + { + m_log.Error("[MONEY MANAGER]: Unable to get transaction info: " + e.ToString()); + } + } + r.Close(); + } + + cmd.Dispose(); + return iRet; + } + + + // + // userinfo + // + /// Adds the user information. + /// + public bool addUserInfo(UserInfo userInfo) + { + bool bRet = false; + string sql = string.Empty; + + if (userInfo.Avatar == null) return false; + + if (userinfo_rev >= 3) + { + sql = "INSERT INTO " + Table_of_UserInfo + "(`user`,`simip`,`avatar`,`pass`,`type`,`class`,`serverurl`) VALUES"; + sql += "(?user,?simip,?avatar,?password,?type,?class,?serverurl);"; + } + else + { + sql = "INSERT INTO " + Table_of_UserInfo + "(`user`,`simip`,`avatar`,`pass`) VALUES"; + sql += "(?user,?simip,?avatar,?password);"; + } + + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?user", userInfo.UserID); + cmd.Parameters.AddWithValue("?simip", userInfo.SimIP); + cmd.Parameters.AddWithValue("?avatar", userInfo.Avatar); + cmd.Parameters.AddWithValue("?password", userInfo.PswHash); + if (userinfo_rev >= 3) + { + cmd.Parameters.AddWithValue("?type", userInfo.Type); + cmd.Parameters.AddWithValue("?class", userInfo.Class); + cmd.Parameters.AddWithValue("?serverurl", userInfo.ServerURL); + } + + if (cmd.ExecuteNonQuery() > 0) bRet = true; + + cmd.Dispose(); + return bRet; + } + + + /// Fetches the user information. + /// The user identifier. + public UserInfo fetchUserInfo(string userID) + { + UserInfo userInfo = new UserInfo(); + userInfo.UserID = null; + string sql = string.Empty; + + sql = "SELECT * FROM " + Table_of_UserInfo + " WHERE user = ?userID;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?userID", userID); + + using (MySqlDataReader r = cmd.ExecuteReader()) + { + if (r.Read()) + { + try + { + userInfo.UserID = (string)r["user"]; + userInfo.SimIP = (string)r["simip"]; + userInfo.Avatar = (string)r["avatar"]; + userInfo.PswHash = (string)r["pass"]; + userInfo.Type = Convert.ToInt32(r["type"]); + userInfo.Class = Convert.ToInt32(r["class"]); + userInfo.ServerURL = (string)r["serverurl"]; + } + catch (Exception e) + { + m_log.Error("[MONEY MANAGER]: Fetching UserInfo failed: " + e.ToString()); + r.Close(); + cmd.Dispose(); + return null; + } + } + r.Close(); + } + cmd.Dispose(); + + if (userInfo.UserID != userID) return null; + return userInfo; + } + + + /// Updates the user information. + /// + public bool updateUserInfo(UserInfo userInfo) + { + bool bRet = false; + string sql = string.Empty; + + sql = "UPDATE " + Table_of_UserInfo + " SET simip=?simip,pass=?pass,class=?class,serverurl=?serverurl WHERE user=?user;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?simip", userInfo.SimIP); + cmd.Parameters.AddWithValue("?pass", userInfo.PswHash); + cmd.Parameters.AddWithValue("?class", userInfo.Class); + cmd.Parameters.AddWithValue("?serverurl", userInfo.ServerURL); + cmd.Parameters.AddWithValue("?user", userInfo.UserID); + + if (cmd.ExecuteNonQuery() > 0) bRet = true; + + cmd.Dispose(); + return bRet; + } + + public bool UserExists(string userID) + { + try + { + UserInfo userInfo = fetchUserInfo(userID); + return userInfo != null; + } + catch (Exception ex) + { + m_log.Error(ex); + return false; + } + } + + public bool UpdateUserInfo(string userID, UserInfo updatedInfo) + { + UserInfo existingInfo = fetchUserInfo(userID); + if (existingInfo != null) + { + updatedInfo.UserID = userID; // Ensure the userID is updated correctly + return updateUserInfo(updatedInfo); + } + else + { + return false; // User not found + } + } + + public bool DeleteUser(string userID) + { + string sql = "DELETE FROM " + Table_of_UserInfo + " WHERE user = ?userID;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?userID", userID); + + return cmd.ExecuteNonQuery() > 0; + } + + public void LogTransactionError(UUID transactionID, string errorMessage) + { + m_log.ErrorFormat("[MONEY MODULE]: Transaction {0} failed with error: {1}", transactionID, errorMessage); + } + + public IEnumerable GetTransactionHistory(string userID, int startTime, int endTime) + { + List transactionHistory = new List(); + + // Assuming you have a database connection and a query to fetch transactions + string sql = "SELECT * FROM " + Table_of_Transactions + " WHERE user = ?userID AND time >= ?startTime AND time <= ?endTime;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?userID", userID); + cmd.Parameters.AddWithValue("?startTime", startTime); + cmd.Parameters.AddWithValue("?endTime", endTime); + + using (MySqlDataReader r = cmd.ExecuteReader()) + { + while (r.Read()) + { + TransactionData transaction = new TransactionData(); + // Assuming you have properties in TransactionData class to match the columns in the database table + transaction.TransUUID = UUID.Parse((string)r["transuuid"]); + transaction.Sender = (string)r["sender"]; + transaction.Receiver = (string)r["receiver"]; + transaction.Amount = Convert.ToInt32(r["amount"]); + transaction.ObjectUUID = (string)r["objectuuid"]; + transaction.ObjectName = (string)r["objectname"]; + transaction.RegionHandle = (string)r["regionhandle"]; + transaction.Type = Convert.ToInt32(r["type"]); + transaction.Time = Convert.ToInt32(r["time"]); + transaction.Status = Convert.ToInt32(r["status"]); + transaction.SecureCode = (string)r["securecode"]; + transaction.CommonName = (string)r["commonname"]; + transaction.Description = (string)r["description"]; + + transactionHistory.Add(transaction); + } + r.Close(); + } + cmd.Dispose(); + + return transactionHistory; + } + + + } +} diff --git a/addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs b/addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs new file mode 100644 index 0000000..8491950 --- /dev/null +++ b/addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs @@ -0,0 +1,100 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Funktion + Die Klasse MySQLSuperManager dient als Wrapper/Manager für einen Thread-Sicherheitsmechanismus (Mutex) und kapselt eine Instanz von MySQLMoneyManager. + Sie enthält: + Ein Mutex-Objekt für Thread-Synchronisation. + Ein Flag Locked zur Statusanzeige. + Eine öffentliche Instanz von MySQLMoneyManager namens Manager. + Die Methode GetLock() blockiert, bis der Mutex verfügbar ist. + Die Methode Release() gibt den Mutex frei und behandelt freigabe-bezogene Fehler. + Ein String Running (wird hier aber nicht verwendet). + +Null Pointer Checks + Manager: Im Konstruktor wird garantiert, dass Manager immer initialisiert ist (niemals null nach Konstruktion). + m_lock: Wird direkt beim Feld-Deklaration erstellt, kann also nie null sein. + Locked: Ist ein bool, daher nie null. + Running: Ist public, aber im Code nicht direkt verwendet. Wenn er von außen auf null gesetzt wird, kann das keinen Fehler verursachen, da er nicht verwendet wird. + +Fehlerquellen + Mutex-Handling: + In Release() wird das Freigeben des Mutex von einem Try-Catch-Block umschlossen. Sollte ein Fehler beim Freigeben auftreten (z.B. Freigabe von einem Thread, der den Mutex nicht besitzt), wird die Exception geloggt und erneut geworfen. + In GetLock() wird WaitOne() direkt aufgerufen, was blockiert, bis der Lock verfügbar ist. Es gibt hier keine Exception-Absicherung, aber üblicherweise ist dies sicher, solange das Objekt korrekt verwendet wird. + + Thread-Safety: + Die Klasse ist thread-safe bezüglich des Mutex, nicht aber bezüglich der Instanzvariablen (Running ist public ohne Schutz, aber nicht verwendet). + Es wird kein expliziter Null-Check auf Manager benötigt, da sie im Konstruktor immer initialisiert wird. + + Unbenutzte Variable: + Das Feld Running ist nicht implementiert/genutzt. Das ist kein Fehler, aber ein Hinweis auf evtl. toten Code. + +Zusammenfassung + Null Pointer: Keine Gefahr für NullPointer-Exceptions im aktuellen Code. + Fehlerquellen: Einzige potenzielle Fehlerquelle ist das Freigeben des Mutex in Release(), aber dies wird per Exception abgefangen und geloggt. + Funktion: Thread-Safe-Manager/Wrapper für den Zugriff auf einen MySQLMoneyManager. + +Fazit: +Die Klasse ist solide und sicher gegen NullPointer-Fehler. Fehler beim Mutex-Handling werden korrekt behandelt. Es gibt keine offensichtlichen Schwachstellen. +Lediglich das Feld Running ist ungenutzt und könnte entfernt oder implementiert werden. + */ + +using MySqlX.XDevAPI; + +using OpenMetaverse.ImportExport.Collada14; + +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; + +using System; +using System.Diagnostics; +using System.Runtime.ConstrainedExecution; +using System.Threading; + +namespace OpenSim.Data.MySQL.MySQLMoneyDataWrapper +{ + public class MySQLSuperManager(string connectionString) + { + public bool Locked { get; private set; } + private readonly Mutex m_lock = new(false); + public MySQLMoneyManager Manager { get; } = new MySQLMoneyManager(connectionString); + public string Running; + + /// Erwirbt die exklusive Sperre für kritische Operationen. + public void GetLock() + { + m_lock.WaitOne(); + Locked = true; + } + + /// Gibt die Sperre wieder frei. + public void Release() + { + try + { + m_lock.ReleaseMutex(); + } + catch (Exception ex) + { + Console.WriteLine($"Ein Fehler beim Freigeben des Mutex ist aufgetreten: {ex.Message}"); + throw; + } + finally + { + Locked = false; + } + } + } +} diff --git a/addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/Properties/AssemblyInfo.cs b/addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..7ae9f91 --- /dev/null +++ b/addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("OpenSim.Data.MySQL.MySQLMoneyDataWrapper")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("OpenSim.Data.MySQL.MySQLMoneyDataWrapper")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2009")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("03f4a266-2e30-47e4-b785-ff02cdb4bbb9")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs b/addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs new file mode 100644 index 0000000..bb9a313 --- /dev/null +++ b/addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs @@ -0,0 +1,291 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Funktion + TransactionData ist eine reine Datenklasse (POCO/DTO), die alle relevanten Felder für eine Finanztransaktion im OpenSim-Money-Modul kapselt. + Felder: UUIDs, Beträge, Balances, Typen, Zeiten, Status, Objekt- und Regionsdaten, Sicherheitscode, Beschreibung usw. + Für alle Felder gibt es Properties mit Getter/Setter. + Zwei Enums: + Status (SUCCESS, PENDING, FAILED, ERROR) + AvatarType (diverse Avatar-Typen, z.B. LOCAL, HG, GUEST, NPC) + UserInfo ist ebenfalls eine reine Datenklasse für Benutzerinformationen, mit Properties für UserID, SimIP, Name, Passwort-Hash, Typ, Klasse, ServerURL. + +Null Pointer & Fehlerquellen +Null Pointer + Felder vom Typ string werden grundsätzlich mit string.Empty initialisiert (niemals null im Standardzustand). + UUIDs werden (soweit gesetzt) aus dem Typ OpenMetaverse.UUID initialisiert – dieser Typ ist ein struct und kann daher nie null sein. + Es gibt keine Methoden mit Logik, daher auch keine Parameterprüfungen oder komplexe Objekt-Manipulationen, bei denen NullPointer auftreten könnten. + Setter/Getter der Properties geben direkt das Feld zurück oder setzen es, ohne Logik. + Die Enums können nicht null sein. + +Fehlerquellen + Keine Methoden mit Logik, also keine klassische Fehlerquelle wie Division durch 0, Indexfehler o.ä. + Setter führen keine Validierung durch (z.B. auf gültige UUIDs, Wertebereiche), + was bei fehlerhafter Nutzung zu inkonsistenten Daten führen könnte – das ist aber kein NullPointer und im Kontext von Datenklassen üblich. + UserInfo: Alle Felder sind mit sinnvollen Defaultwerten versehen (string.Empty oder ein Enum-Wert). + +Zusammenfassung + Null Pointer: Es gibt im aktuellen Code keine Gefahr für NullPointer-Exceptions, da alle Felder mit Defaultwerten belegt sind und keine Logik existiert. + Fehlerquellen: Keine Methoden, keine Validierung – klassisch für Datenklassen. + Fehler können nur entstehen, wenn von außen ungültige Werte gesetzt werden, aber das verursacht keine NullPointer-Fehler. + Funktion: Kapselt Daten für Transaktionen und Benutzer im OpenSim-Money-System. + +Fazit: +Die Datei ist eine reine Datenstruktur und sicher gegenüber NullPointer-Fehlern. Es gibt keine gefährlichen Stellen im Code. +Die Nutzung ist robust, solange von außen sinnvolle Werte gesetzt werden. + */ + +using OpenMetaverse; + + +namespace OpenSim.Data.MySQL.MySQLMoneyDataWrapper +{ + public class TransactionData + { + UUID m_uuid; + string m_sender = string.Empty; + string m_receiver = string.Empty; + int m_amount; + int m_senderBalance; + int m_receiverBalance; + int m_type; + int m_time; + int m_status; + string m_objectID = UUID.Zero.ToString(); + string m_objectName = string.Empty; + string m_regionHandle = string.Empty; + string m_regionUUID = string.Empty; + string m_secureCode = string.Empty; + string m_commonName = string.Empty; + string m_description = string.Empty; + + /// Gets or sets the trans UUID. + /// The trans UUID. + public UUID TransUUID + { + get { return m_uuid; } + set { m_uuid = value; } + } + + /// Gets or sets the sender. + /// The sender. + public string Sender + { + get { return m_sender; } + set { m_sender = value; } + } + + /// Gets or sets the receiver. + /// The receiver. + public string Receiver + { + get { return m_receiver; } + set { m_receiver = value; } + } + + /// Gets or sets the amount. + /// The amount. + public int Amount + { + get { return m_amount; } + set { m_amount = value; } + } + + /// Gets or sets the sender balance. + /// The sender balance. + public int SenderBalance + { + get { return m_senderBalance; } + set { m_senderBalance = value; } + } + + /// Gets or sets the receiver balance. + /// The receiver balance. + public int ReceiverBalance + { + get { return m_receiverBalance; } + set { m_receiverBalance = value; } + } + + /// Gets or sets the type. + /// The type. + public int Type + { + get { return m_type; } + set { m_type = value; } + } + + /// Gets or sets the time. + /// The time. + public int Time + { + get { return m_time; } + set { m_time = value; } + } + + /// Gets or sets the status. + /// The status. + public int Status + { + get { return m_status; } + set { m_status = value; } + } + + /// Gets or sets the description. + /// The description. + public string Description + { + get { return m_description; } + set { m_description = value; } + } + + /// Gets or sets the object UUID. + /// The object UUID. + public string ObjectUUID + { + get { return m_objectID; } + set { m_objectID = value; } + } + + /// Gets or sets the name of the object. + /// The name of the object. + public string ObjectName + { + get { return m_objectName; } + set { m_objectName = value; } + } + + /// Gets or sets the region handle. + /// The region handle. + public string RegionHandle + { + get { return m_regionHandle; } + set { m_regionHandle = value; } + } + + /// Gets or sets the region UUID. + /// The region UUID. + public string RegionUUID + { + get { return m_regionUUID; } + set { m_regionUUID = value; } + } + + /// Gets or sets the secure code. + /// The secure code. + public string SecureCode + { + get { return m_secureCode; } + set { m_secureCode = value; } + } + + /// Gets or sets the name of the common. + /// The name of the common. + public string CommonName + { + get { return m_commonName; } + set { m_commonName = value; } + } + } + + + public enum Status + { + SUCCESS_STATUS = 0, + PENDING_STATUS = 1, + FAILED_STATUS = 2, + ERROR_STATUS = 9 + } + + + public enum AvatarType + { + LOCAL_AVATAR = 0, + HG_AVATAR = 1, + NPC_AVATAR = 2, + GUEST_AVATAR = 3, + FOREIGN_AVATAR = 8, + UNKNOWN_AVATAR = 9 + } + + + public class UserInfo + { + string m_userID = string.Empty; + string m_simIP = string.Empty; + string m_avatarName = string.Empty; + string m_passwordHash = string.Empty; + int m_avatarType = (int)AvatarType.LOCAL_AVATAR; + int m_avatarClass = (int)AvatarType.LOCAL_AVATAR; + string m_serverURL = string.Empty; + + /// Gets or sets the user identifier. + /// The user identifier. + public string UserID + { + get { return m_userID; } + set { m_userID = value; } + } + + /// Gets or sets the sim ip. + /// The sim ip. + public string SimIP + { + get { return m_simIP; } + set { m_simIP = value; } + } + + /// Gets or sets the avatar. + /// The avatar. + public string Avatar + { + get { return m_avatarName; } + set { m_avatarName = value; } + } + + /// Gets or sets the PSW hash. + /// The PSW hash. + public string PswHash + { + get { return m_passwordHash; } + set { m_passwordHash = value; } + } + + /// Gets or sets the type. + /// The type. + public int Type + { + get { return m_avatarType; } + set { m_avatarType = value; } + } + + /// Gets or sets the class. + /// The class. + public int Class + { + get { return m_avatarClass; } + set { m_avatarClass = value; } + } + + /// Gets or sets the server URL. + /// The server URL. + public string ServerURL + { + get { return m_serverURL; } + set { m_serverURL = value; } + } + } +} diff --git a/addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/prebuild-MySQLMoneyDataWrapper.xml b/addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/prebuild-MySQLMoneyDataWrapper.xml new file mode 100644 index 0000000..af0a57f --- /dev/null +++ b/addon-modules/OpenSim-Data-MySQL-MySQLMoneyDataWrapper/prebuild-MySQLMoneyDataWrapper.xml @@ -0,0 +1,39 @@ + + + + + ../../bin/ + true + + + + + ../../bin/ + true + + + + ../../bin/ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/CurrencyStreamHandler.cs b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/CurrencyStreamHandler.cs new file mode 100644 index 0000000..3e4b688 --- /dev/null +++ b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/CurrencyStreamHandler.cs @@ -0,0 +1,61 @@ +/* +Hier ist eine Analyse der Datei CurrencyStreamHandler.cs hinsichtlich möglicher Null-Pointer, Fehlerquellen und Funktion: + +### Funktion des Codes + +Die Datei definiert die Klasse CurrencyStreamHandler im Namespace OpenSim.Grid.MoneyServer. +Diese Klasse erbt von CustomSimpleStreamHandler und dient dazu, HTTP-Requests unter einer bestimmten Pfadangabe zu behandeln. +Im Konstruktor werden ein Pfad (string path) und eine Callback-Action (Action processAction) übergeben und direkt an die Basisklasse weitergereicht. + +### Code-Check: Null-Pointer & Fehlerquellen + +#### 1. Konstruktor + +```csharp +public CurrencyStreamHandler(string path, Action processAction) + : base(path, processAction) +{ +} +``` + +-**Fehleranfällig für Null-Pointer:** + -Wenn `path` oder `processAction` null ist und die Basisklasse (CustomSimpleStreamHandler) keine Null-Prüfung durchführt, könnte es zu NullReferenceExceptions kommen. +- **Empfehlung:** + -Füge Null - Checks hinzu: + ```csharp + if (path == null) throw new ArgumentNullException(nameof(path)); +if (processAction == null) throw new ArgumentNullException(nameof(processAction)); + ``` +-**Funktion:** + -Die Klasse dient als spezialisierter HTTP-Handler, der Requests auf einen bestimmten Pfad verarbeitet, wobei das eigentliche Verhalten durch die übergebene Action bestimmt wird. + +#### 2. Keine weitere Logik + +- Die Klasse enthält keine eigene Logik, sondern reicht alles an die Basisklasse weiter. +- Es gibt keine Felder oder Methoden, die potenziell NullPointer verursachen könnten. + +### Zusammenfassung + +- **Funktion:**Registrierung eines HTTP-Stream-Handlers für einen bestimmten Pfad mit benutzerdefiniertem Callback. +- **Mögliche Null-Pointer:**Konstruktorparameter könnten null sein. +- **Empfehlung:**Null - Checks für Konstruktorparameter hinzufügen, falls die Basisklasse dies nicht übernimmt. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using OpenSim.Framework.Servers.HttpServer; + +namespace OpenSim.Grid.MoneyServer +{ + public class CurrencyStreamHandler : CustomSimpleStreamHandler + { + public CurrencyStreamHandler(string path, Action processAction) + : base(path, processAction) + { + } + } +} diff --git a/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/CustomSimpleStreamHandler.cs b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/CustomSimpleStreamHandler.cs new file mode 100644 index 0000000..264bc89 --- /dev/null +++ b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/CustomSimpleStreamHandler.cs @@ -0,0 +1,110 @@ +/* +Funktion des Codes + + Die Klasse CustomSimpleStreamHandler erweitert SimpleBaseRequestHandler und implementiert ISimpleStreamHandler. + Sie dient als generischer HTTP-Handler, der verschiedene Arten von Authentifizierung und Verarbeitungslogik unterstützt. + Konstruktoren erlauben flexible Kombinationen aus Pfad, Name, Auth-Objekt, Prozess-Methoden und Callbacks. + Die zentrale Methode ist Handle, die einen HTTP-Request verarbeitet: Authentifizierung, dann die eigentliche Verarbeitung durch Callback, Delegate oder Fallback-Logik. + +Verbesserungsvorschläge + + Logging im Catch-Block für bessere Fehlerdiagnose. + Optional: Warnung oder Exception, wenn weder m_processAction, m_processRequest gesetzt ist noch ProcessRequest überschrieben wurde. + Optional: Dokumentation, dass für sinnvolle Funktionalität eine dieser Methoden/Callbacks gesetzt sein muss. + +Fazit: +Die Klasse ist robust gegen NullPointer-Exceptions, weil überall sauber geprüft wird. Die Fehlerbehandlung ist sehr generisch – für Produktionseinsatz wäre Logging sinnvoll. +Die eigentliche Funktion ist flexibel: Sie ermöglicht Authentifizierung und verschiedene Arten von Request-Handling in HTTP-Servern. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenSim.Framework.Servers.HttpServer; +using System.Net; +using OpenSim.Framework.ServiceAuth; + +namespace OpenSim.Grid.MoneyServer +{ + public class CustomSimpleStreamHandler : SimpleBaseRequestHandler, ISimpleStreamHandler + { + protected IServiceAuth m_Auth; + protected SimpleStreamMethod m_processRequest; + private Action m_processAction; + + public CustomSimpleStreamHandler(string path) : base(path) { } + + public CustomSimpleStreamHandler(string path, string name) : base(path, name) { } + + public CustomSimpleStreamHandler(string path, SimpleStreamMethod processRequest) : base(path) + { + m_processRequest = processRequest; + } + + public CustomSimpleStreamHandler(string path, SimpleStreamMethod processRequest, string name) : base(path, name) + { + m_processRequest = processRequest; + } + + public CustomSimpleStreamHandler(string path, IServiceAuth auth) : base(path) + { + m_Auth = auth; + } + + public CustomSimpleStreamHandler(string path, IServiceAuth auth, SimpleStreamMethod processRequest) : base(path) + { + m_Auth = auth; + m_processRequest = processRequest; + } + + public CustomSimpleStreamHandler(string path, IServiceAuth auth, SimpleStreamMethod processRequest, string name) : base(path, name) + { + m_Auth = auth; + m_processRequest = processRequest; + } + + public CustomSimpleStreamHandler(string path, Action processAction) : base(path) + { + m_processAction = processAction; + } + + public virtual void Handle(IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) + { + RequestsReceived++; + + if (m_Auth != null) + { + HttpStatusCode statusCode; + + if (!m_Auth.Authenticate(httpRequest.Headers, httpResponse.AddHeader, out statusCode)) + { + httpResponse.StatusCode = (int)statusCode; + return; + } + } + + try + { + if (m_processAction != null) + m_processAction(httpRequest, httpResponse); + else if (m_processRequest != null) + m_processRequest(httpRequest, httpResponse); + else + ProcessRequest(httpRequest, httpResponse); + } + catch + { + httpResponse.StatusCode = (int)HttpStatusCode.InternalServerError; + } + + RequestsHandled++; + } + + protected virtual void ProcessRequest(IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) + { + // Override in derived classes to implement the request handling logic. + } + } +} diff --git a/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/IMoneyDBService.cs b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/IMoneyDBService.cs new file mode 100644 index 0000000..529d36a --- /dev/null +++ b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/IMoneyDBService.cs @@ -0,0 +1,163 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/, http://www.nsl.tuis.ac.jp/ See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Funktion + +IMoneyDBService ist ein Interface für Datenbankoperationen rund um ein Währungssystem, typischerweise für virtuelle Ökonomien. Es definiert Methoden für: + + Nutzerverwaltung (z.B. addUser, DeleteUser, UserExists, UpdateUserInfo) + Kontostand- und Geldtransaktionen (getBalance, withdrawMoney, giveMoney, BuyMoney, BuyCurrency, PerformMoneyTransfer) + Transaktionsmanagement (addTransaction, updateTransactionStatus, FetchTransaction, GetTransactionHistory, SetTransExpired) + Fehlerprotokollierung (LogTransactionError) + Authentifizierungen und Validierungen (ValidateTransfer) + Verbindungsmanagement (GetLockedConnection) + +Die Methoden sind so gestaltet, dass sie in einer konkreten Implementierung mit einer Datenbank (z.B. MySQL) interagieren. + */ + + +using OpenMetaverse; + +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace OpenSim.Grid.MoneyServer +{ + /// + /// IMoney DB Service + /// + public interface IMoneyDBService + { + int CheckMaximumMoney(string userID, int m_CurrencyMaximum); + Hashtable ApplyFallbackCredit(string agentId); + + void InitializeUserCurrency(string agentId); + + bool PerformMoneyTransfer(string senderID, string receiverID, int amount); + + /// Ruft den Kontostand ab. + /// Die Benutzer-ID. + int getBalance(string userID); + + /// Zieht Geld ab. + /// Die Transaktions-ID. + /// Die Absender-ID. + /// Der Betrag. + bool withdrawMoney(UUID transactionID, string senderID, int amount); + + /// Gibt Geld. + /// Die Transaktions-ID. + /// Die Empfänger-ID. + /// Der Betrag. + bool giveMoney(UUID transactionID, string receiverID, int amount); + + /// Kauft Geld für einen Benutzer. + /// Die Transaktions-ID. + /// Die Benutzer-ID. + /// Der zu kaufende Betrag. + /// True, wenn der Kauf erfolgreich war, andernfalls false. + bool BuyMoney(UUID transactionID, string userID, int amount); + + bool BuyCurrency(string userID, int amount); + + /// Fügt die Transaktion hinzu. + /// Die Transaktion. + bool addTransaction(TransactionData transaction); + + /// Fügt den Benutzer hinzu. + /// Die Benutzer-ID. + /// Der Kontostand. + /// Der Status. + /// Der Typ. + bool addUser(string userID, int balance, int status, int type); + + /// Aktualisiert den Transaktionsstatus. + /// Die Transaktions-ID. + /// Der Status. + /// Die Beschreibung. + bool updateTransactionStatus(UUID transactionID, int status, string description); + + /// Setzt die Transaktion als abgelaufen. + /// Die Ablaufzeit. + bool SetTransExpired(int deadTime); + + /// Validiert die Überweisung. + /// Der Sicherheitscode. + /// Die Transaktions-ID. + bool ValidateTransfer(string secureCode, UUID transactionID); + + /// Ruft die Transaktion ab. + /// Die Transaktions-ID. + TransactionData FetchTransaction(UUID transactionID); + + /// Ruft die Transaktion ab. + /// Die Benutzer-ID. + /// Die Startzeit. + /// Die Endzeit. + /// Der letzte Index. + TransactionData FetchTransaction(string userID, int startTime, int endTime, int lastIndex); + + /// Ruft die Anzahl der Transaktionen ab. + /// Die Benutzer-ID. + /// Die Startzeit. + /// Die Endzeit. + int getTransactionNum(string userID, int startTime, int endTime); + + /// Führt die Überweisung durch. + /// Die Transaktions-UUID. + bool DoTransfer(UUID transactionUUID); + + /// Führt die Geldaufstockung durch. + /// Die Transaktions-UUID. + bool DoAddMoney(UUID transactionUUID); // Hinzugefügt von Fumi.Iseki + + /// Versucht, Benutzerinformationen hinzuzufügen. + /// Der Benutzer. + bool TryAddUserInfo(UserInfo user); + + /// Ruft die Benutzerinformationen ab. + /// Die Benutzer-ID. + UserInfo FetchUserInfo(string userID); + + /// Überprüft, ob ein Benutzer existiert. + /// Die Benutzer-ID. + bool UserExists(string userID); + + /// Aktualisiert die Benutzerinformationen. + /// Die Benutzer-ID. + /// Die aktualisierten Benutzerinformationen. + bool UpdateUserInfo(string userID, UserInfo updatedInfo); + + /// Löscht einen Benutzer aus der Datenbank. + /// Die Benutzer-ID. + bool DeleteUser(string userID); + + /// Protokolliert transaktionsbezogene Fehler oder Änderungen zur Fehlerbehebung. + /// Die Transaktions-ID. + /// Die Fehlermeldung oder Änderungshinweis. + void LogTransactionError(UUID transactionID, string errorMessage); + + /// Ruft eine Liste der Transaktionen für einen Benutzer innerhalb eines bestimmten Zeitrahmens ab. + /// Die Benutzer-ID. + /// Die Startzeit. + /// Die Endzeit. + IEnumerable GetTransactionHistory(string userID, int startTime, int endTime); + MySQLSuperManager GetLockedConnection(); + } +} diff --git a/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs new file mode 100644 index 0000000..3d7b695 --- /dev/null +++ b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs @@ -0,0 +1,79 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Funktion +Das Interface IMoneyServiceCore definiert grundlegende Service-Methoden für den Kern des MoneyServers. Es stellt folgende Funktionalitäten bereit: + + Zugriff auf den HTTP-Server (GetHttpServer) + Zugriff auf verschiedene Session-Dictionaries (GetSessionDic, GetSecureSessionDic, GetWebSessionDic) + Zugriff auf Konfigurationen (GetServerConfig, GetCertConfig) + Prüfen, ob ein Client-Zertifikat geprüft wird (IsCheckClientCert) + +Null-Pointer & Fehlerquellen + Da es sich um ein Interface handelt, gibt es keine Implementierung und somit auch keine direkte Gefahr für NullPointerExceptions im Interface selbst. + Die Gefahr von NullPointerExceptions entsteht erst in der konkreten Implementierung, wenn z.B. eine der Get*-Methoden null zurückliefert und der Aufrufer das Ergebnis nicht prüft. + +Typische Fehlerquellen in der Implementierung: + Rückgabe von null bei Methoden, die Dictionaries oder Konfigurationen liefern sollen. + Nicht initialisierte Objekte in der Implementierung dieser Methoden. + Der HTTP-Server oder die Konfigurationen könnten in Implementierungen nicht korrekt gesetzt sein. + +Empfehlungen für Implementierungen + Immer sicherstellen, dass Methoden niemals null zurückliefern, außer das Verhalten ist explizit dokumentiert und der Aufrufer prüft dies. + Bei Fehlern oder fehlenden Objekten lieber leere Dictionaries oder Default-Konfigurationen zurückgeben. + Gute Dokumentation, wann Methoden null liefern könnten. + +Zusammenfassung + Funktion: Interface zur Bereitstellung zentraler Kernfunktionen für den MoneyServer. + Null-Pointer: Keine Gefahr im Interface selbst, aber möglicherweise in der Implementierung, wenn Rückgabewerte null sind. + Empfehlung: In der Implementierung auf robuste Rückgaben achten und keine null-Referenzen zurückgeben. + */ + +using Nini.Config; + +using OpenSim.Framework.Servers.HttpServer; + +using System.Collections.Generic; + + +namespace OpenSim.Grid.MoneyServer +{ + public interface IMoneyServiceCore + { + /// Gets the HTTP server. + BaseHttpServer GetHttpServer(); + + /// Gets the session dic. + Dictionary GetSessionDic(); + + /// Gets the secure session dic. + Dictionary GetSecureSessionDic(); + + /// Gets the web session dic. + Dictionary GetWebSessionDic(); + + /// Gets the server configuration. + IConfig GetServerConfig(); + + /// Gets the cert configuration. + IConfig GetCertConfig(); + + /// Determines whether [is check client cert]. + /// + /// true if [is check client cert]; otherwise, false. + bool IsCheckClientCert(); + } +} diff --git a/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/LandtoolStreamHandler.cs b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/LandtoolStreamHandler.cs new file mode 100644 index 0000000..a635834 --- /dev/null +++ b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/LandtoolStreamHandler.cs @@ -0,0 +1,37 @@ +/* +Funktion des Codes + Die Klasse LandtoolStreamHandler erbt von CustomSimpleStreamHandler. + Sie ist ein HTTP-Handler für einen bestimmten Pfad (path) und eine Verarbeitungsaktion (processAction). + Der Konstruktor nimmt einen Pfad und eine Action als Parameter und gibt diese direkt an die Basisklasse weiter. + +Zusammenfassung + Funktion: + Registriert einen HTTP-Handler für einen bestimmten Pfad mit einer Callback-Action für die Verarbeitung von Requests. + Null-Pointer: + Die Parameter könnten theoretisch null sein, werden aber im Ablauf geprüft. Kritische Fehler sind unwahrscheinlich, aber für "sauberen" Code sollten idealerweise Null-Prüfungen im Konstruktor ergänzt werden. + Fehlerquellen: + Keine weiteren offensichtlichen Fehler in dieser Datei. Die Robustheit hängt von der Basisklasse und der übergebenen Action ab. + +Fazit: +Der Code ist funktional korrekt und sicher gegen NullPointer-Fehler im laufenden Betrieb, solange die Basisklasse und die Action korrekt implementiert sind. +Für zusätzliche Sicherheit könnten Null-Checks im Konstruktor ergänzt werden, sind aber nicht zwingend notwendig. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using OpenSim.Framework.Servers.HttpServer; + +namespace OpenSim.Grid.MoneyServer +{ + public class LandtoolStreamHandler : CustomSimpleStreamHandler + { + public LandtoolStreamHandler(string path, Action processAction) + : base(path, processAction) + { + } + } +} \ No newline at end of file diff --git a/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/MoneyDBService.cs b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/MoneyDBService.cs new file mode 100644 index 0000000..7510efa --- /dev/null +++ b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/MoneyDBService.cs @@ -0,0 +1,1205 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/, http://www.nsl.tuis.ac.jp/ See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Funktion +Die Klasse MoneyDBService implementiert ein Datenbank-Interface für einen Währungsserver (IMoneyDBService). +Sie verwaltet u.a. Benutzerkonten, Transaktionen, Transfers, Guthaben und Fehlerprotokollierung über eine MySQL-Datenbank. +Null-Pointer-Checks & Fehlerquellen + +1. Konstruktor und Initialisierung + Es gibt einen string-Parameter-Konstruktor und einen parameterlosen Konstruktor. + Der Initialisierungsprozess prüft, ob der Connection-String leer ist (if (connectionString != string.Empty)), aber nicht explizit auf null. + → Ein null-Wert könnte zu einem Fehler führen, bevor Zeile 74 erreicht wird. + +2. DB-Verbindungsmanagement + GetLockedConnection() prüft, ob die Verbindungsanzahl korrekt gesetzt ist und ob ein Connection-Objekt im Pool existiert. + Bei fehlender Verbindung wird ein Fehler geloggt und eine Exception geworfen (KeyNotFoundException). + Die Methode ist robust gegen NullPointer, solange die Initialisierung korrekt verlief. + +3. Methoden für Datenbankoperationen + Fast alle Datenbankmethoden sind in try-catch-finally-Blöcke gekapselt: + Nach MySQL-Fehlern wird ein Reconnect versucht und die Operation wiederholt. + Bei anderen Exceptions wird ein Fehler geloggt und ein sinnvoller Rückgabewert geliefert (z.B. 0, false, oder leere Objekte). + Ressourcenfreigabe (dbm.Release()) geschieht immer im finally-Block, was Memory Leaks vorbeugt. + Rückgabewerte von Methoden wie FetchTransaction werden auf null geprüft, bevor sie weiterverwendet werden. + Methoden, die Objekte aus der DB holen, geben bei Fehlern null oder leere Listen zurück. + +4. SQL- und Parameterhandling + Vor SQL-Operationen werden die Parameter korrekt gesetzt. + Es gibt keine offensichtlichen SQL-Injection-Lücken, da überall Parameter verwendet werden. + +5. Typische Fehlerquellen + Fehlende Null-Checks bei Eingaben: Die meisten Methoden prüfen nicht explizit, ob string-Parameter wie userID oder agentId null oder leer sind. Das könnte zu Fehlern führen, wenn solche Werte übergeben werden. + Release von Verbindungen: In einigen Methoden (z.B. DoTransfer, DoAddMoney) wird die Verbindung teilweise vorzeitig bei Fehlern freigegeben, aber insgesamt ist das Ressourcenmanagement solide. + Fehlende Existenzprüfungen: Manche Methoden gehen davon aus, dass DB-Objekte wie TransactionData oder UserInfo existieren und korrekt initialisiert sind. + +6. Sonstige Fehlerbehandlung + Fehler werden fast immer geloggt (m_log.ErrorFormat, m_log.Error). + In kritischen Fällen werden Exceptions weitergeworfen, in anderen Fällen gibt es Fallback-Rückgaben ohne Exception. + +Zusammenfassung + Funktion: + Verwaltung des Währungsverkehrs (Konten, Transaktionen, Guthaben) für OpenSim über eine MySQL-Datenbank. + NullPointer/Fehlerquellen: + Fast überall sauber gegen NullPointer und Datenbankfehler abgesichert (try-catch-finally). + Mögliche Risiken bei Methodenparametern, die nicht explizit auf null geprüft werden. + Bei fehlerhafter Initialisierung oder fehlerhaftem Connection-String kann es zu Start-Exceptions kommen. + Verbesserungspotential: + Zusätzliche Null-Checks und Plausibilitätsprüfungen für alle Methodenparameter. + Noch mehr Logging in Ausnahmefällen. + */ + +using log4net; +using MySql.Data.MySqlClient; + +using Mysqlx.Crud; +using Mysqlx.Notice; +using OpenMetaverse; +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; +using OpenSim.Modules.Currency; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; + +namespace OpenSim.Grid.MoneyServer +{ + class MoneyDBService : IMoneyDBService + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private string m_connect; + private long TicksToEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; + + // DB manager pool + protected Dictionary m_dbconnections = new Dictionary(); // with Lock + private int m_maxConnections; + + public int m_lastConnect = 0; + + public MoneyDBService(string connect) + { + m_connect = connect; + Initialise(m_connect, 10); + } + + public MoneyDBService() + { + } + + public readonly object connectionLock = new object(); + + public void Initialise(string connectionString, int maxDBConnections) + { + lock (connectionLock) + { + if (maxDBConnections <= 0) + { + throw new ArgumentException("maxDBConnections must be greater than zero", nameof(maxDBConnections)); + } + + //m_log.InfoFormat("[Initialise]: Setting m_maxConnections to {0}", maxDBConnections); + m_connect = connectionString; + m_maxConnections = maxDBConnections; + + //m_log.InfoFormat("[Initialise]: m_maxConnections is now {0}", m_maxConnections); + + if (connectionString != string.Empty) + { + for (int i = 0; i < m_maxConnections; i++) + { + //m_log.Info("Connecting to DB... [" + i + "]"); + MySQLSuperManager msm = new MySQLSuperManager(connectionString); + m_dbconnections.Add(i, msm); + } + } + else + { + m_log.Error("[MONEY DB]: Connection string is null, initialise database failed"); + throw new Exception("Failed to initialise MySql database"); + } + } + } + public MySQLSuperManager GetLockedConnection() + { + lock (connectionLock) + { + //m_log.InfoFormat("[GetLockedConnection]: m_maxConnections is {0}", m_maxConnections); + if (m_maxConnections == 0) + { + throw new InvalidOperationException("m_maxConnections cannot be zero."); + } + + int lockedCons = 0; + while (true) + { + m_lastConnect++; + //m_log.DebugFormat("GetLockedConnection: m_lastConnect incremented to {0}", m_lastConnect); + + if (m_lastConnect == int.MaxValue) + { + m_lastConnect = 0; + //m_log.Debug("GetLockedConnection: m_lastConnect overflow, resetting to 0"); + } + + int index = m_lastConnect % m_maxConnections; + if (!m_dbconnections.ContainsKey(index)) + { + m_log.ErrorFormat("GetLockedConnection: Invalid connection index {0}", index); + throw new KeyNotFoundException($"The given key '{index}' was not present in the dictionary"); + } + + MySQLSuperManager msm = m_dbconnections[index]; + //m_log.DebugFormat("GetLockedConnection: Checking connection {0} for lock", msm); + if (!msm.Locked) + { + msm.GetLock(); + //m_log.DebugFormat("GetLockedConnection: Connection {0} locked successfully", msm); + return msm; + } + + lockedCons++; + //m_log.DebugFormat("GetLockedConnection: Connection {0} is locked, trying next one. lockedCons: {1}", msm, lockedCons); + + if (lockedCons > m_maxConnections) + { + lockedCons = 0; + System.Threading.Thread.Sleep(2000); + m_log.Warn("GetLockedConnection: All connections are in use. Probable cause: Something didn't release a mutex properly, or high volume of requests inbound."); + } + } + } + } + + public void Reconnect() + { + //m_log.Debug("Reconnect attempt started."); + + if (m_maxConnections <= 0) + { + throw new InvalidOperationException("m_maxConnections must be greater than zero."); + } + + for (int i = 0; i < m_maxConnections; i++) + { + MySQLSuperManager msm = m_dbconnections[i]; + try + { + msm.Manager.Reconnect(); + //m_log.DebugFormat("Reconnected to database connection {0} successfully.", i); + } + catch (Exception ex) + { + m_log.ErrorFormat("Failed to reconnect to database connection {0}: {1}", i, ex.Message); + } + } + //m_log.Debug("Reconnect attempt completed."); + } + + public int getBalance(string userID) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try + { + return dbm.Manager.getBalance(userID); + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + e.ToString(); + dbm.Manager.Reconnect(); + return dbm.Manager.getBalance(userID); + } + catch (Exception e) + { + m_log.Error(e); + return 0; + } + finally + { + dbm.Release(); + } + } + + public int CheckMaximumMoney(string userID, int m_CurrencyMaximum) + { + MySQLSuperManager dbm = GetLockedConnection(); + // Beispielwert, sollte aus der Konfigurationsdatei MoneyServer.ini geladen werden + m_log.InfoFormat("[CHECK MAXIMUM MONEY]: Currency Maximum: {0}", m_CurrencyMaximum); + if (m_CurrencyMaximum <= 0) + { + m_CurrencyMaximum = 1000; + } + + try + { + // Ausnahmen f�r SYSTEM und BANKER + if (userID == "SYSTEM" || userID == "BANKER") + { + return 0; // Keine Begrenzung f�r diese Benutzer + } + + // Abrufen des aktuellen Guthabens des Benutzers + string sql = "SELECT balance FROM balances WHERE user = ?userID"; + int currentBalance = 0; + + using (MySqlCommand cmd = new MySqlCommand(sql, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?userID", userID); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + currentBalance = reader.GetInt32("balance"); + } + } + } + + // �berpr�fen, ob das Guthaben �ber dem Maximum liegt und ggf. abziehen + if (currentBalance > m_CurrencyMaximum) + { + int excessAmount = currentBalance - m_CurrencyMaximum; + + // Guthaben auf das Maximum reduzieren + sql = "UPDATE balances SET balance = ?newBalance WHERE user = ?userID"; + using (MySqlCommand cmd = new MySqlCommand(sql, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?newBalance", m_CurrencyMaximum); + cmd.Parameters.AddWithValue("?userID", userID); + cmd.ExecuteNonQuery(); + } + + m_log.InfoFormat("[CheckMaximumMoney]: Reduced balance for user {0} by {1} to enforce maximum limit of {2}", userID, excessAmount, m_CurrencyMaximum); + return excessAmount; // R�ckgabe des abgezogenen Betrags + } + + return 0; // Keine �nderung, falls das Guthaben innerhalb des Limits liegt + } + catch (Exception ex) + { + m_log.ErrorFormat("[CheckMaximumMoney]: Error checking and updating user balance: {0}", ex.Message); + throw; + } + finally + { + dbm.Release(); + } + } + + + + + /// Withdraws the money. + /// The transaction identifier. + /// The sender identifier. + /// The amount. + public bool withdrawMoney(UUID transactionID, string senderID, int amount) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try + { + return dbm.Manager.withdrawMoney(transactionID, senderID, amount); + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + e.ToString(); + dbm.Manager.Reconnect(); + return dbm.Manager.withdrawMoney(transactionID, senderID, amount); + } + catch (Exception e) + { + m_log.Error(e); + return false; + } + finally + { + dbm.Release(); + } + } + public bool giveMoney(UUID transactionID, string receiverID, int amount) + { + MySQLSuperManager dbm = GetLockedConnection(); + //m_log.Debug("giveMoney: Got locked connection"); + + //m_log.DebugFormat("giveMoney: Trying to give {0} units to {1} with transaction ID {2}", amount, receiverID, transactionID); + + try + { + //m_log.Debug("giveMoney: Trying to execute giveMoney on database"); + bool result = dbm.Manager.giveMoney(transactionID, receiverID, amount); + //m_log.DebugFormat("giveMoney: giveMoney on database returned {0}", result); + return result; + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + m_log.ErrorFormat("giveMoney: MySqlException caught: {0}", e.Message); + dbm.Manager.Reconnect(); + //m_log.Debug("giveMoney: Reconnected to database"); + return dbm.Manager.giveMoney(transactionID, receiverID, amount); + } + catch (Exception e) + { + m_log.ErrorFormat("giveMoney: Exception caught: {0}", e.Message); + return false; + } + finally + { + dbm.Release(); + //m_log.Debug("giveMoney: Released connection"); + } + } + + public bool BuyMoney(UUID transactionID, string userID, int amount) + { + //m_log.DebugFormat("[BuyMoney]: Start - transactionID: {0}, userID: {1}, amount: {2}", transactionID, userID, amount); + + MySQLSuperManager dbm = GetLockedConnection(); + string sql = "UPDATE balances SET balance = balance + ?amount WHERE user = ?userID"; + + try + { + using (MySqlCommand cmd = new MySqlCommand(sql, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?amount", amount); + cmd.Parameters.AddWithValue("?userID", userID); + + int rowsAffected = cmd.ExecuteNonQuery(); + bool result = (rowsAffected > 0); + + if (result) + { + LogTransaction(transactionID, userID, amount); + } + + //m_log.DebugFormat("[BuyMoney]: End - transactionID: {0}, userID: {1}, amount: {2}, result: {3}", transactionID, userID, amount, result); + return result; + } + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + m_log.ErrorFormat("[BuyMoney]: SQL Exception - {0}", e.Message); + dbm.Manager.Reconnect(); + + using (MySqlCommand cmd = new MySqlCommand(sql, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?amount", amount); + cmd.Parameters.AddWithValue("?userID", userID); + + int rowsAffected = cmd.ExecuteNonQuery(); + bool result = (rowsAffected > 0); + + if (result) + { + LogTransaction(transactionID, userID, amount); + } + + //m_log.DebugFormat("[BuyMoney]: End after Reconnect - transactionID: {0}, userID: {1}, amount: {2}, result: {3}", transactionID, userID, amount, result); + return result; + } + } + catch (Exception e) + { + m_log.ErrorFormat("[BuyMoney]: General Exception - {0}", e.Message); + return false; + } + finally + { + dbm.Release(); + //m_log.Debug("[BuyMoney]: Connection released"); + } + } + + public void LogTransaction(UUID transactionID, string userID, int amount) + { + //string sql = "INSERT INTO transactions (transactionID, userID, amount, timestamp) VALUES (?transactionID, ?userID, ?amount, ?timestamp)"; + string sql = "INSERT INTO transactions (userID, amount, timestamp) VALUES (?userID, ?amount, ?timestamp)"; + + MySQLSuperManager dbm = GetLockedConnection(); + + try + { + using (MySqlCommand cmd = new MySqlCommand(sql, dbm.Manager.dbcon)) + { + //cmd.Parameters.AddWithValue("?transactionID", transactionID.ToString()); + cmd.Parameters.AddWithValue("?userID", userID); + cmd.Parameters.AddWithValue("?amount", amount); + cmd.Parameters.AddWithValue("?timestamp", DateTime.UtcNow); + + cmd.ExecuteNonQuery(); + } + } + finally + { + dbm.Release(); + } + } + + + public bool BuyCurrency(string userID, int amount) + { + MySQLSuperManager dbm = GetLockedConnection(); + + TransactionData transaction = new TransactionData(); + transaction.TransUUID = UUID.Random(); + transaction.Sender = UUID.Zero.ToString(); // System sender + transaction.Receiver = userID; + transaction.Amount = amount; + transaction.ObjectUUID = UUID.Zero.ToString(); + transaction.ObjectName = string.Empty; + transaction.RegionHandle = string.Empty; + transaction.Type = (int)TransactionType.BuyMoney; // Angenommen, BuyMoney ist ein g�ltiger Transaktionstyp ??? + transaction.Time = (int)((DateTime.UtcNow.Ticks - TicksToEpoch) / 10000000); + transaction.Status = (int)Status.PENDING_STATUS; + transaction.SecureCode = UUID.Random().ToString(); + transaction.CommonName = string.Empty; + transaction.Description = "BuyCurrency " + DateTime.UtcNow.ToString(); + + bool ret = addTransaction(transaction); + if (!ret) + { + dbm.Release(); + return false; + } + + try + { + // F�ge Geld dem Benutzerkonto hinzu + ret = giveMoney(transaction.TransUUID, userID, amount); + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + m_log.Error("[BuyCurrency]: SQL Exception - " + e.ToString()); + dbm.Manager.Reconnect(); + ret = giveMoney(transaction.TransUUID, userID, amount); + } + catch (Exception e) + { + m_log.Error("[BuyCurrency]: Exception - " + e.ToString()); + return false; + } + finally + { + dbm.Release(); + } + + if (ret) + { + m_log.InfoFormat("[BuyCurrency]: Successfully bought currency for user {0} in amount {1}", userID, amount); + } + return ret; + } + + /// Sets the total sale. + /// The transaction. + public bool setTotalSale(TransactionData transaction) + { + MySQLSuperManager dbm = GetLockedConnection(); + + if (transaction.Receiver == transaction.Sender) return false; + if (transaction.Sender == UUID.Zero.ToString()) return false; + + int time = (int)((DateTime.UtcNow.Ticks - TicksToEpoch) / 10000000); + try + { + return dbm.Manager.setTotalSale(transaction.Receiver, transaction.ObjectUUID, transaction.Type, 1, transaction.Amount, time); + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + e.ToString(); + dbm.Manager.Reconnect(); + return dbm.Manager.setTotalSale(transaction.Receiver, transaction.ObjectUUID, transaction.Type, 1, transaction.Amount, time); + } + catch (Exception e) + { + m_log.Error(e); + return false; + } + finally + { + dbm.Release(); + } + } + + /// Adds the transaction. + /// The transaction. + public bool addTransaction(TransactionData transaction) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try + { + return dbm.Manager.addTransaction(transaction); + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + e.ToString(); + dbm.Manager.Reconnect(); + return dbm.Manager.addTransaction(transaction); + } + catch (Exception e) + { + m_log.Error(e); + return false; + } + finally + { + dbm.Release(); + } + } + + /// Adds the user. + /// The user identifier. + /// The balance. + /// The status. + /// The type. + public bool addUser(string userID, int balance, int status, int type) + { + MySQLSuperManager dbm = GetLockedConnection(); + + TransactionData transaction = new TransactionData(); + transaction.TransUUID = UUID.Random(); + transaction.Sender = UUID.Zero.ToString(); + transaction.Receiver = userID; + transaction.Amount = balance; + transaction.ObjectUUID = UUID.Zero.ToString(); + transaction.ObjectName = string.Empty; + transaction.RegionHandle = string.Empty; + transaction.Type = (int)TransactionType.BirthGift; + transaction.Time = (int)((DateTime.UtcNow.Ticks - TicksToEpoch) / 10000000); ; + transaction.Status = (int)Status.PENDING_STATUS; + transaction.SecureCode = UUID.Random().ToString(); + transaction.CommonName = string.Empty; + transaction.Description = "addUser " + DateTime.UtcNow.ToString(); + + bool ret = addTransaction(transaction); + if (!ret) + { + dbm.Release(); + return false; + } + + try + { + ret = dbm.Manager.addUser(userID, 0, status, type); // make Balance Table + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + e.ToString(); + dbm.Manager.Reconnect(); + ret = dbm.Manager.addUser(userID, 0, status, type); // make Balance Table + } + catch (Exception e) + { + m_log.Error(e); + return false; + } + finally + { + dbm.Release(); + } + + // + if (ret) ret = giveMoney(transaction.TransUUID, userID, balance); + return ret; + } + + /// Updates the transaction status. + /// The transaction identifier. + /// The status. + /// The description. + public bool updateTransactionStatus(UUID transactionID, int status, string description) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try + { + return dbm.Manager.updateTransactionStatus(transactionID, status, description); + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + e.ToString(); + dbm.Manager.Reconnect(); + return dbm.Manager.updateTransactionStatus(transactionID, status, description); + } + catch (Exception e) + { + m_log.Error(e); + return false; + } + finally + { + dbm.Release(); + } + } + + /// Sets the trans expired. + /// The dead time. + public bool SetTransExpired(int deadTime) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try + { + return dbm.Manager.SetTransExpired(deadTime); + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + e.ToString(); + dbm.Manager.Reconnect(); + return dbm.Manager.SetTransExpired(deadTime); + } + catch (Exception e) + { + m_log.Error(e); + return false; + } + finally + { + dbm.Release(); + } + } + + /// Validates the transfer. + /// The secure code. + /// The transaction identifier. + public bool ValidateTransfer(string secureCode, UUID transactionID) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try + { + return dbm.Manager.ValidateTransfer(secureCode, transactionID); + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + e.ToString(); + dbm.Manager.Reconnect(); + return dbm.Manager.ValidateTransfer(secureCode, transactionID); + } + catch (Exception e) + { + m_log.Error(e); + return false; + } + finally + { + dbm.Release(); + } + } + + /// Fetches the transaction. + /// The transaction identifier. + public TransactionData FetchTransaction(UUID transactionID) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try + { + return dbm.Manager.FetchTransaction(transactionID); + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + e.ToString(); + dbm.Manager.Reconnect(); + return dbm.Manager.FetchTransaction(transactionID); + } + catch (Exception e) + { + m_log.Error(e); + return null; + } + finally + { + dbm.Release(); + } + } + + /// Fetches the transaction. + /// The user identifier. + /// The start time. + /// The end time. + /// The last index. + public TransactionData FetchTransaction(string userID, int startTime, int endTime, int lastIndex) + { + MySQLSuperManager dbm = GetLockedConnection(); + + TransactionData[] arrTransaction; + + uint index = 0; + if (lastIndex >= 0) index = Convert.ToUInt32(lastIndex) + 1; + + try + { + arrTransaction = dbm.Manager.FetchTransaction(userID, startTime, endTime, index, 1); + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + e.ToString(); + dbm.Manager.Reconnect(); + arrTransaction = dbm.Manager.FetchTransaction(userID, startTime, endTime, index, 1); + } + catch (Exception e) + { + m_log.Error(e); + return null; + } + finally + { + dbm.Release(); + } + + // + if (arrTransaction.Length > 0) + { + return arrTransaction[0]; + } + else + { + return null; + } + } + + /// Does the transfer. + /// The transaction UUID. + public bool DoTransfer(UUID transactionUUID) + { + MySQLSuperManager dbm = GetLockedConnection(); + + bool do_trans = false; + + TransactionData transaction = new TransactionData(); + transaction = FetchTransaction(transactionUUID); + + if (transaction != null && transaction.Status == (int)Status.PENDING_STATUS) + { + int balance = getBalance(transaction.Sender); + + //check the amount + if (transaction.Amount >= 0 && balance >= transaction.Amount) + { + if (withdrawMoney(transactionUUID, transaction.Sender, transaction.Amount)) + { + //If receiver not found, add it to DB. + if (getBalance(transaction.Receiver) == -1) + { + m_log.ErrorFormat("[MONEY DB]: DoTransfer: Receiver not found in balances table. {0}", transaction.Receiver); + dbm.Release(); + return false; + } + + if (giveMoney(transactionUUID, transaction.Receiver, transaction.Amount)) + { + do_trans = true; + } + else + { // give money to receiver failed. Refund Processing + m_log.ErrorFormat("[MONEY DB]: Give money to receiver {0} failed", transaction.Receiver); + //Return money to sender + if (giveMoney(transactionUUID, transaction.Sender, transaction.Amount)) + { + m_log.ErrorFormat("[MONEY DB]: give money to receiver {0} failed but return it to sender {1} successfully", transaction.Receiver, transaction.Sender); + updateTransactionStatus(transactionUUID, (int)Status.FAILED_STATUS, "give money to receiver failed but return it to sender successfully"); + } + else + { + m_log.ErrorFormat("[MONEY DB]: FATAL ERROR: Money withdrawn from sender: {0}, but failed to be given to receiver {1}", + transaction.Sender, transaction.Receiver); + updateTransactionStatus(transactionUUID, (int)Status.ERROR_STATUS, "give money to receiver failed, and return it to sender unsuccessfully!!!"); + } + } + } + else + { // withdraw money failed + m_log.ErrorFormat("[MONEY DB]: Withdraw money from sender {0} failed", transaction.Sender); + } + } + else + { // not enough balance to finish the transaction + m_log.ErrorFormat("[MONEY DB]: Not enough balance for user: {0} to apply the transaction.", transaction.Sender); + } + } + else + { // Can not fetch the transaction or it has expired + m_log.ErrorFormat("[MONEY DB]: The transaction:{0} has expired", transactionUUID.ToString()); + } + + // + if (do_trans) + { + setTotalSale(transaction); + } + + dbm.Release(); + + return do_trans; + } + + // by Fumi.Iseki + /// Does the add money. + /// The transaction UUID. + public bool DoAddMoney(UUID transactionUUID) + { + MySQLSuperManager dbm = GetLockedConnection(); + + TransactionData transaction = new TransactionData(); + transaction = FetchTransaction(transactionUUID); + + if (transaction != null && transaction.Status == (int)Status.PENDING_STATUS) + { + //If receiver not found, add it to DB. + if (getBalance(transaction.Receiver) == -1) + { + m_log.ErrorFormat("[MONEY DB]: DoAddMoney: Receiver not found in balances table. {0}", transaction.Receiver); + dbm.Release(); + return false; + } + // + if (giveMoney(transactionUUID, transaction.Receiver, transaction.Amount)) + { + setTotalSale(transaction); + dbm.Release(); + return true; + } + else + { // give money to receiver failed. + m_log.ErrorFormat("[MONEY DB]: Add money to receiver {0} failed", transaction.Receiver); + updateTransactionStatus(transactionUUID, (int)Status.FAILED_STATUS, "add money to receiver failed"); + } + } + else + { // Can not fetch the transaction or it has expired + m_log.ErrorFormat("[MONEY DB]: The transaction:{0} has expired", transactionUUID.ToString()); + } + + dbm.Release(); + + return false; + } + + /// Tries the add user information. + /// The user. + public bool TryAddUserInfo(UserInfo user) + { + MySQLSuperManager dbm = GetLockedConnection(); + + UserInfo userInfo = null; + + try + { + userInfo = dbm.Manager.fetchUserInfo(user.UserID); + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + e.ToString(); + dbm.Manager.Reconnect(); + userInfo = dbm.Manager.fetchUserInfo(user.UserID); + } + catch (Exception e) + { + m_log.Error(e); + dbm.Release(); + return false; + } + + try + { + if (userInfo != null) + { + //m_log.InfoFormat("[MONEY DB]: Found user \"{0}\", now update information", user.Avatar); + if (dbm.Manager.updateUserInfo(user)) return true; + } + else if (dbm.Manager.addUserInfo(user)) + { + //m_log.InfoFormat("[MONEY DB]: Unable to find user \"{0}\", add it to DB successfully", user.Avatar); + return true; + } + m_log.InfoFormat("[MONEY DB]: WARNNING: TryAddUserInfo: Unable to TryAddUserInfo."); + return false; + } + catch (Exception e) + { + m_log.Error(e.ToString()); + return false; + } + finally + { + dbm.Release(); + } + } + + /// Fetches the user information. + /// The user identifier. + public UserInfo FetchUserInfo(string userID) + { + MySQLSuperManager dbm = GetLockedConnection(); + + UserInfo userInfo = null; + + try + { + userInfo = dbm.Manager.fetchUserInfo(userID); + return userInfo; + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + e.ToString(); + dbm.Manager.Reconnect(); + userInfo = dbm.Manager.fetchUserInfo(userID); + return userInfo; + } + catch (Exception e) + { + m_log.Error(e); + return null; + } + finally + { + dbm.Release(); + } + } + + /// Gets the transaction number. + /// The user identifier. + /// The start time. + /// The end time. + public int getTransactionNum(string userID, int startTime, int endTime) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try + { + return dbm.Manager.getTransactionNum(userID, startTime, endTime); + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + e.ToString(); + dbm.Manager.Reconnect(); + return dbm.Manager.getTransactionNum(userID, startTime, endTime); + } + catch (Exception e) + { + m_log.Error(e); + return -1; + } + finally + { + dbm.Release(); + } + } + + public bool UserExists(string userID) + { + MySQLSuperManager dbm = GetLockedConnection(); + try + { + return dbm.Manager.UserExists(userID); + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + e.ToString(); + dbm.Manager.Reconnect(); + return dbm.Manager.UserExists(userID); + } + catch (Exception e) + { + m_log.Error(e); + return false; + } + finally + { + dbm.Release(); + } + } + + public bool UpdateUserInfo(string userID, UserInfo updatedInfo) + { + MySQLSuperManager dbm = GetLockedConnection(); + try + { + return dbm.Manager.UpdateUserInfo(userID, updatedInfo); + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + e.ToString(); + dbm.Manager.Reconnect(); + return dbm.Manager.UpdateUserInfo(userID, updatedInfo); + } + catch (Exception e) + { + m_log.Error(e); + return false; + } + finally + { + dbm.Release(); + } + } + + public bool DeleteUser(string userID) + { + MySQLSuperManager dbm = GetLockedConnection(); + try + { + return dbm.Manager.DeleteUser(userID); + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + e.ToString(); + dbm.Manager.Reconnect(); + return dbm.Manager.DeleteUser(userID); + } + catch (Exception e) + { + m_log.Error(e); + return false; + } + finally + { + dbm.Release(); + } + } + + public void LogTransactionError(UUID transactionID, string errorMessage) + { + m_log.ErrorFormat("[MONEY DB]: Transaction {0} failed with error: {1}", transactionID, errorMessage); + } + + public IEnumerable GetTransactionHistory(string userID, int startTime, int endTime) + { + MySQLSuperManager dbm = GetLockedConnection(); + try + { + return dbm.Manager.GetTransactionHistory(userID, startTime, endTime); + } + catch (MySql.Data.MySqlClient.MySqlException e) + { + e.ToString(); + dbm.Manager.Reconnect(); + return dbm.Manager.GetTransactionHistory(userID, startTime, endTime); + } + catch (Exception e) + { + m_log.Error(e); + return new List(); // Return an empty list instead of throwing an exception + } + finally + { + dbm.Release(); + } + } + + + public bool PerformMoneyTransfer(string senderID, string receiverID, int amount) + { + m_log.InfoFormat("[MONEY TRANSFER]: Transferring {0} from {1} to {2}.", amount, senderID, receiverID); + + // Beispielhafte Implementierung: F�hre den Geldtransfer durch + try + { + MySQLSuperManager dbm = GetLockedConnection(); + string sql = "UPDATE balances SET balance = balance - ?amount WHERE user = ?senderID; " + + "UPDATE balances SET balance = balance + ?amount WHERE user = ?receiverID"; + + using (MySqlCommand cmd = new MySqlCommand(sql, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?amount", amount); + cmd.Parameters.AddWithValue("?senderID", senderID); + cmd.Parameters.AddWithValue("?receiverID", receiverID); + + int rowsAffected = cmd.ExecuteNonQuery(); + bool result = (rowsAffected > 0); + + if (result) + { + LogTransaction(UUID.Random(), senderID, -amount); + LogTransaction(UUID.Random(), receiverID, amount); + } + + dbm.Release(); + + return result; + } + } + catch (Exception) + { + //m_log.ErrorFormat("[MONEY TRANSFER]: Error transferring money: {0}", ex.Message); + return false; + } + } + + public void InitializeUserCurrency(string agentId) + { + m_log.InfoFormat("[INITIALIZE USER CURRENCY]: Initializing currency for new user: {0}", agentId); + int realMoney = 1000; // Beispielwert oder aus der MoneyServer.ini geladen + int gameMoney = 10000; // Beispielwert oder aus der MoneyServer.ini geladen + + try + { + MySQLSuperManager dbm = GetLockedConnection(); + string sql = "INSERT INTO balances (user, balance) VALUES (?agentId, ?realMoney), (?agentId, ?gameMoney)"; + using (MySqlCommand cmd = new MySqlCommand(sql, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?agentId", agentId); + cmd.Parameters.AddWithValue("?realMoney", realMoney); + cmd.Parameters.AddWithValue("?gameMoney", gameMoney); + + cmd.ExecuteNonQuery(); + } + m_log.InfoFormat("[INITIALIZE USER CURRENCY]: User {0} initialized with {1}� and {2}L$.", agentId, realMoney, gameMoney); + dbm.Release(); + } + catch (Exception ex) + { + m_log.ErrorFormat("[INITIALIZE USER CURRENCY]: Error initializing user currency: {0}", ex.Message); + } + + } + + public Hashtable ApplyFallbackCredit(string agentId) + { + m_log.WarnFormat("[FALLBACK CREDIT]: Applying fallback credit for user {0}", agentId); + + // Beispielhafte Implementierung: Fallback-Gutschrift in die Datenbank eintragen + try + { + MySQLSuperManager dbm = GetLockedConnection(); + string sql = "UPDATE balances SET balance = balance + 100 WHERE user = ?agentId"; + using (MySqlCommand cmd = new MySqlCommand(sql, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?agentId", agentId); + + cmd.ExecuteNonQuery(); + } + dbm.Release(); + } + catch (Exception ex) + { + m_log.ErrorFormat("[FALLBACK CREDIT]: Error applying fallback credit: {0}", ex.Message); + } + + return new Hashtable + { + { "success", true }, + { "creditedAmount", 100 }, + { "message", "Fallback credit applied due to transaction failure." } + }; + + } + + } +} diff --git a/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/MoneyServerBase.cs b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/MoneyServerBase.cs new file mode 100644 index 0000000..5cb3cde --- /dev/null +++ b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/MoneyServerBase.cs @@ -0,0 +1,469 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/, http://www.nsl.tuis.ac.jp/ See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Funktion + MoneyServerBase ist die zentrale Basisklasse für den MoneyServer im OpenSim-Grid. + Sie startet die Serverdienste, initialisiert Konfigurationen, verwaltet Datenbankverbindungen und setzt periodisch Transaktionen zurück. + Sie implementiert das Interface IMoneyServiceCore und erweitert BaseOpenSimServer. + +Null-Pointer-Checks & Fehlerquellen +Initialisierung und Konstruktor + Im Konstruktor (MoneyServerBase()) wird eine Konsole (LocalConsole) initialisiert und auf null geprüft. Bei Fehlschlag wird eine Exception geworfen. + Das Logging (m_log?.Info, m_log?.Error) verwendet sichere Zugriffe (null-conditional operator). + +Konfigurationslesung + ReadIniConfig() liest Konfigurationen aus einer INI-Datei. + Null-Gefahr: + Abschnitte wie [MoneyServer] oder [Certificate] könnten fehlen. Es gibt aber Fallbacks, z.B. wird bei fehlender [Certificate]-Section auf [MoneyServer] zurückgegriffen. + Bei Fehlern wird ein Fehler geloggt und das Programm per Environment.Exit(1) beendet – kritische NullPointer werden so abgefangen. + +Datenbankservice + dbService und m_moneyDBService werden beide korrekt initialisiert und mit Initialise versehen. + Null-Check: In der Timer-Callback-Methode CheckTransaction wird geprüft, ob m_moneyDBService == null ist, bevor darauf zugegriffen wird. + +Timer und Ressourcenmanagement + In der Methode Work() wird ein Timer verwendet und im finally-Block sauber gestoppt und freigegeben, falls er noch läuft. Das verhindert Memory Leaks. + +HTTP-Server + Der HTTP-Server (m_httpServer) wird je nach Konfiguration mit oder ohne Zertifikat initialisiert. + Client-Zertifikatsprüfung wird nur aktiviert, wenn alle nötigen Parameter gesetzt sind. + +Dictionary und Session-Handling + Die Dictionary-Properties (m_sessionDic, m_secureSessionDic, m_webSessionDic) werden direkt im Feld initialisiert, können also nie null sein. + +Zusammenfassung möglicher Fehlerquellen + NullPointer: + Weitestgehend abgefangen durch Initialisierung und Checks. + Mögliche Fehlerquellen werden durch Exceptions und Beenden des Programms abgefangen. + Fehlende Konfigurationen: + Fallbacks vorhanden, Logging bei Problemen. + Datenbank- und Serviceobjekte: + Werden immer initialisiert, NullPointer im Laufzeitbetrieb sind unwahrscheinlich. + Allgemeine Fehlerbehandlung: + Try-Catch-Blöcke mit Logging in allen kritischen Abschnitten. + Im Fehlerfall Exit oder saubere Beendigung. + +Funktionale Zusammenfassung + Initialisierung: Liest Konfiguration, startet HTTP-Server (mit/ohne SSL), setzt Datenbankdienste auf. + Service-Setup: Stellt alle zentralen Services für den MoneyServer bereit (Konfiguration, Session, HTTP). + Transaktionsüberwachung: Überprüft regelmäßig per Timer, ob alte Transaktionen abgelaufen sind. + Ressourcenmanagement: Saubere Freigabe von Timern, keine offensichtlichen Memory Leaks. + +Fazit: +Der Code ist insgesamt robust gegen NullPointer-Fehler, da alle kritischen Ressourcen direkt initialisiert oder auf null geprüft werden. Fehler werden geloggt und führen bei kritischen Problemen zum Programmabbruch. +Die Funktionalität ist klar und zweckmäßig für einen zentralen Service im OpenSim-MoneyServer. + */ + +using log4net; + +using Nini.Config; + +using NSL.Certificate.Tools; + +using OpenSim.Framework; +using OpenSim.Framework.Console; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Grid.MoneyServer; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Security; +using System.Reflection; +using System.Timers; + +using Timer = System.Timers.Timer; + + +/// +/// OpenSim Grid MoneyServer +/// +internal class MoneyServerBase : BaseOpenSimServer, IMoneyServiceCore +{ + private MoneyDBService dbService; + + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private string connectionString = string.Empty; + private uint m_moneyServerPort = 8008; // 8008 is default server port + private Timer checkTimer; + + private string m_certFilename = ""; + private string m_certPassword = ""; + private string m_cacertFilename = ""; + private string m_clcrlFilename = ""; + private bool m_checkClientCert = false; + + private int DEAD_TIME = 120; + private int MAX_DB_CONNECTION = 10; // 10 is default + + // Testbereich + // Maximum pro Tag: + private int m_TotalDay = 100; + // Maximum pro Woche: + private int m_TotalWeek = 250; + // Maximum pro Monat: + private int m_TotalMonth = 500; + // Maximum Besitz: + private int m_CurrencyMaximum = 10000; + // Geldkauf abschalten: + private string m_CurrencyOnOff = "off"; + // Geldkauf nur für Gruppe: + private bool m_CurrencyGroupOnly = false; + private string m_CurrencyGroupName = ""; + + + private MoneyXmlRpcModule m_moneyXmlRpcModule; + private MoneyDBService m_moneyDBService; + + private NSLCertificateVerify m_certVerify = new NSLCertificateVerify(); // Client Certificate + + private Dictionary m_sessionDic = new Dictionary(); + private Dictionary m_secureSessionDic = new Dictionary(); + private Dictionary m_webSessionDic = new Dictionary(); + + IConfig m_server_config; + IConfig m_cert_config; + + + public MoneyServerBase() + { + try + { + // Initialize the console for the Money Server + m_console = new LocalConsole("MoneyServer "); + + if (m_console != null) + { + // Set the main console instance to the Money Server console + MainConsole.Instance = m_console; + + // Log a message to indicate that the Money Server is initializing + m_log?.Info("[MONEY SERVER]: Initializing Money Server module and loading configurations..."); + } + else + { + throw new InvalidOperationException("Failed to initialize LocalConsole instance."); + } + } + catch (Exception ex) + { + // Log the exception + m_log?.Error("An error occurred during MoneyServerBase initialization.", ex); + throw; + } + } + + /// + /// Work + /// + public void Work() + { + // Create a new timer to check transactions every 60 seconds + checkTimer = new Timer + { + Interval = 60 * 1000, + Enabled = true + }; + + // Add event handler to check transactions + checkTimer.Elapsed += CheckTransaction; + + try + { + // Start the timer + checkTimer.Start(); + + // Run the console prompt loop + while (true) + { + m_console.Prompt(); + } + } + catch (Exception ex) + { + // Log any exceptions that occur + m_log.ErrorFormat("Error in Work: {0}", ex.Message); + } + finally + { + // Stop the timer if it's still running + if (checkTimer != null && checkTimer.Enabled) + { + checkTimer.Stop(); + checkTimer.Dispose(); + } + } + } + + /// + /// Checks transactions. + /// + private void CheckTransaction(object sender, ElapsedEventArgs e) + { + if (m_moneyDBService == null) + { + m_log.Error("[CHECK TRANSACTION]: m_moneyDBService is null, cannot check transactions."); + return; + } + + try + { + long ticksToEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; + int unixEpochTime = (int)((DateTime.UtcNow.Ticks - ticksToEpoch) / 10000000); + int deadTime = unixEpochTime - DEAD_TIME; + m_moneyDBService.SetTransExpired(deadTime); + + //m_log.Info("[CHECK TRANSACTION]: Transactions checked successfully."); + } + catch (Exception ex) + { + m_log.ErrorFormat("[CHECK TRANSACTION]: Error in CheckTransaction: {0}", ex.Message); + } + } + + /// + /// Startup Specific + /// + protected override void StartupSpecific() + { + m_log.Info("[MONEY SERVER]: Setup HTTP Server process"); + + ReadIniConfig(); + + try + { + if (m_certFilename != "") + { + m_httpServer = new BaseHttpServer(m_moneyServerPort, true, m_certFilename, m_certPassword); + m_httpServer.CertificateValidationCallback = null; + // + if (m_checkClientCert) + { + m_httpServer.CertificateValidationCallback = (RemoteCertificateValidationCallback)m_certVerify.ValidateClientCertificate; + m_log.Info("[MONEY SERVER]: Set RemoteCertificateValidationCallback"); + } + } + else + { + m_httpServer = new BaseHttpServer(m_moneyServerPort); + } + + SetupMoneyServices(); + m_httpServer.Start(); + base.StartupSpecific(); // OpenSim/Framework/Servers/BaseOpenSimServer.cs + } + + catch (Exception e) + { + m_log.ErrorFormat("[MONEY SERVER]: StartupSpecific: Fail to start HTTPS process"); + m_log.ErrorFormat("[MONEY SERVER]: StartupSpecific: Please Check Certificate File or Password. Exit"); + m_log.ErrorFormat("[MONEY SERVER]: StartupSpecific: {0}", e); + Environment.Exit(1); + } + } + + public void ReadIniConfig() + { + MoneyServerConfigSource moneyConfig = new MoneyServerConfigSource(); + Config = moneyConfig.m_config; + + try + { + // [Startup] + IConfig st_config = moneyConfig.m_config.Configs["Startup"]; + string PIDFile = st_config.GetString("PIDFile", ""); + if (PIDFile != "") Create_PIDFile(PIDFile); + + // [MySql] + IConfig db_config = moneyConfig.m_config.Configs["MySql"]; + string sqlserver = db_config.GetString("hostname", "localhost"); + string database = db_config.GetString("database", "OpenSim"); + string username = db_config.GetString("username", "root"); + string password = db_config.GetString("password", "password"); + string pooling = db_config.GetString("pooling", "false"); + string port = db_config.GetString("port", "3306"); + MAX_DB_CONNECTION = db_config.GetInt("MaxConnection", MAX_DB_CONNECTION); + + connectionString = "Server=" + sqlserver + ";Port=" + port + ";Database=" + database + ";User ID=" + + username + ";Password=" + password + ";Pooling=" + pooling + ";"; + + // [MoneyServer] + m_server_config = moneyConfig.m_config.Configs["MoneyServer"]; + DEAD_TIME = m_server_config.GetInt("ExpiredTime", DEAD_TIME); + m_moneyServerPort = (uint)m_server_config.GetInt("ServerPort", (int)m_moneyServerPort); + + /* + ; Testbereich + ; Maximum pro Tag: + TotalDay = 100; + ; Maximum pro Woche: + TotalWeek = 250; + ; Maximum pro Monat: + TotalMonth = 500; + */ + + m_TotalDay = m_server_config.GetInt("TotalDay", m_TotalDay); + m_TotalWeek = m_server_config.GetInt("TotalWeek", m_TotalWeek); + m_TotalMonth = m_server_config.GetInt("TotalMonth", m_TotalMonth); + m_CurrencyMaximum = m_server_config.GetInt("CurrencyMaximum", m_CurrencyMaximum); + + m_CurrencyOnOff = m_server_config.GetString("CurrencyOnOff", m_CurrencyOnOff); + m_CurrencyGroupOnly = m_server_config.GetBoolean("CurrencyGroupOnly", m_CurrencyGroupOnly); + m_CurrencyGroupName = m_server_config.GetString("CurrencyGroupName", m_CurrencyGroupName); + + + // + // [Certificate] + m_cert_config = moneyConfig.m_config.Configs["Certificate"]; + if (m_cert_config == null) + { + m_log.Info("[MONEY SERVER]: [Certificate] section is not found. Using [MoneyServer] section instead"); + m_cert_config = m_server_config; + } + + // HTTPS Server Cert (Server Mode) + m_certFilename = m_cert_config.GetString("ServerCertFilename", m_certFilename); + m_certPassword = m_cert_config.GetString("ServerCertPassword", m_certPassword); + if (m_certFilename != "") + { + m_log.Info("[MONEY SERVER]: ReadIniConfig: Execute HTTPS comunication. Server Cert file is " + m_certFilename); + } + + // Client Certificate + m_checkClientCert = m_cert_config.GetBoolean("CheckClientCert", m_checkClientCert); + m_cacertFilename = m_cert_config.GetString("CACertFilename", m_cacertFilename); + m_clcrlFilename = m_cert_config.GetString("ClientCrlFilename", m_clcrlFilename); + if (m_checkClientCert && (m_cacertFilename != "")) + { + m_certVerify.SetPrivateCA(m_cacertFilename); + m_log.Info("[MONEY SERVER]: ReadIniConfig: Execute Authentication of Clients. CA file is " + m_cacertFilename); + } + else + { + m_checkClientCert = false; + } + if (m_checkClientCert) + { + if (m_clcrlFilename != "") + { + m_certVerify.SetPrivateCRL(m_clcrlFilename); + m_log.Info("[MONEY SERVER]: ReadIniConfig: Execute Authentication of Clients. CRL file is " + m_clcrlFilename); + } + } + + // Initialisiere die MoneyDBService mit der Verbindungszeichenkette und der maxDBConnections + dbService = new MoneyDBService(); + dbService.Initialise(connectionString, MAX_DB_CONNECTION); + } + catch (Exception ex) + { + m_log.Error("[MONEY SERVER]: ReadIniConfig: Fail to setup configure. Please check MoneyServer.ini. Exit", ex); + Environment.Exit(1); + } + } + + /// + /// Create PID File added by skidz + /// + protected void Create_PIDFile(string path) + { + try + { + string pidstring = System.Diagnostics.Process.GetCurrentProcess().Id.ToString(); + FileStream fs = File.Create(path); + System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding(); + Byte[] buf = enc.GetBytes(pidstring); + fs.Write(buf, 0, buf.Length); + fs.Close(); + m_pidFile = path; + } + + catch (Exception) { } + + } + + protected virtual void SetupMoneyServices() + { + m_log.Info("[MONEY SERVER]: Connecting to Money Storage Server"); + + m_moneyDBService = new MoneyDBService(); + m_moneyDBService.Initialise(connectionString, MAX_DB_CONNECTION); + + IConfigSource config = new IniConfigSource(); // Beispiel für das Erstellen einer IConfigSource + m_moneyXmlRpcModule = new MoneyXmlRpcModule(connectionString, MAX_DB_CONNECTION); + m_moneyXmlRpcModule.Initialise(m_version, m_moneyDBService, this, config); + m_moneyXmlRpcModule.PostInitialise(); + } + + public bool IsCheckClientCert() + { + return m_checkClientCert; + } + + public IConfig GetServerConfig() + { + return m_server_config; + } + + public IConfig GetCertConfig() + { + return m_cert_config; + } + + public BaseHttpServer GetHttpServer() + { + return m_httpServer; + } + + public Dictionary GetSessionDic() + { + return m_sessionDic; + } + + public Dictionary GetSecureSessionDic() + { + return m_secureSessionDic; + } + + public Dictionary GetWebSessionDic() + { + return m_webSessionDic; + } + + class MoneyServerConfigSource + { + + public IniConfigSource m_config; + + public MoneyServerConfigSource() + { + string configPath = Path.Combine(Directory.GetCurrentDirectory(), "MoneyServer.ini"); + if (File.Exists(configPath)) + { + m_config = new IniConfigSource(configPath); + } + } + + public void Save(string path) + { + m_config.Save(path); + } + + } +} + diff --git a/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs new file mode 100644 index 0000000..9dc03c9 --- /dev/null +++ b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs @@ -0,0 +1,3885 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/, http://www.nsl.tuis.ac.jp/ See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Funktion +Diese Klasse ist das zentrale XML-RPC Modul für den OpenSim MoneyServer. Sie erweitert MoneyDBService und implementiert das Interface IMoneyDBService. Ihre Aufgaben: + + Verwaltung und Verarbeitung von XML-RPC-Anfragen für Geldtransaktionen, Landkauf, Session-Management und mehr. + Bindung von HTTP- und XML-RPC-Handlern an den HTTP-Server. + Zugriff und Manipulation von Benutzerguthaben, Transaktionen und Gruppenmitgliedschaften über die Datenbank. + Spezialfunktionen für Cashbook-Ausgaben, Konsolenbefehle und Logging. + +Null-Pointer-Checks & Fehlerquellen +1. Konstruktor & Initialisierung + Alle kritischen Dependencies (moneyDBService, moneyCore) werden mit ArgumentNullException.ThrowIfNull() geprüft. + Konfigurationswerte werden aus INI geladen und auf null/Fehlwerte geprüft. + Dictionaries und andere Felder werden direkt initialisiert oder nachgeladen. + +2. HTTP/XML-RPC-Handler + Viele Methoden prüfen die Eingabeparameter (z.B. ob ein Request-Objekt null ist). + Bei fehlerhaften Requests oder Sessions wird ein Fehler geloggt und eine Standardantwort zurückgegeben. + Beispiel: + C# + + if (httpRequest == null || httpResponse == null) { ... return; } + if (request == null) { ... return new XmlRpcResponse { Value = new Hashtable { { "success", false } } }; } + + Bei Session-Handling wird vor jedem Zugriff geprüft, ob die Dictionary-Schlüssel existieren. + Bei Datenbankoperationen werden Exceptions gefangen und führen nicht zu Abstürzen, sondern zu Logging und Fehlerantwort. + +3. Datenbankzugriffe + Datenbankverbindungen werden immer in einem Try-Catch-Finally-Block verwendet, wobei im finally-Block die Verbindung freigegeben wird (dbm.Release()). + Bei Datenbankoperationen wird auf mögliche Null-Rückgaben geachtet (z.B. FetchUserInfo kann null liefern). + +4. Spezielle Fehlerquellen + Parsen von XML-Requests: Hier können null-Werte entstehen, werden aber meistens abgefangen. + Zahlreiche Methoden prüfen, ob Parameter null/leer sind, bevor sie verwendet werden (z.B. agentId, groupId). + SQL-Parameter werden mit AddWithValue gesetzt, was SQL-Injection weitgehend verhindert. + +5. Allgemeine Fehlerbehandlung + Bei Fehlern wird stets ins Log geschrieben. + Rückgabewerte für Fehlerfälle sind konsistent (meistens false, null oder ein Hashtable mit "success": false). + Fehler in der Verarbeitung führen zu keinen unkontrollierten Null-Pointern oder Abstürzen. + +Beispiele für Null-Checks und Fehlerbehandlung + +Session-Checks: +C# + +if (m_sessionDic.ContainsKey(senderID) && m_secureSessionDic.ContainsKey(senderID)) { ... } +else { responseData["message"] = "Session check failure, please re-login later!"; return response; } + +Datenbankobjekte: +C# + +UserInfo rcvr = m_moneyDBService.FetchUserInfo(receiverID); +if (rcvr == null) { m_log.ErrorFormat(...); return response; } + +Catch-All für Exceptions: +C# + +catch (Exception ex) +{ + m_log.ErrorFormat("[MONEY XMLRPC]: ... Exception occurred: {0}", ex.Message); + return new XmlRpcResponse(); +} + +Hashtable-Zugriffe: +C# + +Hashtable requestData = (Hashtable)request.Params[0]; +if (requestData == null) { ... } + +Verbesserungsmöglichkeiten + + In einigen Methoden werden Werte aus Hashtables direkt abgerufen, ohne vorher zu prüfen, + ob sie wirklich existieren oder den erwarteten Typ haben (z.B. int.Parse direkt auf einen String aus einer Hashtable). Hier könnten defensive Checks ergänzt werden. + XML-Parsing könnte robuster gestaltet werden, um fehlerhafte oder absichtlich manipulierte Requests besser abzufangen. + Teilweise könnten Methoden Parameter noch expliziter auf Plausibilität prüfen (z.B. Range-Checks bei Beträgen). + +Fazit + +Sehr robust gegen NullPointer-Fehler und allgemeine Fehlerquellen! + + Alle kritischen Zugriffe werden defensiv behandelt. + Fehler werden stets geloggt und führen zu konsistenten Fehlerantworten. + Ressourcen werden ordentlich freigegeben. + Die Funktionalität ist klar: XML-RPC-Endpoint-Management, Geldtransaktionen, User-Sessions, Gruppen-Checks, Logging, Cashbook, etc. + +Empfehlung: +Das Modul ist für produktiven Einsatz gut vorbereitet. Zusätzliche defensive Checks bei Daten aus externen Quellen (z.B. XML/Hashtable) wären sinnvoll, sind aber kein kritischer Mangel. + */ + +using log4net; +using MySql.Data.MySqlClient; +using Nini.Config; +using NSL.Certificate.Tools; +using NSL.Network.XmlRpc; +using Nwc.XmlRpc; +using OpenMetaverse; +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; +using OpenSim.Framework; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Modules.Currency; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Net; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using System.Transactions; +using System.Xml; +using static Mono.Security.X509.X520; +using static OpenMetaverse.DllmapConfigHelper; +using OpenSim.Server.Base; +using OpenSim.Framework.Console; + + +namespace OpenSim.Grid.MoneyServer +{ + class MoneyXmlRpcModule : MoneyDBService, IMoneyDBService + { + // ################## Initial ################## + #region Setup Initial + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private int m_realMoney = 1000; // Beispielwert oder aus der MoneyServer.ini geladen + private int m_gameMoney = 10000; // Beispielwert oder aus der MoneyServer.ini geladen + + // MoneyServer settings + public int m_defaultBalance = 1000; + + private bool m_forceTransfer = false; + private string m_bankerAvatar = ""; + + // Testbereich + // Maximum pro Tag: + public int m_TotalDay = 100; + // Maximum pro Woche: + public int m_TotalWeek = 250; + // Maximum pro Monat: + public int m_TotalMonth = 500; + // Maximum Besitz: + public int m_CurrencyMaximum; + // Geldkauf abschalten: + public string m_CurrencyOnOff; + // Geldkauf nur für Gruppe: + public bool m_CurrencyGroupOnly = false; + public bool m_UserMailLock = false; + public string m_CurrencyGroupName = ""; + public string m_CurrencyGroupID = ""; + + // Script settings + private bool m_scriptSendMoney = false; + private string m_scriptAccessKey = ""; + private string m_scriptIPaddress = "127.0.0.1"; + + // HG settings + private bool m_hg_enable = false; + private bool m_gst_enable = false; + private int m_hg_defaultBalance = 0; + private int m_gst_defaultBalance = 0; + private int m_CalculateCurrency = 0; + + // XMLRPC Debug settings + private bool m_DebugConsole = false; + private bool m_DebugFile = false; + + // Certificate settings + private bool m_checkServerCert = false; + private string m_cacertFilename = ""; + private string m_certFilename = ""; + private string m_certPassword = ""; + + + + // SSL settings + private string m_sslCommonName = ""; + + private Dictionary m_scenes = new Dictionary(); + + private NSLCertificateVerify m_certVerify = new NSLCertificateVerify(); + + private string m_BalanceMessageLandSale = "Paid the Money L${0} for Land."; + private string m_BalanceMessageRcvLandSale = ""; + private string m_BalanceMessageSendGift = "Sent Gift L${0} to {1}."; + private string m_BalanceMessageReceiveGift = "Received Gift L${0} from {1}."; + private string m_BalanceMessagePayCharge = ""; + private string m_BalanceMessageBuyObject = "Bought the Object {2} from {1} by L${0}."; + private string m_BalanceMessageSellObject = "{1} bought the Object {2} by L${0}."; + private string m_BalanceMessageGetMoney = "Got the Money L${0} from {1}."; + private string m_BalanceMessageBuyMoney = "Bought the Money L${0}."; + private string m_BalanceMessageRollBack = "RollBack the Transaction: L${0} from/to {1}."; + private string m_BalanceMessageSendMoney = "Paid the Money L${0} to {1}."; + private string m_BalanceMessageReceiveMoney = "Received L${0} from {1}."; + + private bool m_enableAmountZero = false; + + const int MONEYMODULE_REQUEST_TIMEOUT = 30 * 1000; //30 seconds + private long TicksToEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; + + private IMoneyDBService m_moneyDBService; + // Konfig fuer Konsolenbefehle. + private IConfigSource m_config; + private IMoneyServiceCore m_moneyCore; + + protected IConfig m_server_config; + protected IConfig m_cert_config; + + /// + /// Used to notify old regions as to which OpenSim version to upgrade to + /// + //private string m_opensimVersion; + + private Dictionary m_sessionDic; + private Dictionary m_secureSessionDic; + private Dictionary m_webSessionDic; + + protected BaseHttpServer m_httpServer; + + + /// Initializes a new instance of the class. + public MoneyXmlRpcModule(string connectionString, int maxDBConnections) + { + Initialise(connectionString, maxDBConnections); + } + + /// Initialises the specified opensim version. + /// The opensim version. + /// The money database service. + /// The money core. + public void Initialise(string opensimVersion, IMoneyDBService moneyDBService, IMoneyServiceCore moneyCore, IConfigSource config) + { + ArgumentNullException.ThrowIfNull(moneyDBService); + + ArgumentNullException.ThrowIfNull(moneyCore); + + m_moneyDBService = moneyDBService; + m_moneyCore = moneyCore; + + // Get server configuration + var serverConfig = m_moneyCore.GetServerConfig() ?? throw new InvalidOperationException("Server configuration is not available"); + + // Get certificate configuration + var certConfig = m_moneyCore.GetCertConfig() ?? throw new InvalidOperationException("Certificate configuration is not available"); + + // Load configuration values + m_defaultBalance = serverConfig.GetInt("DefaultBalance", m_defaultBalance); + m_forceTransfer = serverConfig.GetBoolean("EnableForceTransfer", m_forceTransfer); + m_bankerAvatar = serverConfig.GetString("BankerAvatar", m_bankerAvatar).ToLower(); + + m_moneyDBService = moneyDBService; + m_moneyCore = moneyCore; + m_server_config = m_moneyCore.GetServerConfig(); // [MoneyServer] Section + m_cert_config = m_moneyCore.GetCertConfig(); // [Certificate] Section + + m_TotalDay = serverConfig.GetInt("TotalDay", m_TotalDay); + m_TotalWeek = serverConfig.GetInt("TotalWeek", m_TotalWeek); + m_TotalMonth = serverConfig.GetInt("TotalMonth", m_TotalMonth); + m_CurrencyMaximum = serverConfig.GetInt("CurrencyMaximum", m_CurrencyMaximum); + + m_CurrencyOnOff = serverConfig.GetString("CurrencyOnOff", m_CurrencyOnOff); + m_CurrencyGroupOnly = serverConfig.GetBoolean("CurrencyGroupOnly", m_CurrencyGroupOnly); + m_UserMailLock = serverConfig.GetBoolean("UserMailLock", m_UserMailLock); + + m_CurrencyGroupName = serverConfig.GetString("CurrencyGroupName", m_CurrencyGroupName); + m_CurrencyGroupID = serverConfig.GetString("CurrencyGroupID", m_CurrencyGroupID); + + // [MoneyServer] Section + m_defaultBalance = m_server_config.GetInt("DefaultBalance", m_defaultBalance); + + m_forceTransfer = m_server_config.GetBoolean("EnableForceTransfer", m_forceTransfer); + + string banker = m_server_config.GetString("BankerAvatar", m_bankerAvatar); + m_bankerAvatar = banker.ToLower(); + + m_enableAmountZero = m_server_config.GetBoolean("EnableAmountZero", m_enableAmountZero); + m_scriptSendMoney = m_server_config.GetBoolean("EnableScriptSendMoney", m_scriptSendMoney); + m_scriptAccessKey = m_server_config.GetString("MoneyScriptAccessKey", m_scriptAccessKey); + m_scriptIPaddress = m_server_config.GetString("MoneyScriptIPaddress", m_scriptIPaddress); + + m_CalculateCurrency = m_server_config.GetInt("CalculateCurrency", m_CalculateCurrency); // New feature + m_DebugConsole = m_server_config.GetBoolean("DebugConsole", m_DebugConsole); // New feature + m_DebugFile = m_server_config.GetBoolean("m_DebugFile", m_DebugFile); // New feature + + m_TotalDay = m_server_config.GetInt("TotalDay", m_TotalDay); + m_TotalWeek = m_server_config.GetInt("TotalWeek", m_TotalWeek); + m_TotalMonth = m_server_config.GetInt("TotalMonth", m_TotalMonth); + m_CurrencyMaximum = m_server_config.GetInt("CurrencyMaximum", m_CurrencyMaximum); + + m_CurrencyOnOff = m_server_config.GetString("CurrencyOnOff", m_CurrencyOnOff); + m_CurrencyGroupOnly = m_server_config.GetBoolean("CurrencyGroupOnly", m_CurrencyGroupOnly); + m_UserMailLock = m_server_config.GetBoolean("UserMailLock", m_UserMailLock); + + m_CurrencyGroupName = m_server_config.GetString("CurrencyGroupName", m_CurrencyGroupName); + m_CurrencyGroupID = m_server_config.GetString("CurrencyGroupID", m_CurrencyGroupID); + + if (m_CurrencyMaximum <= 0) m_CurrencyMaximum = 1000; + + // Hyper Grid Avatar + m_hg_enable = m_server_config.GetBoolean("EnableHGAvatar", m_hg_enable); + m_gst_enable = m_server_config.GetBoolean("EnableGuestAvatar", m_gst_enable); + m_hg_defaultBalance = m_server_config.GetInt("HGAvatarDefaultBalance", m_hg_defaultBalance); + m_gst_defaultBalance = m_server_config.GetInt("GuestAvatarDefaultBalance", m_gst_defaultBalance); + + // Update Balance Messages + m_BalanceMessageLandSale = m_server_config.GetString("BalanceMessageLandSale", m_BalanceMessageLandSale); + m_BalanceMessageRcvLandSale = m_server_config.GetString("BalanceMessageRcvLandSale", m_BalanceMessageRcvLandSale); + m_BalanceMessageSendGift = m_server_config.GetString("BalanceMessageSendGift", m_BalanceMessageSendGift); + m_BalanceMessageReceiveGift = m_server_config.GetString("BalanceMessageReceiveGift", m_BalanceMessageReceiveGift); + m_BalanceMessagePayCharge = m_server_config.GetString("BalanceMessagePayCharge", m_BalanceMessagePayCharge); + m_BalanceMessageBuyObject = m_server_config.GetString("BalanceMessageBuyObject", m_BalanceMessageBuyObject); + m_BalanceMessageSellObject = m_server_config.GetString("BalanceMessageSellObject", m_BalanceMessageSellObject); + m_BalanceMessageGetMoney = m_server_config.GetString("BalanceMessageGetMoney", m_BalanceMessageGetMoney); + m_BalanceMessageBuyMoney = m_server_config.GetString("BalanceMessageBuyMoney", m_BalanceMessageBuyMoney); + m_BalanceMessageRollBack = m_server_config.GetString("BalanceMessageRollBack", m_BalanceMessageRollBack); + m_BalanceMessageSendMoney = m_server_config.GetString("BalanceMessageSendMoney", m_BalanceMessageSendMoney); + m_BalanceMessageReceiveMoney = m_server_config.GetString("BalanceMessageReceiveMoney", m_BalanceMessageReceiveMoney); + + // [Certificate] Section + + // XML RPC to Region Server (Client Mode) + // Client Certificate + m_certFilename = m_cert_config.GetString("ClientCertFilename", m_certFilename); + m_certPassword = m_cert_config.GetString("ClientCertPassword", m_certPassword); + if (m_certFilename != "") + { + m_certVerify.SetPrivateCert(m_certFilename, m_certPassword); + m_log.Info("[MONEY XMLRPC]: Initialise: Issue Authentication of Client. Cert file is " + m_certFilename); + } + + // Server Authentication + // CA : MoneyServer config for checking the server certificate of the web server for XMLRPC + m_checkServerCert = m_cert_config.GetBoolean("CheckServerCert", m_checkServerCert); + m_cacertFilename = m_cert_config.GetString("CACertFilename", m_cacertFilename); + + if (m_cacertFilename != "") + { + m_certVerify.SetPrivateCA(m_cacertFilename); + } + else + { + m_checkServerCert = false; + } + + if (m_checkServerCert) + { + m_log.Info("[MONEY XMLRPC]: Initialise: Execute Authentication of Server. CA file is " + m_cacertFilename); + } + else + { + m_log.Info("[MONEY XMLRPC]: CheckServerCert is false."); + } + + m_moneyDBService = moneyDBService; + m_config = config; + + // Rufe die RegisterConsoleCommands Methode auf + RegisterConsoleCommands(MainConsole.Instance); // Aufruf der Initialisierung der Konsolenbefehle + + m_sessionDic = m_moneyCore.GetSessionDic(); + m_secureSessionDic = m_moneyCore.GetSecureSessionDic(); + m_webSessionDic = m_moneyCore.GetWebSessionDic(); + RegisterHandlers(); + + RegisterStreamHandlers(); + } + + private void RegisterStreamHandlers() + { + m_log.Info("[MONEY XMLRPC]: Registering currency.php handlers."); + m_httpServer.AddSimpleStreamHandler(new CurrencyStreamHandler("/currency.php", CurrencyProcessPHP)); + + m_log.Info("[MONEY XMLRPC]: Registering landtool.php handlers."); + m_httpServer.AddSimpleStreamHandler(new LandtoolStreamHandler("/landtool.php", LandtoolProcessPHP)); + + m_log.InfoFormat("[MONEY MODULE]: Registered /currency.php and /landtool.php handlers on Port: {0}", m_httpServer.Port); + } + + /// Posts the initialise. + public void PostInitialise() + { + } + + private Dictionary m_rpcHandlers = new Dictionary(); + + + /// Registers the handlers. + public void RegisterHandlers() + { + m_httpServer = m_moneyCore.GetHttpServer(); + m_httpServer.AddXmlRPCHandler("ClientLogin", handleClientLogin); + m_httpServer.AddXmlRPCHandler("ClientLogout", handleClientLogout); + m_httpServer.AddXmlRPCHandler("GetBalance", handleGetBalance); + m_httpServer.AddXmlRPCHandler("GetTransaction", handleGetTransaction); + + m_httpServer.AddXmlRPCHandler("CancelTransfer", handleCancelTransfer); + + m_httpServer.AddXmlRPCHandler("TransferMoney", handleTransaction); + m_httpServer.AddXmlRPCHandler("ForceTransferMoney", handleForceTransaction); // added + m_httpServer.AddXmlRPCHandler("PayMoneyCharge", handlePayMoneyCharge); // added + m_httpServer.AddXmlRPCHandler("AddBankerMoney", handleAddBankerMoney); // added + + m_httpServer.AddXmlRPCHandler("SendMoney", handleScriptTransaction); + m_httpServer.AddXmlRPCHandler("MoveMoney", handleScriptTransaction); + + // this is from original DTL. not check yet. + m_httpServer.AddXmlRPCHandler("WebLogin", handleWebLogin); + m_httpServer.AddXmlRPCHandler("WebLogout", handleWebLogout); + m_httpServer.AddXmlRPCHandler("WebGetBalance", handleWebGetBalance); + m_httpServer.AddXmlRPCHandler("WebGetTransaction", handleWebGetTransaction); + m_httpServer.AddXmlRPCHandler("WebGetTransactionNum", handleWebGetTransactionNum); + + // Land Buy Test + m_httpServer.AddXmlRPCHandler("preflightBuyLandPrep", preflightBuyLandPrep); + m_httpServer.AddXmlRPCHandler("buyLandPrep", buyLandPrep); + + // Currency Buy Test + // getCurrencyQuote", quote_func + // buyCurrency", buy_func + m_httpServer.AddXmlRPCHandler("getCurrencyQuote", getCurrencyQuote); + m_httpServer.AddXmlRPCHandler("buyCurrency", buyCurrency); + + // Money Transfer Test + m_httpServer.AddXmlRPCHandler("OnMoneyTransfered", OnMoneyTransferedHandler); + m_httpServer.AddXmlRPCHandler("UpdateBalance", BalanceUpdateHandler); + m_httpServer.AddXmlRPCHandler("UserAlert", UserAlertHandler); + + // Angebot oder eine Information zu einem Kaufpreis + // m_httpServer.AddXmlRPCHandler("quote", getCurrencyQuote); + } + #endregion + // ################## Land Buy ################## + #region Land Buy + + // Flexibilität: Die gesamte Logik wird innerhalb von LandtoolProcessPHP und ihren Hilfsfunktionen abgewickelt. + // Fehlerbehandlung: Umfassende Überprüfung auf fehlende Daten oder Fehler während der Verarbeitung. + // Unabhängigkeit: Keine Abhängigkeit von externen Funktionen oder Modulen. + + private void LandtoolProcessPHP(IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) + { + m_log.InfoFormat("[MONEY XMLRPC MODULE]: LANDTOOL PROCESS PHP starting..."); + + if (httpRequest == null || httpResponse == null) + { + m_log.Error("[MONEY XMLRPC MODULE]: Invalid request or response object."); + return; + } + + try + { + // XML-String aus Anfrage lesen + string requestBody; + using (var reader = new StreamReader(httpRequest.InputStream, Encoding.UTF8)) + { + requestBody = reader.ReadToEnd(); + } + + // XML-Daten parsen + XmlDocument doc = new XmlDocument(); + doc.LoadXml(requestBody); + + // Methode extrahieren + XmlNode methodNameNode = doc.SelectSingleNode("/methodCall/methodName"); + if (methodNameNode == null) + { + throw new Exception("Missing method name in XML-RPC request."); + } + + string methodName = methodNameNode.InnerText; + XmlNodeList members = doc.SelectNodes("//param/value/struct/member"); + + // Variablen für Landanfrage initialisieren + string agentId = null, secureSessionId = null, language = null; + int billableArea = 0, currencyBuy = 0; + + // Werte aus der XML-Struktur extrahieren + foreach (XmlNode member in members) + { + string name = member.SelectSingleNode("name")?.InnerText; + string value = member.SelectSingleNode("value")?.InnerText; + + if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value)) continue; + + switch (name) + { + case "agentId": agentId = value; break; + case "billableArea": billableArea = int.Parse(value); break; + case "currencyBuy": currencyBuy = int.Parse(value); break; + case "language": language = value; break; + case "secureSessionId": secureSessionId = value; break; + } + } + m_log.InfoFormat("[MONEY XMLRPC MODULE]: agentId ", agentId); + m_log.InfoFormat("[MONEY XMLRPC MODULE]: billableArea", billableArea); + m_log.InfoFormat("[MONEY XMLRPC MODULE]: currencyBuy", currencyBuy); + m_log.InfoFormat("[MONEY XMLRPC MODULE]: language", language); + m_log.InfoFormat("[MONEY XMLRPC MODULE]: secureSessionId", secureSessionId); + + if (methodName == "preflightBuyLandPrep") + { + m_log.InfoFormat("[MONEY XMLRPC MODULE]: Processing Preflight Land Purchase Request for AgentId: {0}, BillableArea: {1}", + agentId, billableArea); + + // Preflight-Prüfung + Hashtable preflightResponse = PerformPreflightLandCheck(agentId, billableArea, currencyBuy, language, secureSessionId); + if (!(bool)preflightResponse["success"]) + { + m_log.Error("[MONEY XMLRPC MODULE]: Preflight check failed."); + httpResponse.StatusCode = 400; + httpResponse.RawBuffer = Encoding.UTF8.GetBytes("Preflight check failed"); + return; + } + + // Erfolgreiche Antwort zurückgeben + httpResponse.StatusCode = 200; + XmlRpcResponse xmlResponse = new XmlRpcResponse { Value = preflightResponse }; + httpResponse.RawBuffer = Encoding.UTF8.GetBytes(xmlResponse.ToString()); + } + else if (methodName == "buyLandPrep") + { + m_log.InfoFormat("[MONEY XMLRPC MODULE]: Processing Land Purchase Request for AgentId: {0}, BillableArea: {1}", + agentId, billableArea); + + // Landkauf durchführen + Hashtable purchaseResponse = ProcessLandPurchase(agentId, billableArea, currencyBuy, language, secureSessionId); + + // Überprüfung der Antwort + if (!(bool)purchaseResponse["success"]) + { + m_log.Error("[MONEY XMLRPC MODULE]: Land purchase failed."); + httpResponse.StatusCode = 400; + httpResponse.RawBuffer = Encoding.UTF8.GetBytes("Land purchase failed"); + return; + } + + // Erfolgreiche Antwort zurückgeben + httpResponse.StatusCode = 200; + XmlRpcResponse xmlResponse = new XmlRpcResponse { Value = purchaseResponse }; + httpResponse.RawBuffer = Encoding.UTF8.GetBytes(xmlResponse.ToString()); + } + else + { + m_log.ErrorFormat("[MONEY XMLRPC MODULE]: Unknown landtool method name: {0}", methodName); + httpResponse.StatusCode = 400; + httpResponse.RawBuffer = Encoding.UTF8.GetBytes("Invalid method name"); + } + } + catch (Exception ex) + { + m_log.ErrorFormat("[MONEY XMLRPC MODULE]: Error processing LANDTOOL request. Error: {0}", ex.ToString()); + httpResponse.StatusCode = 500; + httpResponse.RawBuffer = Encoding.UTF8.GetBytes("Error"); + } + } + + // Beispiel einer Funktion zur Preflight-Prüfung + // PerformPreflightLandCheck: + // Simuliert die Preflight-Prüfung für den Landkauf. + // Gibt eine Erfolgsmeldung in Form einer Hashtable zurück. + private Hashtable PerformPreflightLandCheck(string agentId, int billableArea, int currencyBuy, string language, string secureSessionId) + { + // Beispielhafte Logik für Preflight-Prüfung + m_log.InfoFormat("[MONEY XMLRPC MODULE]: Preflight check for AgentId: {0}, Area: {1}, CurrencyBuy: {2}", agentId, billableArea, currencyBuy); + + // Erfolg simulieren + return new Hashtable + { + { "success", true }, + { "agentId", agentId }, + { "billableArea", billableArea }, + { "currencyBuy", currencyBuy }, + { "message", "Preflight check passed" } + }; + } + + // Beispiel einer Funktion zur Bearbeitung eines Landkaufs + // ProcessLandPurchase: + // Simuliert die Durchführung des Landkaufs. + // Gibt ebenfalls eine Erfolgsmeldung als Hashtable zurück. + private Hashtable ProcessLandPurchase(string agentId, int billableArea, int currencyBuy, string language, string secureSessionId) + { + // Beispielhafte Logik für Landkauf + m_log.InfoFormat("[MONEY XMLRPC MODULE]: Processing land purchase for AgentId: {0}, Area: {1}, CurrencyBuy: {2}", agentId, billableArea, currencyBuy); + + // Erfolg simulieren + return new Hashtable + { + { "success", true }, + { "agentId", agentId }, + { "billableArea", billableArea }, + { "currencyBuy", currencyBuy }, + { "message", "Land purchase completed successfully" } + }; + } + private XmlRpcResponse preflightBuyLandPrep(XmlRpcRequest request, IPEndPoint client) + { + m_log.InfoFormat("[MONEY XMLRPC]: preflightBuyLandPrep starting..."); + + if (request == null) + { + m_log.Error("[MONEY XMLRPC]: preflightBuyLandPrep: request is null."); + return new XmlRpcResponse { Value = new Hashtable { { "success", false } } }; + } + + if (client == null) + { + m_log.Error("[MONEY XMLRPC]: preflightBuyLandPrep: client is null."); + return new XmlRpcResponse { Value = new Hashtable { { "success", false } } }; + } + + try + { + Hashtable requestData = (Hashtable)request.Params[0]; + if (requestData == null) + { + m_log.Error("[MONEY XMLRPC]: preflightBuyLandPrep: request data is null."); + return new XmlRpcResponse { Value = new Hashtable { { "success", false } } }; + } + + int billableArea = Convert.ToInt32(requestData["billableArea"]); + int currencyBuy = Convert.ToInt32(requestData["currencyBuy"]); + + // Log the received data for debugging + m_log.InfoFormat("[MONEY XMLRPC]: Received billableArea = {0}, currencyBuy = {1}", billableArea, currencyBuy); + + // Process preflight logic here + if (billableArea < 0 || currencyBuy < 0) + { + m_log.Error("[MONEY XMLRPC]: preflightBuyLandPrep: Invalid input values."); + return new XmlRpcResponse { Value = new Hashtable { { "success", false } } }; + } + + // Simulate sending response + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseValue = new Hashtable + { + { "success", true }, + { "billableArea", billableArea }, + { "currencyBuy", currencyBuy } + }; + response.Value = responseValue; + return response; + } + catch (Exception ex) + { + m_log.ErrorFormat("[MONEY XMLRPC]: preflightBuyLandPrep: {0}", ex.Message); + return new XmlRpcResponse { Value = new Hashtable { { "success", false } } }; + } + } + private XmlRpcResponse buyLandPrep(XmlRpcRequest request, IPEndPoint client) + { + m_log.InfoFormat("[MONEY XMLRPC]: buyLandPrep starting..."); + + if (request == null) + { + m_log.Error("[MONEY XMLRPC]: buyLandPrep: request is null."); + return new XmlRpcResponse { Value = new Hashtable { { "success", false } } }; + } + + if (client == null) + { + m_log.Error("[MONEY XMLRPC]: buyLandPrep: client is null."); + return new XmlRpcResponse { Value = new Hashtable { { "success", false } } }; + } + + try + { + Hashtable requestData = (Hashtable)request.Params[0]; + if (requestData == null) + { + m_log.Error("[MONEY XMLRPC]: buyLandPrep: request data is null."); + return new XmlRpcResponse { Value = new Hashtable { { "success", false } } }; + } + + string agentId = requestData["agentId"]?.ToString(); + string secureSessionId = requestData["secureSessionId"]?.ToString(); + string language = requestData["language"]?.ToString(); + int billableArea = Convert.ToInt32(requestData["billableArea"]); + int currencyBuy = Convert.ToInt32(requestData["currencyBuy"]); + + // Log the received data for debugging + m_log.InfoFormat("[MONEY XMLRPC]: Received agentId = {0}, secureSessionId = {1}, billableArea = {2}, currencyBuy = {3}", + agentId, secureSessionId, billableArea, currencyBuy); + + if (string.IsNullOrEmpty(agentId) || string.IsNullOrEmpty(secureSessionId)) + { + m_log.Error("[MONEY XMLRPC]: buyLandPrep: Missing required parameters."); + return new XmlRpcResponse { Value = new Hashtable { { "success", false } } }; + } + + // Process purchase logic here + bool purchaseSuccessful = ProcessLandPurchase(agentId, secureSessionId, billableArea, currencyBuy); + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseValue = new Hashtable + { + { "success", purchaseSuccessful } + }; + response.Value = responseValue; + return response; + } + catch (Exception ex) + { + m_log.ErrorFormat("[MONEY XMLRPC]: buyLandPrep: {0}", ex.Message); + return new XmlRpcResponse { Value = new Hashtable { { "success", false } } }; + } + } + + // Helper function to simulate land purchase logic + private bool ProcessLandPurchase(string agentId, string secureSessionId, int billableArea, int currencyBuy) + { + // Simulate some purchase validation + if (billableArea > 0 && currencyBuy >= billableArea * 10) // Example: each square meter costs 10 currency units + { + m_log.InfoFormat("[MONEY XMLRPC]: ProcessLandPurchase: Purchase successful for Agent {0}.", agentId); + return true; + } + + m_log.WarnFormat("[MONEY XMLRPC]: ProcessLandPurchase: Purchase failed for Agent {0}.", agentId); + return false; + } + #endregion + // ################## Currency Buy ################## + #region Currency Buy + + private void CurrencyProcessPHP(IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) + { + m_log.InfoFormat("[CURRENCY PROCESS PHP]: Currency Process Starting..."); + + if (httpRequest == null || httpResponse == null) + { + m_log.Error("[CURRENCY PROCESS PHP]: Invalid request or response object."); + httpResponse.StatusCode = 400; + httpResponse.RawBuffer = Encoding.UTF8.GetBytes("Invalid request"); + return; + } + + try + { + string requestBody; + using (var reader = new StreamReader(httpRequest.InputStream, Encoding.UTF8)) + { + requestBody = reader.ReadToEnd(); + } + + XmlDocument doc = new XmlDocument(); + doc.LoadXml(requestBody); + + XmlNode methodNameNode = doc.SelectSingleNode("/methodCall/methodName"); + if (methodNameNode == null) + { + throw new Exception("Missing method name in XML-RPC request."); + } + + string methodName = methodNameNode.InnerText; + + Hashtable parameters = ExtractXmlRpcParams(doc); + + string agentId = parameters["agentId"]?.ToString(); + string secureSessionId = parameters["secureSessionId"]?.ToString(); + int currencyBuy = int.Parse(parameters["currencyBuy"]?.ToString() ?? "0"); + + m_log.InfoFormat("[CURRENCY PROCESS PHP]: Parsed values - AgentId: {0}, CurrencyBuy: {1}, SecureSessionId: {2}", agentId, currencyBuy, secureSessionId); + + string transactionID = parameters["transactionID"]?.ToString(); + string userID = parameters["agentId"]?.ToString(); + int amount = int.Parse(parameters["currencyBuy"]?.ToString() ?? "0"); + + if (string.IsNullOrEmpty(transactionID)) + { + transactionID = secureSessionId; + } + + m_log.InfoFormat("[CURRENCY PROCESS PHP]: Parsed values - transactionID: {0}, userID: {1}, amount: {2}", transactionID, userID, amount); + + if (m_CurrencyGroupOnly && m_CurrencyGroupID != "00000000-0000-0000-0000-000000000000" && !IsUserInGroup(agentId, m_CurrencyGroupID)) + { + m_log.InfoFormat("[CURRENCY PROCESS PHP]: User {0} is not a member of the required group {1}.", agentId, m_CurrencyGroupID); + httpResponse.StatusCode = 403; + httpResponse.RawBuffer = Encoding.UTF8.GetBytes("User is not a member of the required group"); + return; + } + + if (m_UserMailLock && !UserMailLock(agentId)) + { + m_log.InfoFormat("[CURRENCY PROCESS PHP]: User {0} does not have a registered email address.", agentId); + httpResponse.StatusCode = 403; + httpResponse.RawBuffer = Encoding.UTF8.GetBytes("User does not have a registered email address"); + return; + } + + if (m_CurrencyOnOff != "off" && !CheckGroupMoney(agentId, m_CurrencyGroupID)) + { + m_log.Info("[CURRENCY PROCESS PHP]: Currency purchase is turned off for this user."); + httpResponse.StatusCode = 403; + httpResponse.RawBuffer = Encoding.UTF8.GetBytes("Currency purchase is turned off"); + return; + } + + m_log.InfoFormat("[CURRENCY PROCESS PHP]: Currency Maximum loaded: {0}", m_CurrencyMaximum); + + if (methodName == "getCurrencyQuote") + { + Hashtable quoteResponse = PerformGetCurrencyQuote(agentId, currencyBuy, secureSessionId); + + int excessAmount = CheckMaximumMoney(agentId, m_CurrencyMaximum); + if (excessAmount > 0) + { + quoteResponse["message"] = $"Your balance was reduced by {excessAmount} to enforce the maximum limit."; + } + + XmlRpcResponse xmlResponse = new XmlRpcResponse { Value = quoteResponse }; + httpResponse.StatusCode = 200; + httpResponse.RawBuffer = Encoding.UTF8.GetBytes(xmlResponse.ToString()); + } + else if (methodName == "buyCurrency") + { + Hashtable purchaseResponse = PerformBuyCurrency(agentId, currencyBuy, secureSessionId); + + if ((bool)purchaseResponse["success"]) + { + m_log.Info("[CURRENCY PROCESS PHP]: Purchase successful. Proceeding to credit currency."); + PerformMoneyTransfer("BANKER", agentId, currencyBuy); + UpdateBalance(agentId, "Currency purchase successful."); + + CheckMaximumMoney(agentId, m_CurrencyMaximum); + + XmlRpcResponse xmlResponse = new XmlRpcResponse { Value = purchaseResponse }; + httpResponse.StatusCode = 200; + httpResponse.RawBuffer = Encoding.UTF8.GetBytes(xmlResponse.ToString()); + } + else + { + m_log.Error("[CURRENCY PROCESS PHP]: Currency purchase failed."); + httpResponse.StatusCode = 400; + httpResponse.RawBuffer = Encoding.UTF8.GetBytes("Currency purchase failed"); + } + } + else + { + m_log.ErrorFormat("[CURRENCY PROCESS PHP]: Unknown method name: {0}", methodName); + httpResponse.StatusCode = 400; + httpResponse.RawBuffer = Encoding.UTF8.GetBytes("Invalid method name"); + } + } + catch (Exception ex) + { + m_log.ErrorFormat("[CURRENCY PROCESS PHP]: Error processing request. Error: {0}", ex.ToString()); + httpResponse.StatusCode = 500; + httpResponse.RawBuffer = Encoding.UTF8.GetBytes("Error"); + } + } + + + private bool UserMailLock(string userID) + { + m_log.InfoFormat("[USER MAIL LOCK]: Checking if user {0} has a registered email address", userID); + + MySQLSuperManager dbm = GetLockedConnection(); + + try + { + // SQL-Abfrage zum Überprüfen, ob der Benutzer eine hinterlegte E-Mail-Adresse hat + string sql = "SELECT email FROM `UserAccounts` WHERE PrincipalID = ?userID"; + string email = null; + + using (MySqlCommand cmd = new MySqlCommand(sql, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?userID", userID); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + email = reader.GetString("email"); + m_log.InfoFormat("[USER MAIL LOCK]: User {0} email address is {1}", userID, email); + } + } + } + + // Überprüfen, ob die E-Mail-Adresse leer ist oder null + if (string.IsNullOrEmpty(email)) + { + m_log.InfoFormat("[USER MAIL LOCK]: User {0} does not have a registered email address", userID); + return false; // Benutzer hat keine hinterlegte E-Mail-Adresse + } + + m_log.InfoFormat("[USER MAIL LOCK]: User {0} has a registered email address", userID); + return true; // Benutzer hat eine hinterlegte E-Mail-Adresse + } + catch (Exception ex) + { + m_log.ErrorFormat("[USER MAIL LOCK]: Error checking user email: {0}", ex.Message); + return false; + } + finally + { + dbm.Release(); + } + } + + private bool CheckGroupMoney(string agentId, string groupId) + { + m_log.InfoFormat("[CHECK GROUP MONEY]: Checking group membership for agentId: {0}, groupId: {1}", agentId, groupId); + + if (string.IsNullOrEmpty(groupId) || groupId == "00000000-0000-0000-0000-000000000000") + { + m_log.Info("[CHECK GROUP MONEY]: groupId is empty or set to accept all groups, returning true"); + return true; + } + + MySQLSuperManager dbm = GetLockedConnection(); + + try + { + string sql = "SELECT COUNT(*) FROM os_groups_membership WHERE PrincipalID = ?agentId AND GroupID = ?groupId"; + using (MySqlCommand cmd = new MySqlCommand(sql, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?agentId", agentId); + cmd.Parameters.AddWithValue("?groupId", groupId); + + int count = Convert.ToInt32(cmd.ExecuteScalar()); + m_log.InfoFormat("[CHECK GROUP MONEY]: Query result: count={0}", count); + return count > 0; + } + } + catch (Exception ex) + { + m_log.ErrorFormat("[CHECK GROUP MONEY]: Error checking group membership: {0}", ex.Message); + return false; + } + finally + { + dbm.Release(); + } + } + + + // Überprüfen, ob der Benutzer Mitglied der angegebenen Gruppe ist + private bool IsUserInGroup(string agentId, string groupId) + { + // IClientAPI + m_log.DebugFormat("[IsUserInGroup]: Checking group membership for agentId={0}, groupId={1}", agentId, groupId); + + if (string.IsNullOrEmpty(groupId)) + { + m_log.Debug("[IsUserInGroup]: groupId is empty, returning true"); + return true; // Keine Einschränkung, wenn groupId nicht gesetzt ist + } + + MySQLSuperManager dbm = GetLockedConnection(); + + try + { + // SQL-Abfrage zum Überprüfen der Gruppenmitgliedschaft + string sql = "SELECT COUNT(*) FROM os_groups_membership WHERE PrincipalID = ?agentId AND GroupID = ?groupId"; + using (MySqlCommand cmd = new MySqlCommand(sql, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?agentId", agentId); + cmd.Parameters.AddWithValue("?groupId", groupId); + + int count = Convert.ToInt32(cmd.ExecuteScalar()); + m_log.DebugFormat("[IsUserInGroup]: Query result: count={0}", count); + return count > 0; + } + } + catch (Exception ex) + { + m_log.ErrorFormat("[IsUserInGroup]: Error checking group membership: {0}", ex.Message); + return false; + } + finally + { + dbm.Release(); + } + } + + public new int CheckMaximumMoney(string userID, int m_CurrencyMaximum) + { + MySQLSuperManager dbm = GetLockedConnection(); + m_log.InfoFormat("[CHECK MAXIMUM MONEY]: Checking for userID: {0}, Currency Maximum: {1}", userID, m_CurrencyMaximum); + + try + { + // Ausnahmen für SYSTEM und BANKER + if (userID == "SYSTEM" || userID == "BANKER" || userID == m_bankerAvatar) + { + m_log.InfoFormat("[CHECK MAXIMUM MONEY]: User {0} is SYSTEM or BANKER, skipping check.", userID); + return 0; // Keine Begrenzung für diese Benutzer + } + + // Abrufen des aktuellen Guthabens des Benutzers + string sql = "SELECT balance FROM balances WHERE user = ?userID"; + int currentBalance = 0; + + using (MySqlCommand cmd = new MySqlCommand(sql, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?userID", userID); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + currentBalance = reader.GetInt32("balance"); + m_log.InfoFormat("[CHECK MAXIMUM MONEY]: Current balance for user {0} is {1}", userID, currentBalance); + } + } + } + + // Überprüfen, ob das Guthaben über dem Maximum liegt und ggf. abziehen + if (currentBalance > m_CurrencyMaximum) + { + int excessAmount = currentBalance - m_CurrencyMaximum; + + // Guthaben auf das Maximum reduzieren + sql = "UPDATE balances SET balance = ?newBalance WHERE user = ?userID"; + using (MySqlCommand cmd = new MySqlCommand(sql, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?newBalance", m_CurrencyMaximum); + cmd.Parameters.AddWithValue("?userID", userID); + cmd.ExecuteNonQuery(); + } + + m_log.InfoFormat("[CHECK MAXIMUM MONEY]: Reduced balance for user {0} by {1} to enforce maximum limit of {2}", userID, excessAmount, m_CurrencyMaximum); + return excessAmount; // Rückgabe des abgezogenen Betrags + } + + m_log.InfoFormat("[CHECK MAXIMUM MONEY]: No adjustment needed for user {0}", userID); + return 0; // Keine Änderung, falls das Guthaben innerhalb des Limits liegt + } + catch (Exception ex) + { + m_log.ErrorFormat("[CHECK MAXIMUM MONEY]: Error checking and updating user balance: {0}", ex.Message); + throw; + } + finally + { + dbm.Release(); + } + } + + private Hashtable ExtractXmlRpcParams(XmlDocument doc) + { + Hashtable parameters = new Hashtable(); + XmlNodeList members = doc.SelectNodes("//param/value/struct/member"); + + foreach (XmlNode member in members) + { + string name = member.SelectSingleNode("name")?.InnerText; + string value = member.SelectSingleNode("value")?.InnerText; + + if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(value)) + { + parameters[name] = value; + } + } + return parameters; + } + + private Hashtable PerformGetCurrencyQuote(string agentId, int currencyBuy, string secureSessionId) + { + m_log.InfoFormat("[PERFORM GET CURRENCY QUOTE]: Generating currency quote for AgentId: {0}", agentId); + + int rate = 100; // 1€ = 100L$ + return new Hashtable + { + { "success", true }, + { "currency", new Hashtable + { + { "estimatedCost", currencyBuy / rate }, // Kosten in € + { "currencyBuy", currencyBuy } // Angeforderte Spielwährung + } + }, + { "confirm", Guid.NewGuid().ToString() } + }; + } + + private Hashtable PerformBuyCurrency(string agentId, int currencyBuy, string secureSessionId) + { + if (string.IsNullOrEmpty(agentId)) + { + m_log.Error("[PERFORM BUY CURRENCY]: AgentId is null or empty."); + return new Hashtable + { + { "success", false }, + { "message", "AgentId is required." } + }; + } + + if (currencyBuy <= 0) + { + m_log.Error("[PERFORM BUY CURRENCY]: Invalid currencyBuy amount."); + return new Hashtable + { + { "success", false }, + { "message", "Invalid currencyBuy amount." } + }; + } + + m_log.InfoFormat("[PERFORM BUY CURRENCY]: Processing currency purchase for AgentId: {0}, Amount: {1}", agentId, currencyBuy); + return new Hashtable + { + { "success", true }, + { "message", $"Successfully purchased {currencyBuy}L$ for {agentId}" } + }; + } + public XmlRpcResponse getCurrencyQuote(XmlRpcRequest request, IPEndPoint remoteClient) + { + int amount = 0; + + // Protokolliere die eingehende XML-Anfrage + m_log.InfoFormat("[GET CURRENCY QUOTE]: Incoming XML Request: {0}", ToXmlString((Hashtable)request.Params[0])); + + try + { + Hashtable requestData = (Hashtable)request.Params[0]; + amount = (int)requestData["currencyBuy"]; + } + catch (Exception ex) + { + m_log.ErrorFormat("[GET CURRENCY QUOTE]: Error parsing request: {0}", ex.Message); + } + + // Berechnung und Antwortvorbereitung + Hashtable currencyResponse = new Hashtable + { + { "estimatedCost", amount * 0.01 }, // Berechnung für Kosten + { "currencyBuy", amount } + }; + + Hashtable quoteResponse = new Hashtable + { + { "success", true }, + { "currency", currencyResponse }, + { "confirm", Guid.NewGuid().ToString() } + }; + + // Protokolliere die Antwort, bevor sie zurückgegeben wird + m_log.InfoFormat("[GET CURRENCY QUOTE]: XML Response: {0}", ToXmlString(quoteResponse)); + + // Erstelle die Antwort + XmlRpcResponse returnval = new XmlRpcResponse { Value = quoteResponse }; + + // Füge ein weiteres Log hinzu, um sicherzustellen, dass die Antwort korrekt erstellt wurde + m_log.InfoFormat("[GET CURRENCY QUOTE]: Returning response for getCurrencyQuote: {0}", ToXmlString((Hashtable)returnval.Value)); + + return returnval; + } + public XmlRpcResponse buyCurrency(XmlRpcRequest request, IPEndPoint remoteClient) + { + Hashtable requestData = (Hashtable)request.Params[0]; + string agentId = requestData["agentId"]?.ToString(); + int amount = (int)requestData["currencyBuy"]; + + // Protokolliere die eingehende XML-Anfrage + m_log.InfoFormat("[BUY CURRENCY]: Incoming XML Request: {0}", ToXmlString((Hashtable)request.Params[0])); + + // Verarbeite den Kauf und logge die Details + m_log.InfoFormat("[BUY CURRENCY]: Processing currency purchase for AgentId: {0}, Amount: {1}, Banker: {2}", agentId, amount, m_bankerAvatar); + + // Hier erfolgt der Transfer an den Money Banker (BankerAvatar als Sender) + string senderID = m_bankerAvatar; // Der Sender ist der BankerAvatar + string receiverID = agentId; // Der Empfänger ist der Agent + UUID transactionUUID = UUID.Random(); // Eine eindeutige Transaktions-ID für die Transaktion + + Hashtable responseData = new Hashtable(); + responseData["success"] = false; + + // Versuche, die Transaktion auszuführen + try + { + // Logge die Übertragung + m_log.InfoFormat("[MONEY XMLRPC]: handlePayMoneyCharge: Transferring money from {0} to {1}, Amount = {2}", senderID, receiverID, amount); + + // Führe die tatsächliche Transaktion durch, indem die handlePayMoneyCharge-Methode aufgerufen wird + XmlRpcResponse transferResponse = handlePayMoneyCharge(request, remoteClient); // Übergibt die Anfrage an die tatsächliche Überweisungsmethode + + // Überprüfe, ob die Antwort erfolgreich war + if (transferResponse != null && transferResponse.Value is Hashtable transferResult && + transferResult.ContainsKey("success") && (bool)transferResult["success"]) + { + // Wenn die Transaktion erfolgreich war, setze die Antwortdaten + responseData["success"] = true; + responseData["message"] = $"Successfully purchased {amount} currency for AgentId {agentId}"; + } + else + { + // Fehler bei der Überweisung + responseData["message"] = "Currency purchase failed during money transfer."; + } + } + catch (Exception ex) + { + // Fehlerbehandlung + m_log.Error($"[BUY CURRENCY]: Error processing currency purchase: {ex.Message}"); + responseData["message"] = "Currency purchase failed."; + } + + // Erstelle die Antwort + XmlRpcResponse returnval = new XmlRpcResponse { Value = responseData }; + + // Protokolliere die XML-Antwort + m_log.InfoFormat("[BUY CURRENCY]: XML Response: {0}", ToXmlString(responseData)); + + return returnval; + } + public new bool PerformMoneyTransfer(string senderID, string receiverID, int amount) + { + //m_log.InfoFormat("[MONEY TRANSFER]: Transferring {0} from {1} to {2}.", amount, senderID, receiverID); + try + { + MySQLSuperManager dbm = GetLockedConnection(); + string sql = "UPDATE balances SET balance = balance - ?amount WHERE user = ?senderID; " + + "UPDATE balances SET balance = balance + ?amount WHERE user = ?receiverID"; + + using (MySqlCommand cmd = new MySqlCommand(sql, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?amount", amount); + cmd.Parameters.AddWithValue("?senderID", senderID); + cmd.Parameters.AddWithValue("?receiverID", receiverID); + + int rowsAffected = cmd.ExecuteNonQuery(); + bool result = (rowsAffected > 0); + + if (result) + { + LogTransaction(UUID.Random(), senderID, -amount); + LogTransaction(UUID.Random(), receiverID, amount); + } + + return result; + } + } + catch (Exception) + { + //m_log.ErrorFormat("[MONEY TRANSFER]: Error transferring money: {0}", ex.Message); + return false; + } + } + + public new void InitializeUserCurrency(string agentId) + { + m_log.InfoFormat("[INITIALIZE USER CURRENCY]: Initializing currency for new user: {0}", agentId); + + try + { + MySQLSuperManager dbm = GetLockedConnection(); + string sql = "INSERT INTO balances (user, balance) VALUES (?agentId, ?realMoney), (?agentId, ?gameMoney)"; + + using (MySqlCommand cmd = new MySqlCommand(sql, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?agentId", agentId); + cmd.Parameters.AddWithValue("?realMoney", m_realMoney); + cmd.Parameters.AddWithValue("?gameMoney", m_gameMoney); + + cmd.ExecuteNonQuery(); + } + + m_log.InfoFormat("[INITIALIZE USER CURRENCY]: User {0} initialized with {1}€ and {2}L$.", agentId, m_realMoney, m_gameMoney); + } + catch (Exception ex) + { + m_log.ErrorFormat("[INITIALIZE USER CURRENCY]: Error initializing user currency: {0}", ex.Message); + } + } + + public new Hashtable ApplyFallbackCredit(string agentId) + { + m_log.WarnFormat("[FALLBACK CREDIT]: Applying fallback credit for user {0}", agentId); + + try + { + MySQLSuperManager dbm = GetLockedConnection(); + string sql = "UPDATE balances SET balance = balance + 100 WHERE user = ?agentId"; + + using (MySqlCommand cmd = new MySqlCommand(sql, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?agentId", agentId); + cmd.ExecuteNonQuery(); + } + } + catch (Exception ex) + { + m_log.ErrorFormat("[FALLBACK CREDIT]: Error applying fallback credit: {0}", ex.Message); + } + + return new Hashtable + { + { "success", true }, + { "creditedAmount", 100 }, + { "message", "Fallback credit applied due to transaction failure." } + }; + } + + public bool ValidateTransaction(UUID transactionID, string secureCode) + { + // Die Methode ValidateTransfer aus IMoneyDBService verwenden + bool isValid = ValidateTransfer(secureCode, transactionID); + if (!isValid) + { + m_log.ErrorFormat("[MONEY SERVER]: Transaction validation failed for TransactionID: {0}", transactionID); + return false; + } + return true; + } + public bool AddMoney(UUID transactionUUID) + { + // Geld zu einer Transaktion hinzufügen + bool success = DoAddMoney(transactionUUID); + if (!success) + { + m_log.ErrorFormat("[MONEY SERVER]: Failed to add money for TransactionUUID: {0}", transactionUUID); + return false; + } + return true; + } + public bool WithdrawMoney(UUID transactionID, string senderID, int amount) + { + bool success = withdrawMoney(transactionID, senderID, amount); + if (!success) + { + m_log.ErrorFormat("[MONEY SERVER]: Failed to withdraw {0} for sender {1} in TransactionID: {2}", amount, senderID, transactionID); + return false; + } + return true; + } + public bool GiveMoney(UUID transactionID, string receiverID, int amount) + { + bool success = giveMoney(transactionID, receiverID, amount); + if (!success) + { + m_log.ErrorFormat("[MONEY SERVER]: Failed to give {0} to receiver {1} in TransactionID: {2}", amount, receiverID, transactionID); + return false; + } + return true; + } + public bool UpdateTransactionStatus(UUID transactionID, int status, string description) + { + bool success = updateTransactionStatus(transactionID, status, description); + if (!success) + { + m_log.ErrorFormat("[MONEY SERVER]: Failed to update status for TransactionID: {0} with Status: {1}", transactionID, status); + return false; + } + return true; + } + public bool AddUser(string userID, int balance, int status, int type) + { + bool success = addUser(userID, balance, status, type); + if (!success) + { + m_log.ErrorFormat("[MONEY SERVER]: Failed to add user {0} with balance {1}", userID, balance); + return false; + } + return true; + } + public new IEnumerable GetTransactionHistory(string userID, int startTime, int endTime) + { + return GetTransactionHistory(userID, startTime, endTime); + } + public new UserInfo FetchUserInfo(string userID) + { + return FetchUserInfo(userID); + } + public new bool UserExists(string userID) + { + return UserExists(userID); + } + public bool PerformTransaction(UUID transactionUUID) + { + bool success = DoTransfer(transactionUUID); + if (!success) + { + m_log.ErrorFormat("[MONEY SERVER]: Failed to perform transaction for TransactionUUID: {0}", transactionUUID); + return false; + } + return true; + } + public static bool IsValidEmail(string email) + { + if (string.IsNullOrWhiteSpace(email)) + { + return false; + } + + // Regular Expression für gültige E-Mail-Adressen + const string pattern = @"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"; + + // Überprüfen, ob die E-Mail-Adresse das Muster erfüllt + return Regex.IsMatch(email, pattern, RegexOptions.Compiled); + } + + private Dictionary balances = new Dictionary(); + + public int GetBalance(UUID uuid) + { + if (balances.TryGetValue(uuid, out int balance)) + { + return balance; + } + else + { + // Return a default value if the UUID is not found + return 0; + } + } + + public void SetBalance(UUID uuid, int balance) + { + balances[uuid] = balance; + } + + #endregion + // ################## XMLRPC Pasing ################## + #region XMLRPC Pasing + + + public static string ToXmlStringMini(Hashtable data) + { + XmlDocument doc = new XmlDocument(); + XmlElement root = doc.CreateElement("methodResponse"); + doc.AppendChild(root); + + XmlElement paramsElement = doc.CreateElement("params"); + root.AppendChild(paramsElement); + + foreach (DictionaryEntry entry in data) + { + XmlElement param = doc.CreateElement("param"); + XmlElement value = doc.CreateElement("value"); + value.InnerText = entry.Value.ToString(); + param.AppendChild(value); + paramsElement.AppendChild(param); + } + + return doc.OuterXml; + } + private string ToXmlString(Hashtable data) + { + XmlDocument doc = new XmlDocument(); + XmlNode methodCallNode = doc.CreateElement("methodCall"); + + XmlNode methodNameNode = doc.CreateElement("methodName"); + methodNameNode.InnerText = "response"; + methodCallNode.AppendChild(methodNameNode); + + XmlNode paramsNode = doc.CreateElement("params"); + foreach (DictionaryEntry entry in data) + { + XmlNode paramNode = doc.CreateElement("param"); + + XmlNode valueNode = doc.CreateElement("value"); + if (entry.Value is Hashtable) + { + XmlNode structNode = doc.CreateElement("struct"); + foreach (DictionaryEntry structEntry in (Hashtable)entry.Value) + { + XmlNode memberNode = doc.CreateElement("member"); + XmlNode nameNode = doc.CreateElement("name"); + nameNode.InnerText = structEntry.Key.ToString(); + XmlNode valueNode2 = doc.CreateElement("value"); + valueNode2.InnerText = structEntry.Value.ToString(); + memberNode.AppendChild(nameNode); + memberNode.AppendChild(valueNode2); + structNode.AppendChild(memberNode); + } + valueNode.AppendChild(structNode); + } + else + { + valueNode.InnerText = entry.Value.ToString(); + } + + paramNode.AppendChild(valueNode); + paramsNode.AppendChild(paramNode); + } + + methodCallNode.AppendChild(paramsNode); + doc.AppendChild(methodCallNode); + return doc.OuterXml; + } + + // Methode zur Verarbeitung und Parsing der XML-RPC-Anfrage + private object ParseXmlRpcRequest(string xml) + { + XmlDocument doc = new XmlDocument(); + doc.LoadXml(xml); + + XmlNode methodCallNode = doc.SelectSingleNode("/methodCall"); + XmlNode methodNameNode = methodCallNode.SelectSingleNode("methodName"); + + if (methodNameNode == null) + throw new Exception("Missing method name"); + + string methodName = methodNameNode.InnerText; + XmlNodeList members = methodCallNode.SelectNodes("//param/value/struct/member"); + + if (methodName == "getCurrencyQuote") + { + CurrencyQuoteRequest request = new CurrencyQuoteRequest(); + foreach (XmlNode member in members) + { + string name = member.SelectSingleNode("name").InnerText; + string value = member.SelectSingleNode("value").InnerText; + + switch (name) + { + case "agentId": request.AgentId = value; break; + case "currencyBuy": request.CurrencyBuy = int.Parse(value); break; + case "language": request.Language = value; break; + case "secureSessionId": request.SecureSessionId = value; break; + case "viewerBuildVersion": request.ViewerBuildVersion = value; break; + case "viewerChannel": request.ViewerChannel = value; break; + case "viewerMajorVersion": request.ViewerMajorVersion = int.Parse(value); break; + case "viewerMinorVersion": request.ViewerMinorVersion = int.Parse(value); break; + case "viewerPatchVersion": request.ViewerPatchVersion = int.Parse(value); break; + } + } + m_log.InfoFormat("[MONEY XML RPC MODULE]: Processed Currency Quote Request for AgentId: {0}", request.AgentId); + return request; + + } + else if (methodName == "preflightBuyLandPrep") + { + LandPurchaseRequest request = new LandPurchaseRequest(); + foreach (XmlNode member in members) + { + string name = member.SelectSingleNode("name").InnerText; + string value = member.SelectSingleNode("value").InnerText; + + switch (name) + { + case "agentId": request.AgentId = value; break; + case "billableArea": request.BillableArea = int.Parse(value); break; + case "currencyBuy": request.CurrencyBuy = int.Parse(value); break; + case "language": request.Language = value; break; + case "secureSessionId": request.SecureSessionId = value; break; + } + } + m_log.InfoFormat("[MONEY XML RPC MODULE]: Processed Land Purchase Request for AgentId: {0}, BillableArea: {1}", request.AgentId, request.BillableArea); + return request; + } + m_log.ErrorFormat("[MONEY XML RPC MODULE]: Unknown method name: {0}", methodName); + throw new Exception("Unknown method name: " + methodName); + } + + + private void LogXmlRpcRequestFile(IOSHttpRequest request) + { + try + { + // Erstelle einen Dateipfad für das Log + string logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "xmlrpc_debug.log"); + + // Lies den Request-Body + string requestBody; + using (var reader = new StreamReader(request.InputStream, Encoding.UTF8)) + { + requestBody = reader.ReadToEnd(); + } + + // Bereite den Logeintrag vor + string logEntry = $"{DateTime.UtcNow}: {request.RawUrl}\n{requestBody}\n\n"; + + // Schreibe den Logeintrag in die Datei + File.AppendAllText(logFilePath, logEntry); + } + catch (Exception ex) + { + m_log.ErrorFormat("[MONEY XML RPC MODULE DEBUG]: Error logging XML-RPC request: {0}", ex.Message); + } + } + + private void LogXmlRpcRequestConsole(IOSHttpRequest request) + { + m_log.InfoFormat("[MONEY XML RPC MODULE]: {0}", new StreamReader(request.InputStream).ReadToEnd()); // TODO: test + + try + { + // Lies den Request-Body + string requestBody; + using (var reader = new StreamReader(request.InputStream, Encoding.UTF8)) + { + requestBody = reader.ReadToEnd(); + } + + // Bereite den Logeintrag vor + string logEntry = $"{DateTime.UtcNow}: {request.RawUrl}\n{requestBody}\n\n"; + + // Schreibe den Logeintrag in das Log + m_log.Info(logEntry); + } + catch (Exception ex) + { + m_log.ErrorFormat("[MONEY XML RPC MODULE DEBUG]: Error logging XML-RPC request: {0}", ex.Message); + } + } + #endregion + // ################## handler ################## + #region handler + + // Spezifische Handler BalanceUpdateHandler: Verarbeitet Updates zum Kontostand. + public XmlRpcResponse BalanceUpdateHandler(XmlRpcRequest request, IPEndPoint client) + { + if (request == null) + { + m_log.Error("[MONEY XMLRPC]: BalanceUpdateHandler: request is null."); + return new XmlRpcResponse(); + } + + if (client == null) + { + m_log.Error("[MONEY XMLRPC]: BalanceUpdateHandler: client is null."); + return new XmlRpcResponse(); + } + + try + { + Hashtable requestData = (Hashtable)request.Params[0]; + string balanceUpdateData = (string)requestData["balanceUpdateData"]; + + // Process the balance update data + m_log.InfoFormat("[MONEY XMLRPC]: BalanceUpdateHandler: Updating balance for user {0}", balanceUpdateData); + + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + responseData.Add("success", true); + response.Value = responseData; + return response; + } + catch (Exception ex) + { + m_log.ErrorFormat("[MONEY XMLRPC]: BalanceUpdateHandler: Exception occurred: {0}", ex.Message); + return new XmlRpcResponse(); + } + } + + // Spezifische Handler UserAlertHandler: Handhabt Benutzerbenachrichtigungen. + public XmlRpcResponse UserAlertHandler(XmlRpcRequest request, IPEndPoint client) + { + try + { + Hashtable requestData = (Hashtable)request.Params[0]; + string alertMessage = (string)requestData["alertMessage"]; + + // Process the alert message + m_log.InfoFormat("[MONEY XMLRPC]: UserAlertHandler: Alert message received: {0}", alertMessage); + + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + responseData.Add("success", true); + response.Value = responseData; + return response; + } + catch (Exception ex) + { + m_log.ErrorFormat("[MONEY XMLRPC]: UserAlertHandler: Exception occurred: {0}", ex.Message); + return new XmlRpcResponse(); + } + } + + public XmlRpcResponse handleClientLogin(XmlRpcRequest request, IPEndPoint remoteClient) + { + m_log.InfoFormat("[MONEY XMLRPC]: handleClientLogin: Start."); + + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + response.Value = responseData; + + responseData["success"] = false; + responseData["clientBalance"] = 0; + + // Check Client Cert + if (m_moneyCore.IsCheckClientCert()) + { + string commonName = GetSSLCommonName(); + if (commonName == "") + { + m_log.ErrorFormat("[MONEY XMLRPC]: handleClientLogin: Warnning: Check Client Cert is set, but SSL Common Name is empty."); + responseData["success"] = false; + responseData["description"] = "SSL Common Name is empty"; + return response; + } + else + { + m_log.InfoFormat("[MONEY XMLRPC]: handleClientLogin: SSL Common Name is \"{0}\"", commonName); + } + + } + + string universalID = string.Empty; + string clientUUID = string.Empty; + string sessionID = string.Empty; + string secureID = string.Empty; + string simIP = string.Empty; + string userName = string.Empty; + int balance = 0; + int avatarType = (int)AvatarType.UNKNOWN_AVATAR; + int avatarClass = (int)AvatarType.UNKNOWN_AVATAR; + + if (requestData.ContainsKey("clientUUID")) clientUUID = (string)requestData["clientUUID"]; + if (requestData.ContainsKey("clientSessionID")) sessionID = (string)requestData["clientSessionID"]; + if (requestData.ContainsKey("clientSecureSessionID")) secureID = (string)requestData["clientSecureSessionID"]; + if (requestData.ContainsKey("universalID")) universalID = (string)requestData["universalID"]; + if (requestData.ContainsKey("userName")) userName = (string)requestData["userName"]; + if (requestData.ContainsKey("openSimServIP")) simIP = (string)requestData["openSimServIP"]; + if (requestData.ContainsKey("avatarType")) avatarType = Convert.ToInt32(requestData["avatarType"]); + if (requestData.ContainsKey("avatarClass")) avatarClass = Convert.ToInt32(requestData["avatarClass"]); + + string firstName = string.Empty; + string lastName = string.Empty; + string serverURL = string.Empty; + string securePsw = string.Empty; + + if (!String.IsNullOrEmpty(universalID)) + { + UUID uuid; + Util.ParseUniversalUserIdentifier(universalID, out uuid, out serverURL, out firstName, out lastName, out securePsw); + } + if (String.IsNullOrEmpty(userName)) + { + userName = firstName + " " + lastName; + } + + // Information from DB + UserInfo userInfo = m_moneyDBService.FetchUserInfo(clientUUID); + if (userInfo != null) + { + avatarType = userInfo.Type; // Avatar Type is not updated + if (avatarType == (int)AvatarType.LOCAL_AVATAR) avatarClass = (int)AvatarType.LOCAL_AVATAR; + if (avatarClass == (int)AvatarType.UNKNOWN_AVATAR) avatarClass = userInfo.Class; + if (String.IsNullOrEmpty(userName)) userName = userInfo.Avatar; + } + + if (avatarType == (int)AvatarType.UNKNOWN_AVATAR) avatarType = avatarClass; + if (String.IsNullOrEmpty(serverURL)) avatarClass = (int)AvatarType.NPC_AVATAR; + + m_log.InfoFormat("[MONEY XMLRPC]: handleClientLogon: Avatar {0} ({1}) is logged on.", userName, clientUUID); + m_log.InfoFormat("[MONEY XMLRPC]: handleClientLogon: Avatar Type is {0} and Avatar Class is {1}", avatarType, avatarClass); + + // Check Avatar + if (avatarClass == (int)AvatarType.GUEST_AVATAR && !m_gst_enable) + { + responseData["description"] = "Avatar is a Guest avatar. But this Money Server does not support Guest avatars."; + m_log.InfoFormat("[MONEY XMLRPC]: handleClientLogon: {0}", responseData["description"]); + return response; + } + else if (avatarClass == (int)AvatarType.HG_AVATAR && !m_hg_enable) + { + responseData["description"] = "Avatar is a HG avatar. But this Money Server does not support HG avatars."; + m_log.InfoFormat("[MONEY XMLRPC]: handleClientLogon: {0}", responseData["description"]); + return response; + } + else if (avatarClass == (int)AvatarType.FOREIGN_AVATAR) + { + responseData["description"] = "Avatar is a Foreign avatar."; + m_log.InfoFormat("[MONEY XMLRPC]: handleClientLogon: {0}", responseData["description"]); + return response; + } + else if (avatarClass == (int)AvatarType.UNKNOWN_AVATAR) + { + responseData["description"] = "Avatar is a Unknown avatar."; + m_log.InfoFormat("[MONEY XMLRPC]: handleClientLogon: {0}", responseData["description"]); + return response; + } + // NPC + else if (avatarClass == (int)AvatarType.NPC_AVATAR) + { + responseData["success"] = true; + responseData["clientBalance"] = 0; + responseData["description"] = "Avatar is a NPC."; + m_log.InfoFormat("[MONEY XMLRPC]: handleClientLogon: {0}", responseData["description"]); + return response; + } + + //Update the session and secure session dictionary + lock (m_sessionDic) + { + if (!m_sessionDic.ContainsKey(clientUUID)) + { + m_sessionDic.Add(clientUUID, sessionID); + } + else m_sessionDic[clientUUID] = sessionID; + } + lock (m_secureSessionDic) + { + if (!m_secureSessionDic.ContainsKey(clientUUID)) + { + m_secureSessionDic.Add(clientUUID, secureID); + } + else m_secureSessionDic[clientUUID] = secureID; + } + + try + { + if (userInfo == null) userInfo = new UserInfo(); + userInfo.UserID = clientUUID; + userInfo.SimIP = simIP; + userInfo.Avatar = userName; + userInfo.PswHash = UUID.Zero.ToString(); + userInfo.Type = avatarType; + userInfo.Class = avatarClass; + userInfo.ServerURL = serverURL; + if (!String.IsNullOrEmpty(securePsw)) userInfo.PswHash = securePsw; + + if (!m_moneyDBService.TryAddUserInfo(userInfo)) + { + m_log.ErrorFormat("[MONEY XMLRPC]: handleClientLogin: Unable to refresh information for user \"{0}\" in DB.", userName); + responseData["success"] = true; // for FireStorm + responseData["description"] = "Update or add user information to db failed"; + return response; + } + } + catch (Exception e) + { + m_log.ErrorFormat("[MONEY XMLRPC]: handleClientLogin: Can't update userinfo for user {0}: {1}", clientUUID, e.ToString()); + responseData["description"] = "Exception occured" + e.ToString(); + return response; + } + + try + { + balance = m_moneyDBService.getBalance(clientUUID); + + //add user to balances table if not exist. (if balance is -1, it means avatar is not exist at balances table) + if (balance == -1) + { + int default_balance = m_defaultBalance; + if (avatarClass == (int)AvatarType.HG_AVATAR) default_balance = m_hg_defaultBalance; + if (avatarClass == (int)AvatarType.GUEST_AVATAR) default_balance = m_gst_defaultBalance; + + if (m_moneyDBService.addUser(clientUUID, default_balance, 0, avatarType)) + { + responseData["success"] = true; + responseData["description"] = "add user successfully"; + responseData["clientBalance"] = default_balance; + } + else + { + responseData["description"] = "add user failed"; + } + } + //Success + else if (balance >= 0) + { + responseData["success"] = true; + responseData["description"] = "get user balance successfully"; + responseData["clientBalance"] = balance; + } + + return response; + } + catch (Exception e) + { + m_log.ErrorFormat("[MONEY XMLRPC]: handleClientLogin: Can't get balance of user {0}: {1}", clientUUID, e.ToString()); + responseData["description"] = "Exception occured" + e.ToString(); + } + + return response; + } + + + public XmlRpcResponse handleClientLogout(XmlRpcRequest request, IPEndPoint remoteClient) + { + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + response.Value = responseData; + + string clientUUID = string.Empty; + if (requestData.ContainsKey("clientUUID")) clientUUID = (string)requestData["clientUUID"]; + + m_log.InfoFormat("[MONEY XMLRPC]: handleClientLogout: User {0} is logging off.", clientUUID); + try + { + lock (m_sessionDic) + { + if (m_sessionDic.ContainsKey(clientUUID)) + { + m_sessionDic.Remove(clientUUID); + } + } + + lock (m_secureSessionDic) + { + if (m_secureSessionDic.ContainsKey(clientUUID)) + { + m_secureSessionDic.Remove(clientUUID); + } + } + } + catch (Exception e) + { + m_log.Error("[MONEY XMLRPC]: handleClientLogout: Failed to delete user session: " + e.ToString()); + responseData["success"] = false; + return response; + } + + responseData["success"] = true; + return response; + } + + public XmlRpcResponse handleTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + m_log.InfoFormat("[MONEY XMLRPC]: handleTransaction:"); + + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + response.Value = responseData; + + int amount = 0; + int transactionType = 0; + string senderID = string.Empty; + string receiverID = string.Empty; + string senderSessionID = string.Empty; + string senderSecureSessionID = string.Empty; + string objectID = string.Empty; + string objectName = string.Empty; + string regionHandle = string.Empty; + string regionUUID = string.Empty; + string description = "Newly added on"; + + responseData["success"] = false; + UUID transactionUUID = UUID.Random(); + + if (requestData.ContainsKey("senderID")) senderID = (string)requestData["senderID"]; + if (requestData.ContainsKey("receiverID")) receiverID = (string)requestData["receiverID"]; + if (requestData.ContainsKey("senderSessionID")) senderSessionID = (string)requestData["senderSessionID"]; + if (requestData.ContainsKey("senderSecureSessionID")) senderSecureSessionID = (string)requestData["senderSecureSessionID"]; + if (requestData.ContainsKey("amount")) amount = Convert.ToInt32(requestData["amount"]); + if (requestData.ContainsKey("objectID")) objectID = (string)requestData["objectID"]; + if (requestData.ContainsKey("objectName")) objectName = (string)requestData["objectName"]; + if (requestData.ContainsKey("regionHandle")) regionHandle = (string)requestData["regionHandle"]; + if (requestData.ContainsKey("regionUUID")) regionUUID = (string)requestData["regionUUID"]; + if (requestData.ContainsKey("transactionType")) transactionType = Convert.ToInt32(requestData["transactionType"]); + if (requestData.ContainsKey("description")) description = (string)requestData["description"]; + + m_log.InfoFormat("[MONEY XMLRPC]: handleTransaction: Transfering money from {0} to {1}, Amount = {2}", senderID, receiverID, amount); + m_log.InfoFormat("[MONEY XMLRPC]: handleTransaction: Object ID = {0}, Object Name = {1}", objectID, objectName); + + if (m_sessionDic.ContainsKey(senderID) && m_secureSessionDic.ContainsKey(senderID)) + { + if (m_sessionDic[senderID] == senderSessionID && m_secureSessionDic[senderID] == senderSecureSessionID) + { + m_log.InfoFormat("[MONEY XMLRPC]: handleTransaction: Transfering money from {0} to {1}", senderID, receiverID); + int time = (int)((DateTime.UtcNow.Ticks - TicksToEpoch) / 10000000); + try + { + TransactionData transaction = new TransactionData(); + transaction.TransUUID = transactionUUID; + transaction.Sender = senderID; + transaction.Receiver = receiverID; + transaction.Amount = amount; + transaction.ObjectUUID = objectID; + transaction.ObjectName = objectName; + transaction.RegionHandle = regionHandle; + transaction.RegionUUID = regionUUID; + transaction.Type = transactionType; + transaction.Time = time; + transaction.SecureCode = UUID.Random().ToString(); + transaction.Status = (int)Status.PENDING_STATUS; + transaction.CommonName = GetSSLCommonName(); + transaction.Description = description + " " + DateTime.UtcNow.ToString(); + + UserInfo rcvr = m_moneyDBService.FetchUserInfo(receiverID); + if (rcvr == null) + { + m_log.ErrorFormat("[MONEY XMLRPC]: handleTransaction: Receive User is not yet in DB {0}", receiverID); + return response; + } + + bool result = m_moneyDBService.addTransaction(transaction); + if (result) + { + UserInfo user = m_moneyDBService.FetchUserInfo(senderID); + if (user != null) + { + if (amount > 0 || (m_enableAmountZero && amount == 0)) + { + string snd_message = ""; + string rcv_message = ""; + + if (transaction.Type == (int)TransactionType.Gift) + { + snd_message = m_BalanceMessageSendGift; + rcv_message = m_BalanceMessageReceiveGift; + } + else if (transaction.Type == (int)TransactionType.LandSale) + { + snd_message = m_BalanceMessageLandSale; + rcv_message = m_BalanceMessageRcvLandSale; + } + else if (transaction.Type == (int)TransactionType.PayObject) + { + snd_message = m_BalanceMessageBuyObject; + rcv_message = m_BalanceMessageSellObject; + } + else if (transaction.Type == (int)TransactionType.ObjectPays) + { // ObjectGiveMoney + rcv_message = m_BalanceMessageGetMoney; + } + + responseData["success"] = NotifyTransfer(transactionUUID, snd_message, rcv_message, objectName); + } + else if (amount == 0) + { + responseData["success"] = true; // No messages for L$0 object. by Fumi.Iseki + } + return response; + } + } + else + { // add transaction failed + m_log.ErrorFormat("[MONEY XMLRPC]: handleTransaction: Add transaction for user {0} failed.", senderID); + } + return response; + } + catch (Exception e) + { + m_log.Error("[MONEY XMLRPC]: handleTransaction: Exception occurred while adding transaction: " + e.ToString()); + } + return response; + } + } + + m_log.Error("[MONEY XMLRPC]: handleTransaction: Session authentication failure for sender " + senderID); + responseData["message"] = "Session check failure, please re-login later!"; + return response; + } + + public XmlRpcResponse handleForceTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + response.Value = responseData; + + int amount = 0; + int transactionType = 0; + string senderID = string.Empty; + string receiverID = string.Empty; + string objectID = string.Empty; + string objectName = string.Empty; + string regionHandle = string.Empty; + string regionUUID = string.Empty; + string description = "Newly added on"; + + responseData["success"] = false; + UUID transactionUUID = UUID.Random(); + + // + if (!m_forceTransfer) + { + m_log.Error("[MONEY XMLRPC]: handleForceTransaction: Not allowed force transfer of Money."); + m_log.Error("[MONEY XMLRPC]: handleForceTransaction: Set enableForceTransfer at [MoneyServer] to true in MoneyServer.ini"); + responseData["message"] = "not allowed force transfer of Money!"; + return response; + } + + if (requestData.ContainsKey("senderID")) senderID = (string)requestData["senderID"]; + if (requestData.ContainsKey("receiverID")) receiverID = (string)requestData["receiverID"]; + if (requestData.ContainsKey("amount")) amount = Convert.ToInt32(requestData["amount"]); + if (requestData.ContainsKey("objectID")) objectID = (string)requestData["objectID"]; + if (requestData.ContainsKey("objectName")) objectName = (string)requestData["objectName"]; + if (requestData.ContainsKey("regionHandle")) regionHandle = (string)requestData["regionHandle"]; + if (requestData.ContainsKey("regionUUID")) regionUUID = (string)requestData["regionUUID"]; + if (requestData.ContainsKey("transactionType")) transactionType = Convert.ToInt32(requestData["transactionType"]); + if (requestData.ContainsKey("description")) description = (string)requestData["description"]; + + m_log.InfoFormat("[MONEY XMLRPC]: handleForceTransaction: Force transfering money from {0} to {1}, Amount = {2}", senderID, receiverID, amount); + m_log.InfoFormat("[MONEY XMLRPC]: handleForceTransaction: Object ID = {0}, Object Name = {1}", objectID, objectName); + + int time = (int)((DateTime.UtcNow.Ticks - TicksToEpoch) / 10000000); + + try + { + TransactionData transaction = new TransactionData(); + transaction.TransUUID = transactionUUID; + transaction.Sender = senderID; + transaction.Receiver = receiverID; + transaction.Amount = amount; + transaction.ObjectUUID = objectID; + transaction.ObjectName = objectName; + transaction.RegionHandle = regionHandle; + transaction.RegionUUID = regionUUID; + transaction.Type = transactionType; + transaction.Time = time; + transaction.SecureCode = UUID.Random().ToString(); + transaction.Status = (int)Status.PENDING_STATUS; + transaction.CommonName = GetSSLCommonName(); + transaction.Description = description + " " + DateTime.UtcNow.ToString(); + + UserInfo rcvr = m_moneyDBService.FetchUserInfo(receiverID); + if (rcvr == null) + { + m_log.ErrorFormat("[MONEY XMLRPC]: handleForceTransaction: Force receive User is not yet in DB {0}", receiverID); + return response; + } + + bool result = m_moneyDBService.addTransaction(transaction); + if (result) + { + UserInfo user = m_moneyDBService.FetchUserInfo(senderID); + if (user != null) + { + if (amount > 0 || (m_enableAmountZero && amount == 0)) + { + string snd_message = ""; + string rcv_message = ""; + + if (transaction.Type == (int)TransactionType.Gift) + { + snd_message = m_BalanceMessageSendGift; + rcv_message = m_BalanceMessageReceiveGift; + } + else if (transaction.Type == (int)TransactionType.LandSale) + { + snd_message = m_BalanceMessageLandSale; + snd_message = m_BalanceMessageRcvLandSale; + } + else if (transaction.Type == (int)TransactionType.PayObject) + { + snd_message = m_BalanceMessageBuyObject; + rcv_message = m_BalanceMessageSellObject; + } + else if (transaction.Type == (int)TransactionType.ObjectPays) + { // ObjectGiveMoney + rcv_message = m_BalanceMessageGetMoney; + } + + responseData["success"] = NotifyTransfer(transactionUUID, snd_message, rcv_message, objectName); + } + else if (amount == 0) + { + responseData["success"] = true; // No messages for L$0 object. by Fumi.Iseki + } + return response; + } + } + else + { // add transaction failed + m_log.ErrorFormat("[MONEY XMLRPC]: handleForceTransaction: Add force transaction for user {0} failed.", senderID); + } + return response; + } + catch (Exception e) + { + m_log.Error("[MONEY XMLRPC]: handleForceTransaction: Exception occurred while adding force transaction: " + e.ToString()); + } + return response; + } + + public XmlRpcResponse handleScriptTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + m_log.InfoFormat("[MONEY XMLRPC]: handleScriptTransaction:"); + + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + response.Value = responseData; + + int amount = 0; + int transactionType = 0; + string senderID = UUID.Zero.ToString(); + string receiverID = UUID.Zero.ToString(); + string clientIP = remoteClient.Address.ToString(); + string secretCode = string.Empty; + string description = "Scripted Send Money from/to Avatar on"; + + responseData["success"] = false; + UUID transactionUUID = UUID.Random(); + + if (!m_scriptSendMoney || m_scriptAccessKey == "") + { + m_log.Error("[MONEY XMLRPC]: handleScriptTransaction: Not allowed send money to avatar!!"); + m_log.Error("[MONEY XMLRPC]: handleScriptTransaction: Set enableScriptSendMoney and MoneyScriptAccessKey at [MoneyServer] in MoneyServer.ini"); + responseData["message"] = "not allowed set money to avatar!"; + return response; + } + + if (requestData.ContainsKey("senderID")) senderID = (string)requestData["senderID"]; + if (requestData.ContainsKey("receiverID")) receiverID = (string)requestData["receiverID"]; + if (requestData.ContainsKey("amount")) amount = Convert.ToInt32(requestData["amount"]); + if (requestData.ContainsKey("transactionType")) transactionType = Convert.ToInt32(requestData["transactionType"]); + if (requestData.ContainsKey("description")) description = (string)requestData["description"]; + if (requestData.ContainsKey("secretAccessCode")) secretCode = (string)requestData["secretAccessCode"]; + + MD5 md5 = MD5.Create(); + byte[] code = md5.ComputeHash(ASCIIEncoding.Default.GetBytes(m_scriptAccessKey + "_" + clientIP)); + string hash = BitConverter.ToString(code).ToLower().Replace("-", ""); + code = md5.ComputeHash(ASCIIEncoding.Default.GetBytes(hash + "_" + m_scriptIPaddress)); + hash = BitConverter.ToString(code).ToLower().Replace("-", ""); + + if (secretCode.ToLower() != hash) + { + m_log.Error("[MONEY XMLRPC]: handleScriptTransaction: Not allowed send money to avatar!!"); + m_log.Error("[MONEY XMLRPC]: handleScriptTransaction: Not match Script Access Key."); + responseData["message"] = "not allowed send money to avatar! not match Script Key"; + return response; + } + + m_log.InfoFormat("[MONEY XMLRPC]: handleScriptTransaction: Send money from {0} to {1}", senderID, receiverID); + int time = (int)((DateTime.UtcNow.Ticks - TicksToEpoch) / 10000000); + + try + { + TransactionData transaction = new TransactionData(); + transaction.TransUUID = transactionUUID; + transaction.Sender = senderID; + transaction.Receiver = receiverID; + transaction.Amount = amount; + transaction.ObjectUUID = UUID.Zero.ToString(); + transaction.RegionHandle = "0"; + transaction.Type = transactionType; + transaction.Time = time; + transaction.SecureCode = UUID.Random().ToString(); + transaction.Status = (int)Status.PENDING_STATUS; + transaction.CommonName = GetSSLCommonName(); + transaction.Description = description + " " + DateTime.UtcNow.ToString(); + + UserInfo senderInfo = null; + UserInfo receiverInfo = null; + if (transaction.Sender != UUID.Zero.ToString()) senderInfo = m_moneyDBService.FetchUserInfo(transaction.Sender); + if (transaction.Receiver != UUID.Zero.ToString()) receiverInfo = m_moneyDBService.FetchUserInfo(transaction.Receiver); + + if (senderInfo == null && receiverInfo == null) + { + m_log.ErrorFormat("[MONEY XMLRPC]: handleScriptTransaction: Sender and Receiver are not yet in DB, or both of them are System: {0}, {1}", + transaction.Sender, transaction.Receiver); + return response; + } + + bool result = m_moneyDBService.addTransaction(transaction); + if (result) + { + if (amount > 0 || (m_enableAmountZero && amount == 0)) + { + if (m_moneyDBService.DoTransfer(transactionUUID)) + { + transaction = m_moneyDBService.FetchTransaction(transactionUUID); + if (transaction != null && transaction.Status == (int)Status.SUCCESS_STATUS) + { + m_log.InfoFormat("[MONEY XMLRPC]: handleScriptTransaction: ScriptTransaction money finished successfully, now update balance {0}", + transactionUUID.ToString()); + string message = string.Empty; + if (senderInfo != null) + { + if (receiverInfo == null) message = string.Format(m_BalanceMessageSendMoney, amount, "SYSTEM", ""); + else message = string.Format(m_BalanceMessageSendMoney, amount, receiverInfo.Avatar, ""); + UpdateBalance(transaction.Sender, message); + m_log.InfoFormat("[MONEY XMLRPC]: handleScriptTransaction: Update balance of {0}. Message = {1}", transaction.Sender, message); + } + if (receiverInfo != null) + { + if (senderInfo == null) message = string.Format(m_BalanceMessageReceiveMoney, amount, "SYSTEM", ""); + else message = string.Format(m_BalanceMessageReceiveMoney, amount, senderInfo.Avatar, ""); + UpdateBalance(transaction.Receiver, message); + m_log.InfoFormat("[MONEY XMLRPC]: handleScriptTransaction: Update balance of {0}. Message = {1}", transaction.Receiver, message); + } + + responseData["success"] = true; + } + } + } + else if (amount == 0) + { + responseData["success"] = true; // No messages for L$0 add + } + return response; + } + else + { // add transaction failed + m_log.ErrorFormat("[MONEY XMLRPC]: handleScriptTransaction: Add force transaction for user {0} failed.", transaction.Sender); + } + return response; + } + catch (Exception e) + { + m_log.Error("[MONEY XMLRPC]: handleScriptTransaction: Exception occurred while adding money transaction: " + e.ToString()); + } + return response; + } + + public XmlRpcResponse handleAddBankerMoney(XmlRpcRequest request, IPEndPoint remoteClient) + { + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + response.Value = responseData; + + int amount = 0; + int transactionType = 0; + string senderID = UUID.Zero.ToString(); + string bankerID = string.Empty; + string regionHandle = "0"; + string regionUUID = UUID.Zero.ToString(); + string description = "Add Money to Avatar on"; + + responseData["success"] = false; + UUID transactionUUID = UUID.Random(); + + if (requestData.ContainsKey("bankerID")) bankerID = (string)requestData["bankerID"]; + if (requestData.ContainsKey("amount")) amount = Convert.ToInt32(requestData["amount"]); + if (requestData.ContainsKey("regionHandle")) regionHandle = (string)requestData["regionHandle"]; + if (requestData.ContainsKey("regionUUID")) regionUUID = (string)requestData["regionUUID"]; + if (requestData.ContainsKey("transactionType")) transactionType = Convert.ToInt32(requestData["transactionType"]); + if (requestData.ContainsKey("description")) description = (string)requestData["description"]; + + // Check Banker Avatar + if (m_bankerAvatar != UUID.Zero.ToString() && m_bankerAvatar != bankerID) + { + m_log.Error("[MONEY XMLRPC]: handleAddBankerMoney: Not allowed add money to avatar!!"); + m_log.Error("[MONEY XMLRPC]: handleAddBankerMoney: Set BankerAvatar at [MoneyServer] in MoneyServer.ini"); + responseData["message"] = "not allowed add money to avatar!"; + responseData["banker"] = false; + return response; + } + responseData["banker"] = true; + + m_log.InfoFormat("[MONEY XMLRPC]: handleAddBankerMoney: Add money to avatar {0}", bankerID); + int time = (int)((DateTime.UtcNow.Ticks - TicksToEpoch) / 10000000); + + try + { + TransactionData transaction = new TransactionData(); + transaction.TransUUID = transactionUUID; + transaction.Sender = senderID; + transaction.Receiver = bankerID; + transaction.Amount = amount; + transaction.ObjectUUID = UUID.Zero.ToString(); + transaction.RegionHandle = regionHandle; + transaction.RegionUUID = regionUUID; + transaction.Type = transactionType; + transaction.Time = time; + transaction.SecureCode = UUID.Random().ToString(); + transaction.Status = (int)Status.PENDING_STATUS; + transaction.CommonName = GetSSLCommonName(); + transaction.Description = description + " " + DateTime.UtcNow.ToString(); + + UserInfo rcvr = m_moneyDBService.FetchUserInfo(bankerID); + if (rcvr == null) + { + m_log.ErrorFormat("[MONEY XMLRPC]: handleAddBankerMoney: Avatar is not yet in DB {0}", bankerID); + return response; + } + + bool result = m_moneyDBService.addTransaction(transaction); + if (result) + { + if (amount > 0 || (m_enableAmountZero && amount == 0)) + { + if (m_moneyDBService.DoAddMoney(transactionUUID)) + { + transaction = m_moneyDBService.FetchTransaction(transactionUUID); + if (transaction != null && transaction.Status == (int)Status.SUCCESS_STATUS) + { + m_log.InfoFormat("[MONEY XMLRPC]: handleAddBankerMoney: Adding money finished successfully, now update balance: {0}", + transactionUUID.ToString()); + string message = string.Format(m_BalanceMessageBuyMoney, amount, "SYSTEM", ""); + UpdateBalance(transaction.Receiver, message); + responseData["success"] = true; + } + } + } + else if (amount == 0) + { + responseData["success"] = true; // No messages for L$0 add + } + return response; + } + else + { // add transaction failed + m_log.ErrorFormat("[MONEY XMLRPC]: handleAddBankerMoney: Add force transaction for user {0} failed.", senderID); + } + return response; + } + catch (Exception e) + { + m_log.Error("[MONEY XMLRPC]: handleAddBankerMoney: Exception occurred while adding money transaction: " + e.ToString()); + } + return response; + } + + public XmlRpcResponse handlePayMoneyCharge(XmlRpcRequest request, IPEndPoint remoteClient) + { + m_log.InfoFormat("[MONEY XMLRPC]: handlePayMoneyCharge now."); + + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + response.Value = responseData; + + int amount = 0; + int transactionType = 0; + string senderID = string.Empty; + string receiverID = UUID.Zero.ToString(); + string senderSessionID = string.Empty; + string senderSecureSessionID = string.Empty; + string objectID = UUID.Zero.ToString(); + string objectName = string.Empty; + string regionHandle = string.Empty; + string regionUUID = string.Empty; + string description = "Pay Charge on"; + + responseData["success"] = false; + UUID transactionUUID = UUID.Random(); + + // Parameter aus der Anfrage extrahieren + if (requestData.ContainsKey("senderID")) senderID = (string)requestData["senderID"]; + if (requestData.ContainsKey("senderSessionID")) senderSessionID = (string)requestData["senderSessionID"]; + if (requestData.ContainsKey("senderSecureSessionID")) senderSecureSessionID = (string)requestData["senderSecureSessionID"]; + if (requestData.ContainsKey("amount")) amount = Convert.ToInt32(requestData["amount"]); + if (requestData.ContainsKey("regionHandle")) regionHandle = (string)requestData["regionHandle"]; + if (requestData.ContainsKey("regionUUID")) regionUUID = (string)requestData["regionUUID"]; + if (requestData.ContainsKey("transactionType")) transactionType = Convert.ToInt32(requestData["transactionType"]); + if (requestData.ContainsKey("description")) description = (string)requestData["description"]; + if (requestData.ContainsKey("receiverID")) receiverID = (string)requestData["receiverID"]; + if (requestData.ContainsKey("objectID")) objectID = (string)requestData["objectID"]; + if (requestData.ContainsKey("objectName")) objectName = (string)requestData["objectName"]; + + m_log.InfoFormat("[MONEY XMLRPC]: handlePayMoneyCharge: Transfering money from {0} to {1}, Amount = {2}", senderID, receiverID, amount); + + // Sitzungsprüfung überspringen für SYSTEM oder Banker + if (senderID == m_bankerAvatar || senderID == "SYSTEM") + { + m_log.InfoFormat("[MONEY XMLRPC]: handlePayMoneyCharge: Sender ist SYSTEM oder BankerAvatar. Sitzungsprüfung wird übersprungen."); + } + else if (m_sessionDic.ContainsKey(senderID) && m_secureSessionDic.ContainsKey(senderID)) + { + if (m_sessionDic[senderID] != senderSessionID || m_secureSessionDic[senderID] != senderSecureSessionID) + { + m_log.Error("[MONEY XMLRPC]: handlePayMoneyCharge: Sitzungsprüfung für Sender fehlgeschlagen " + senderID); + responseData["message"] = "Session check failure, please re-login later!"; + return response; + } + } + else + { + m_log.Error("[MONEY XMLRPC]: handlePayMoneyCharge: Sitzungsprüfung für Sender fehlgeschlagen " + senderID); + responseData["message"] = "Session check failure, please re-login later!"; + return response; + } + + int time = (int)((DateTime.UtcNow.Ticks - TicksToEpoch) / 10000000); + + try + { + TransactionData transaction = new TransactionData + { + TransUUID = transactionUUID, + Sender = senderID, + Receiver = receiverID, + Amount = amount, + ObjectUUID = objectID, + ObjectName = objectName, + RegionHandle = regionHandle, + RegionUUID = regionUUID, + Type = transactionType, + Time = time, + SecureCode = UUID.Random().ToString(), + Status = (int)Status.PENDING_STATUS, + CommonName = GetSSLCommonName(), + Description = description + " " + DateTime.UtcNow.ToString() + }; + + bool result = m_moneyDBService.addTransaction(transaction); + if (result) + { + UserInfo user = m_moneyDBService.FetchUserInfo(senderID); + if (user != null) + { + if (amount == 0) + { + // Für L$0 keine Transferaktion, einfach Erfolg zurückgeben + responseData["success"] = true; + return response; + } + + if (amount > 0 || (m_enableAmountZero && amount == 0)) + { + if (!NotifyTransfer(transactionUUID, "", "", "")) + { + m_log.Error("[MONEY XMLRPC]: handlePayMoneyCharge: Gutschrift fehlgeschlagen, versuche manuell Geld hinzuzufügen."); + + Hashtable addMoneyParams = new Hashtable(); + addMoneyParams["bankerID"] = "SYSTEM"; + addMoneyParams["amount"] = amount; + addMoneyParams["regionHandle"] = regionHandle; + addMoneyParams["regionUUID"] = regionUUID; + addMoneyParams["transactionType"] = transactionType; + addMoneyParams["description"] = "Manuelle Gutschrift nach Fehlermeldung"; + + XmlRpcResponse addMoneyResponse = handleAddBankerMoney( + new XmlRpcRequest("AddBankerMoney", new object[] { addMoneyParams }), + remoteClient + ); + + Hashtable responseValue = addMoneyResponse.Value as Hashtable; + bool addMoneySuccess = addMoneyResponse != null + && responseValue != null + && responseValue.ContainsKey("success") + && (bool)responseValue["success"]; + + responseData["success"] = addMoneySuccess; + + if (!addMoneySuccess) + { + responseData["message"] = "Manuelles Hinzufügen des Geldes fehlgeschlagen."; + } + + return response; + } + + // Wenn NotifyTransfer erfolgreich war + responseData["success"] = true; + return response; + } + } + } + else + { + m_log.ErrorFormat("[MONEY XMLRPC]: handlePayMoneyCharge: Zahlungstransaktion für Benutzer {0} fehlgeschlagen.", senderID); + } + } + catch (Exception e) + { + m_log.Error("[MONEY XMLRPC]: handlePayMoneyCharge: Ausnahme bei der Zahlungstransaktion: " + e.ToString()); + } + + return response; + } + + public XmlRpcResponse handleCancelTransfer(XmlRpcRequest request, IPEndPoint remoteClient) + { + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + response.Value = responseData; + + string secureCode = string.Empty; + string transactionID = string.Empty; + UUID transactionUUID = UUID.Zero; + + responseData["success"] = false; + + if (requestData.ContainsKey("secureCode")) secureCode = (string)requestData["secureCode"]; + if (requestData.ContainsKey("transactionID")) + { + transactionID = (string)requestData["transactionID"]; + UUID.TryParse(transactionID, out transactionUUID); + } + + if (string.IsNullOrEmpty(secureCode) || string.IsNullOrEmpty(transactionID)) + { + m_log.Error("[MONEY XMLRPC]: handleCancelTransfer: secureCode and/or transactionID are empty."); + return response; + } + + TransactionData transaction = m_moneyDBService.FetchTransaction(transactionUUID); + UserInfo user = m_moneyDBService.FetchUserInfo(transaction.Sender); + + try + { + m_log.InfoFormat("[MONEY XMLRPC]: handleCancelTransfer: User {0} wanted to cancel the transaction.", user.Avatar); + if (m_moneyDBService.ValidateTransfer(secureCode, transactionUUID)) + { + m_log.InfoFormat("[MONEY XMLRPC]: handleCancelTransfer: User {0} has canceled the transaction {1}", user.Avatar, transactionID); + m_moneyDBService.updateTransactionStatus(transactionUUID, (int)Status.FAILED_STATUS, + "User canceled the transaction on " + DateTime.UtcNow.ToString()); + responseData["success"] = true; + } + } + catch (Exception e) + { + m_log.ErrorFormat("[MONEY XMLRPC]: handleCancelTransfer: Exception occurred when transaction {0}: {1}", transactionID, e.ToString()); + } + return response; + } + + public XmlRpcResponse handleGetTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + response.Value = responseData; + + string clientID = string.Empty; + string sessionID = string.Empty; + string secureID = string.Empty; + string transactionID = string.Empty; + UUID transactionUUID = UUID.Zero; + + responseData["success"] = false; + + if (requestData.ContainsKey("clientUUID")) clientID = (string)requestData["clientUUID"]; + if (requestData.ContainsKey("clientSessionID")) sessionID = (string)requestData["clientSessionID"]; + if (requestData.ContainsKey("clientSecureSessionID")) secureID = (string)requestData["clientSecureSessionID"]; + + if (requestData.ContainsKey("transactionID")) + { + transactionID = (string)requestData["transactionID"]; + UUID.TryParse(transactionID, out transactionUUID); + } + + if (m_sessionDic.ContainsKey(clientID) && m_secureSessionDic.ContainsKey(clientID)) + { + if (m_sessionDic[clientID] == sessionID && m_secureSessionDic[clientID] == secureID) + { + // + if (string.IsNullOrEmpty(transactionID)) + { + responseData["description"] = "TransactionID is empty"; + m_log.Error("[MONEY XMLRPC]: handleGetTransaction: TransactionID is empty."); + return response; + } + + try + { + TransactionData transaction = m_moneyDBService.FetchTransaction(transactionUUID); + if (transaction != null) + { + responseData["success"] = true; + responseData["amount"] = transaction.Amount; + responseData["time"] = transaction.Time; + responseData["type"] = transaction.Type; + responseData["sender"] = transaction.Sender.ToString(); + responseData["receiver"] = transaction.Receiver.ToString(); + responseData["description"] = transaction.Description; + } + else + { + responseData["description"] = "Invalid Transaction UUID"; + } + + return response; + } + catch (Exception e) + { + m_log.ErrorFormat("[MONEY XMLRPC]: handleGetTransaction: {0}", e.ToString()); + m_log.ErrorFormat("[MONEY XMLRPC]: handleGetTransaction: Can't get transaction information for {0}", transactionUUID.ToString()); + } + return response; + } + } + + responseData["success"] = false; + responseData["description"] = "Session check failure, please re-login"; + return response; + } + + public XmlRpcResponse handleWebLogin(XmlRpcRequest request, IPEndPoint remoteClient) + { + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + response.Value = responseData; + + string userID = string.Empty; + string webSessionID = string.Empty; + + responseData["success"] = false; + + if (requestData.ContainsKey("userID")) userID = (string)requestData["userID"]; + if (requestData.ContainsKey("sessionID")) webSessionID = (string)requestData["sessionID"]; + + if (string.IsNullOrEmpty(userID) || string.IsNullOrEmpty(webSessionID)) + { + responseData["errorMessage"] = "userID or sessionID can`t be empty, login failed!"; + return response; + } + + //Update the web session dictionary + lock (m_webSessionDic) + { + if (!m_webSessionDic.ContainsKey(userID)) + { + m_webSessionDic.Add(userID, webSessionID); + } + else m_webSessionDic[userID] = webSessionID; + } + + m_log.InfoFormat("[MONEY XMLRPC]: handleWebLogin: User {0} has logged in from web.", userID); + responseData["success"] = true; + return response; + } + + public XmlRpcResponse handleWebLogout(XmlRpcRequest request, IPEndPoint remoteClient) + { + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + response.Value = responseData; + + string userID = string.Empty; + string webSessionID = string.Empty; + + responseData["success"] = false; + + if (requestData.ContainsKey("userID")) userID = (string)requestData["userID"]; + if (requestData.ContainsKey("sessionID")) webSessionID = (string)requestData["sessionID"]; + + if (string.IsNullOrEmpty(userID) || string.IsNullOrEmpty(webSessionID)) + { + responseData["errorMessage"] = "userID or sessionID can`t be empty, log out failed!"; + return response; + } + + //Update the web session dictionary + lock (m_webSessionDic) + { + if (m_webSessionDic.ContainsKey(userID)) + { + m_webSessionDic.Remove(userID); + } + } + + m_log.InfoFormat("[MONEY XMLRPC]: handleWebLogout: User {0} has logged out from web.", userID); + responseData["success"] = true; + return response; + } + + public XmlRpcResponse handleWebGetBalance(XmlRpcRequest request, IPEndPoint remoteClient) + { + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + response.Value = responseData; + + string userID = string.Empty; + string webSessionID = string.Empty; + int balance = 0; + + responseData["success"] = false; + + if (requestData.ContainsKey("userID")) userID = (string)requestData["userID"]; + if (requestData.ContainsKey("sessionID")) webSessionID = (string)requestData["sessionID"]; + + m_log.InfoFormat("[MONEY XMLRPC]: handleWebGetBalance: Getting balance for user {0}", userID); + + //perform session check + if (m_webSessionDic.ContainsKey(userID)) + { + if (m_webSessionDic[userID] == webSessionID) + { + try + { + balance = m_moneyDBService.getBalance(userID); + UserInfo user = m_moneyDBService.FetchUserInfo(userID); + if (user != null) + { + responseData["userName"] = user.Avatar; + } + else + { + responseData["userName"] = "unknown user"; + } + // User not found + if (balance == -1) + { + responseData["errorMessage"] = "User not found"; + responseData["balance"] = 0; + } + else if (balance >= 0) + { + responseData["success"] = true; + responseData["balance"] = balance; + } + return response; + } + catch (Exception e) + { + m_log.ErrorFormat("[MONEY XMLRPC]: handleWebGetBalance: Can't get balance for user {0}, Exception {1}", userID, e.ToString()); + responseData["errorMessage"] = "Exception occurred when getting balance"; + return response; + } + } + } + + m_log.Error("[MONEY XMLRPC]: handleWebLogout: Session authentication failed when getting balance for user " + userID); + responseData["errorMessage"] = "Session check failure, please re-login"; + return response; + } + + public XmlRpcResponse handleWebGetTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + response.Value = responseData; + + string userID = string.Empty; + string webSessionID = string.Empty; + int lastIndex = -1; + int startTime = 0; + int endTime = 0; + + responseData["success"] = false; + + if (requestData.ContainsKey("userID")) userID = (string)requestData["userID"]; + if (requestData.ContainsKey("sessionID")) webSessionID = (string)requestData["sessionID"]; + if (requestData.ContainsKey("startTime")) startTime = (int)requestData["startTime"]; + if (requestData.ContainsKey("endTime")) endTime = (int)requestData["endTime"]; + if (requestData.ContainsKey("lastIndex")) lastIndex = (int)requestData["lastIndex"]; + + if (m_webSessionDic.ContainsKey(userID)) + { + if (m_webSessionDic[userID] == webSessionID) + { + try + { + int total = m_moneyDBService.getTransactionNum(userID, startTime, endTime); + TransactionData tran = null; + m_log.InfoFormat("[MONEY XMLRPC]: handleWebGetTransaction: Getting transation[{0}] for user {1}", lastIndex + 1, userID); + if (total > lastIndex + 2) + { + responseData["isEnd"] = false; + } + else + { + responseData["isEnd"] = true; + } + + tran = m_moneyDBService.FetchTransaction(userID, startTime, endTime, lastIndex); + if (tran != null) + { + UserInfo senderInfo = m_moneyDBService.FetchUserInfo(tran.Sender); + UserInfo receiverInfo = m_moneyDBService.FetchUserInfo(tran.Receiver); + if (senderInfo != null && receiverInfo != null) + { + responseData["senderName"] = senderInfo.Avatar; + responseData["receiverName"] = receiverInfo.Avatar; + } + else + { + responseData["senderName"] = "unknown user"; + responseData["receiverName"] = "unknown user"; + } + responseData["success"] = true; + responseData["transactionIndex"] = lastIndex + 1; + responseData["transactionUUID"] = tran.TransUUID.ToString(); + responseData["senderID"] = tran.Sender; + responseData["receiverID"] = tran.Receiver; + responseData["amount"] = tran.Amount; + responseData["type"] = tran.Type; + responseData["time"] = tran.Time; + responseData["status"] = tran.Status; + responseData["description"] = tran.Description; + } + else + { + responseData["errorMessage"] = string.Format("Unable to fetch transaction data with the index {0}", lastIndex + 1); + } + return response; + } + catch (Exception e) + { + m_log.ErrorFormat("[MONEY XMLRPC]: handleWebGetTransaction: Can't get transaction for user {0}, Exception {1}", userID, e.ToString()); + responseData["errorMessage"] = "Exception occurred when getting transaction"; + return response; + } + } + } + + m_log.Error("[MONEY XMLRPC]: handleWebGetTransaction: Session authentication failed when getting transaction for user " + userID); + responseData["errorMessage"] = "Session check failure, please re-login"; + return response; + } + + public XmlRpcResponse handleWebGetTransactionNum(XmlRpcRequest request, IPEndPoint remoteClient) + { + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + response.Value = responseData; + + string userID = string.Empty; + string webSessionID = string.Empty; + int startTime = 0; + int endTime = 0; + + responseData["success"] = false; + + if (requestData.ContainsKey("userID")) userID = (string)requestData["userID"]; + if (requestData.ContainsKey("sessionID")) webSessionID = (string)requestData["sessionID"]; + if (requestData.ContainsKey("startTime")) startTime = (int)requestData["startTime"]; + if (requestData.ContainsKey("endTime")) endTime = (int)requestData["endTime"]; + + if (m_webSessionDic.ContainsKey(userID)) + { + if (m_webSessionDic[userID] == webSessionID) + { + int it = m_moneyDBService.getTransactionNum(userID, startTime, endTime); + if (it >= 0) + { + m_log.InfoFormat("[MONEY XMLRPC]: handleWebGetTransactionNum: Get {0} transactions for user {1}", it, userID); + responseData["success"] = true; + responseData["number"] = it; + } + return response; + } + } + + m_log.Error("[MONEY XMLRPC]: handleWebGetTransactionNum: Session authentication failed when getting transaction number for user " + userID); + responseData["errorMessage"] = "Session check failure, please re-login"; + return response; + } + + + #endregion + // ################## helper ################## + #region helper + + + //Spezifische Handler OnMoneyTransferedHandler: Protokolliert Details zu einer Geldüberweisung. + public XmlRpcResponse OnMoneyTransferedHandler(XmlRpcRequest request, IPEndPoint client) + { + if (request == null) + { + m_log.Error("[MONEY XMLRPC]: OnMoneyTransferedHandler: request is null."); + return new XmlRpcResponse(); + } + + if (client == null) + { + m_log.Error("[MONEY XMLRPC]: OnMoneyTransferedHandler: client is null."); + return new XmlRpcResponse(); + } + + try + { + Hashtable requestData = (Hashtable)request.Params[0]; + string transactionID = (string)requestData["transactionID"]; + UUID transactionUUID = UUID.Zero; + UUID.TryParse(transactionID, out transactionUUID); + + TransactionData transaction = m_moneyDBService.FetchTransaction(transactionUUID); + UserInfo user = m_moneyDBService.FetchUserInfo(transaction.Sender); + + m_log.InfoFormat("[MONEY XMLRPC]: OnMoneyTransferedHandler: Transaction {0} from user {1} to user {2} for {3} units", + transactionID, user.Avatar, transaction.Receiver, transaction.Amount); + + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + responseData.Add("success", true); + response.Value = responseData; + return response; + } + catch (Exception ex) + { + m_log.ErrorFormat("[MONEY XMLRPC]: OnMoneyTransferedHandler: Exception occurred: {0}", ex.Message); + return new XmlRpcResponse(); + } + } + + public string GetSSLCommonName(XmlRpcRequest request) + { + if (request.Params.Count > 5) + { + m_sslCommonName = (string)request.Params[5]; + } + else if (request.Params.Count == 5) + { + m_sslCommonName = (string)request.Params[4]; + if (m_sslCommonName == "gridproxy") m_sslCommonName = ""; + } + else + { + m_sslCommonName = ""; + } + return m_sslCommonName; + } + + /// Gets the name of the SSL common. + public string GetSSLCommonName() + { + return m_sslCommonName; + } + + public bool ObjectGiveMoney(UUID objectID, UUID fromID, UUID toID, int amount, UUID txn, out string result) + { + result = String.Empty; + string description = String.Format("Object {0} pays {1}", resolveObjectName(objectID), resolveAgentName(toID)); + + bool give_result = doMoneyTransfer(fromID, toID, amount, 2, description); + + + BalanceUpdate(fromID, toID, give_result, description); + + return give_result; + } + + private string resolveObjectName(UUID objectID) + { + SceneObjectPart part = findPrim(objectID); + if (part != null) + { + return part.Name; + } + return String.Empty; + } + + private string resolveAgentName(UUID agentID) + { + // try avatar username surname + Scene scene = GetRandomScene(); + UserAccount account = scene.UserAccountService.GetUserAccount(scene.RegionInfo.ScopeID, agentID); + if (account != null) + { + string avatarname = account.FirstName + " " + account.LastName; + return avatarname; + } + else + { + m_log.ErrorFormat( + "[MONEY]: Could not resolve user {0}", + agentID); + } + + return String.Empty; + } + + /// + /// Utility function Gets a Random scene in the instance. For when which scene exactly you're doing something with doesn't matter + /// + /// + public Scene GetRandomScene() + { + lock (m_scenes) + { + foreach (Scene rs in m_scenes.Values) + return rs; + } + return null; + } + + private SceneObjectPart findPrim(UUID objectID) + { + lock (m_scenes) + { + foreach (Scene s in m_scenes.Values) + { + SceneObjectPart part = s.GetSceneObjectPart(objectID); + if (part != null) + { + return part; + } + } + } + return null; + } + + /// + /// Transfer money + /// + /// + /// + /// + /// + private bool doMoneyTransfer(UUID Sender, UUID Receiver, int amount, int transactiontype, string description) + { + return true; + } + + private void BalanceUpdate(UUID senderID, UUID receiverID, bool transactionresult, string description) + { + IClientAPI sender = LocateClientObject(senderID); + IClientAPI receiver = LocateClientObject(receiverID); + + if (senderID != receiverID) + { + if (sender != null) + { + sender.SendMoneyBalance(UUID.Random(), transactionresult, Utils.StringToBytes(description), GetFundsForAgentID(senderID), 0, UUID.Zero, false, UUID.Zero, false, 0, String.Empty); + } + + if (receiver != null) + { + receiver.SendMoneyBalance(UUID.Random(), transactionresult, Utils.StringToBytes(description), GetFundsForAgentID(receiverID), 0, UUID.Zero, false, UUID.Zero, false, 0, String.Empty); + } + } + } + + /// + /// Sends the the stored money balance to the client + /// + /// + /// + /// + /// + public void SendMoneyBalance(IClientAPI client, UUID agentID, UUID SessionID, UUID TransactionID) + { + if (client.AgentId == agentID && client.SessionId == SessionID) + { + int returnfunds = 0; + + try + { + returnfunds = GetFundsForAgentID(agentID); + } + catch (Exception e) + { + client.SendAlertMessage(e.Message + " "); + } + + client.SendMoneyBalance(TransactionID, true, Array.Empty(), returnfunds, 0, UUID.Zero, false, UUID.Zero, false, 0, String.Empty); + } + else + { + client.SendAlertMessage("Unable to send your money balance to you!"); + } + } + + private int GetFundsForAgentID(UUID AgentID) + { + int returnfunds = 0; + + return returnfunds; + } + + /// + /// Locates a IClientAPI for the client specified + /// + /// + /// + private IClientAPI LocateClientObject(UUID AgentID) + { + ScenePresence tPresence; + lock (m_scenes) + { + foreach (Scene _scene in m_scenes.Values) + { + tPresence = _scene.GetScenePresence(AgentID); + if (tPresence != null && !tPresence.IsDeleted && !tPresence.IsChildAgent) + return tPresence.ControllingClient; + } + } + return null; + } + + public bool NotifyTransfer(UUID transactionUUID, string msg2sender, string msg2receiver, string objectName) + { + m_log.InfoFormat("[MONEY XMLRPC]: NotifyTransfer: User has accepted the transaction, now continue with the transaction"); + + try + { + if (m_moneyDBService.DoTransfer(transactionUUID)) + { + TransactionData transaction = m_moneyDBService.FetchTransaction(transactionUUID); + if (transaction != null && transaction.Status == (int)Status.SUCCESS_STATUS) + { + //m_log.InfoFormat("[MONEY XMLRPC]: NotifyTransfer: Transaction Type = {0}", transaction.Type); + //m_log.InfoFormat("[MONEY XMLRPC]: NotifyTransfer: Payment finished successfully, now update balance {0}", transactionUUID.ToString()); + + bool updateSender = true; + bool updateReceiv = true; + if (transaction.Sender == transaction.Receiver) updateSender = false; + if (transaction.Type == (int)TransactionType.UploadCharge) updateReceiv = false; + + if (updateSender) + { + UserInfo receiverInfo = m_moneyDBService.FetchUserInfo(transaction.Receiver); + string receiverName = "unknown user"; + if (receiverInfo != null) receiverName = receiverInfo.Avatar; + string snd_message = string.Format(msg2sender, transaction.Amount, receiverName, objectName); + UpdateBalance(transaction.Sender, snd_message); + } + if (updateReceiv) + { + UserInfo senderInfo = m_moneyDBService.FetchUserInfo(transaction.Sender); + string senderName = "unknown user"; + if (senderInfo != null) senderName = senderInfo.Avatar; + string rcv_message = string.Format(msg2receiver, transaction.Amount, senderName, objectName); + UpdateBalance(transaction.Receiver, rcv_message); + } + + // Notify to sender + if (transaction.Type == (int)TransactionType.PayObject) + { + m_log.InfoFormat("[MONEY XMLRPC]: NotifyTransfer: Now notify opensim to give object to customer {0} ", transaction.Sender); + Hashtable requestTable = new Hashtable(); + requestTable["clientUUID"] = transaction.Sender; + requestTable["receiverUUID"] = transaction.Receiver; + + if (m_sessionDic.ContainsKey(transaction.Sender) && m_secureSessionDic.ContainsKey(transaction.Sender)) + { + requestTable["clientSessionID"] = m_sessionDic[transaction.Sender]; + requestTable["clientSecureSessionID"] = m_secureSessionDic[transaction.Sender]; + } + else + { + requestTable["clientSessionID"] = UUID.Zero.ToString(); + requestTable["clientSecureSessionID"] = UUID.Zero.ToString(); + } + requestTable["transactionType"] = transaction.Type; + requestTable["amount"] = transaction.Amount; + requestTable["objectID"] = transaction.ObjectUUID; + requestTable["objectName"] = transaction.ObjectName; + requestTable["regionHandle"] = transaction.RegionHandle; + + UserInfo user = m_moneyDBService.FetchUserInfo(transaction.Sender); + if (user != null) + { + Hashtable responseTable = genericCurrencyXMLRPCRequest(requestTable, "OnMoneyTransfered", user.SimIP); + + if (responseTable != null && responseTable.ContainsKey("success")) + { + //User not online or failed to get object ? + if (!(bool)responseTable["success"]) + { + m_log.ErrorFormat("[MONEY XMLRPC]: NotifyTransfer: User {0} can't get the object, rolling back.", transaction.Sender); + if (RollBackTransaction(transaction)) + { + m_log.ErrorFormat("[MONEY XMLRPC]: NotifyTransfer: Transaction {0} failed but roll back succeeded.", transactionUUID.ToString()); + } + else + { + m_log.ErrorFormat("[MONEY XMLRPC]: NotifyTransfer: Transaction {0} failed and roll back failed as well.", + transactionUUID.ToString()); + } + } + else + { + m_log.InfoFormat("[MONEY XMLRPC]: NotifyTransfer: Transaction {0} finished successfully.", transactionUUID.ToString()); + return true; + } + } + } + return false; + } + return true; + } + } + m_log.ErrorFormat("[MONEY XMLRPC]: NotifyTransfer: Transaction {0} failed.", transactionUUID.ToString()); + } + catch (Exception e) + { + m_log.ErrorFormat("[MONEY XMLRPC]: NotifyTransfer: exception occurred when transaction {0}: {1}", transactionUUID.ToString(), e.ToString()); + } + + return false; + } + + public XmlRpcResponse handleGetBalance(XmlRpcRequest request, IPEndPoint remoteClient) + { + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + response.Value = responseData; + + string clientUUID = string.Empty; + string sessionID = string.Empty; + string secureID = string.Empty; + int balance; + + responseData["success"] = false; + + if (requestData.ContainsKey("clientUUID")) clientUUID = (string)requestData["clientUUID"]; + if (requestData.ContainsKey("clientSessionID")) sessionID = (string)requestData["clientSessionID"]; + if (requestData.ContainsKey("clientSecureSessionID")) secureID = (string)requestData["clientSecureSessionID"]; + + m_log.InfoFormat("[MONEY XMLRPC]: handleGetBalance: Getting balance for user {0}", clientUUID); + + if (m_sessionDic.ContainsKey(clientUUID) && m_secureSessionDic.ContainsKey(clientUUID)) + { + if (m_sessionDic[clientUUID] == sessionID && m_secureSessionDic[clientUUID] == secureID) + { + try + { + balance = m_moneyDBService.getBalance(clientUUID); + if (balance == -1) // User not found + { + responseData["description"] = "user not found"; + responseData["clientBalance"] = 0; + } + else if (balance >= 0) + { + responseData["success"] = true; + responseData["clientBalance"] = balance; + } + + return response; + } + catch (Exception e) + { + m_log.ErrorFormat("[MONEY XMLRPC]: handleGetBalance: Can't get balance for user {0}, Exception {1}", clientUUID, e.ToString()); + } + return response; + } + } + + m_log.Error("[MONEY XMLRPC]: handleGetBalance: Session authentication failed when getting balance for user " + clientUUID); + responseData["description"] = "Session check failure, please re-login"; + return response; + } + + private Hashtable genericCurrencyXMLRPCRequest(Hashtable reqParams, string method, string uri) + { + m_log.InfoFormat("[MONEY XMLRPC]: genericCurrencyXMLRPCRequest: to {0}", uri); + + if (reqParams.Count <= 0 || string.IsNullOrEmpty(method)) return null; + + if (m_checkServerCert) + { + if (!uri.StartsWith("https://")) + { + m_log.InfoFormat("[MONEY XMLRPC]: genericCurrencyXMLRPCRequest: CheckServerCert is true, but protocol is not HTTPS. Please check INI file."); + //return null; + } + } + else + { + if (!uri.StartsWith("https://") && !uri.StartsWith("http://")) + { + m_log.ErrorFormat("[MONEY XMLRPC]: genericCurrencyXMLRPCRequest: Invalid Region Server URL: {0}", uri); + return null; + } + } + + ArrayList arrayParams = new ArrayList(); + arrayParams.Add(reqParams); + XmlRpcResponse moneyServResp = null; + try + { + NSLXmlRpcRequest moneyModuleReq = new NSLXmlRpcRequest(method, arrayParams); + moneyServResp = moneyModuleReq.certSend(uri, m_certVerify, m_checkServerCert, MONEYMODULE_REQUEST_TIMEOUT); + } + catch (Exception ex) + { + m_log.ErrorFormat("[MONEY XMLRPC]: genericCurrencyXMLRPCRequest: Unable to connect to Region Server {0}", uri); + m_log.ErrorFormat("[MONEY XMLRPC]: genericCurrencyXMLRPCRequest: {0}", ex.ToString()); + + Hashtable ErrorHash = new Hashtable(); + ErrorHash["success"] = false; + ErrorHash["errorMessage"] = "Failed to perform actions on OpenSim Server"; + ErrorHash["errorURI"] = ""; + return ErrorHash; + } + + if (moneyServResp == null || moneyServResp.IsFault) + { + Hashtable ErrorHash = new Hashtable(); + ErrorHash["success"] = false; + ErrorHash["errorMessage"] = "Failed to perform actions on OpenSim Server"; + ErrorHash["errorURI"] = ""; + return ErrorHash; + } + + Hashtable moneyRespData = (Hashtable)moneyServResp.Value; + return moneyRespData; + } + + private void UpdateBalance(string userID, string message) + { + string sessionID = string.Empty; + string secureID = string.Empty; + + // Konfiguriere das maximale Guthaben (dieser Wert kann aus einer Konfigurationsdatei wie MoneyServer.ini geladen werden) + //int m_CurrencyMaximum = m_CurrencyMaximum; + + if (m_sessionDic.ContainsKey(userID) && m_secureSessionDic.ContainsKey(userID)) + { + sessionID = m_sessionDic[userID]; + secureID = m_secureSessionDic[userID]; + + // Aktuelles Guthaben des Benutzers abrufen + int currentBalance = m_moneyDBService.getBalance(userID); + + // Überprüfen, ob das Guthaben über dem Maximum liegt und ggf. abziehen + if (currentBalance > m_CurrencyMaximum) + { + int excessAmount = currentBalance - m_CurrencyMaximum; + + // Guthaben auf das Maximum reduzieren + bool balanceUpdateSuccess = ReduceUserBalance(userID, excessAmount); + if (!balanceUpdateSuccess) + { + m_log.ErrorFormat("[UpdateBalance]: Error reducing user balance for user {0}", userID); + return; + } + + currentBalance = m_CurrencyMaximum; + m_log.InfoFormat("[UpdateBalance]: Reduced balance for user {0} by {1} to enforce maximum limit of {2}", userID, excessAmount, m_CurrencyMaximum); + } + + Hashtable requestTable = new Hashtable(); + requestTable["clientUUID"] = userID; + requestTable["clientSessionID"] = sessionID; + requestTable["clientSecureSessionID"] = secureID; + requestTable["Balance"] = currentBalance; + if (message != "") requestTable["Message"] = message; + + UserInfo user = m_moneyDBService.FetchUserInfo(userID); + if (user != null) + { + genericCurrencyXMLRPCRequest(requestTable, "UpdateBalance", user.SimIP); + m_log.InfoFormat("[MONEY XMLRPC]: UpdateBalance: Sent UpdateBalance Request to {0}", user.SimIP.ToString()); + } + } + } + + private bool ReduceUserBalance(string userID, int amount) + { + string sql = "UPDATE balances SET balance = balance - ?amount WHERE user = ?userID"; + MySQLSuperManager dbm = GetLockedConnection(); + + try + { + using (MySqlCommand cmd = new MySqlCommand(sql, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?amount", amount); + cmd.Parameters.AddWithValue("?userID", userID); + + int rowsAffected = cmd.ExecuteNonQuery(); + return rowsAffected > 0; + } + } + catch (Exception ex) + { + m_log.ErrorFormat("[ReduceUserBalance]: Error reducing user balance: {0}", ex.Message); + return false; + } + finally + { + dbm.Release(); + } + } + + + protected bool RollBackTransaction(TransactionData transaction) + { + if (m_moneyDBService.withdrawMoney(transaction.TransUUID, transaction.Receiver, transaction.Amount)) + { + if (m_moneyDBService.giveMoney(transaction.TransUUID, transaction.Sender, transaction.Amount)) + { + m_log.InfoFormat("[MONEY XMLRPC]: RollBackTransaction: Transaction {0} is successfully.", transaction.TransUUID.ToString()); + m_moneyDBService.updateTransactionStatus(transaction.TransUUID, (int)Status.FAILED_STATUS, + "The buyer failed to get the object, roll back the transaction"); + UserInfo senderInfo = m_moneyDBService.FetchUserInfo(transaction.Sender); + UserInfo receiverInfo = m_moneyDBService.FetchUserInfo(transaction.Receiver); + string senderName = "unknown user"; + string receiverName = "unknown user"; + if (senderInfo != null) senderName = senderInfo.Avatar; + if (receiverInfo != null) receiverName = receiverInfo.Avatar; + + string snd_message = string.Format(m_BalanceMessageRollBack, transaction.Amount, receiverName, transaction.ObjectName); + string rcv_message = string.Format(m_BalanceMessageRollBack, transaction.Amount, senderName, transaction.ObjectName); + + if (transaction.Sender != transaction.Receiver) UpdateBalance(transaction.Sender, snd_message); + UpdateBalance(transaction.Receiver, rcv_message); + return true; + } + } + return false; + } + + // ######################################### + // Cashbook ausgabe Transaktionen der User. + // ######################################### + + public void GetCashbookBalance(string userID) + { + int balance = 0; + string query = "SELECT balance FROM balances WHERE user = ?userID"; + + MySQLSuperManager dbm = ((MoneyDBService)m_moneyDBService).GetLockedConnection(); + + try + { + using (MySqlCommand cmd = new MySqlCommand(query, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?userID", userID); + object result = cmd.ExecuteScalar(); + m_log.InfoFormat("[Cashbook]: Query executed for userID: {0}, result: {1}", userID, result); + if (result != null) + { + balance = Convert.ToInt32(result); + } + } + } + catch (Exception ex) + { + m_log.ErrorFormat("[Cashbook]: Error getting balance for userID: {0}. Exception: {1}", userID, ex); + } + finally + { + dbm.Release(); + } + + m_log.InfoFormat("[Cashbook]: User: {0}, Balance: {1}", userID, balance); + } + + + public void GetCashbookTotalSales(string userID) + { + List totalSalesList = new List(); + string query = "SELECT objectUUID, TotalCount, TotalAmount FROM totalsales WHERE user = ?userID"; + + MySQLSuperManager dbm = ((MoneyDBService)m_moneyDBService).GetLockedConnection(); + + try + { + using (MySqlCommand cmd = new MySqlCommand(query, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?userID", userID); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + CashbookTotalSalesData data = new CashbookTotalSalesData + { + ObjectUUID = reader.GetString("objectUUID"), + TotalCount = reader.GetInt32("TotalCount"), + TotalAmount = reader.GetInt32("TotalAmount") + }; + totalSalesList.Add(data); + m_log.InfoFormat("[Cashbook]: Found sale: ObjectUUID={0}, TotalCount={1}, TotalAmount={2}", data.ObjectUUID, data.TotalCount, data.TotalAmount); + } + } + } + } + catch (Exception ex) + { + m_log.ErrorFormat("[Cashbook]: Error getting total sales for userID: {0}. Exception: {1}", userID, ex); + } + finally + { + dbm.Release(); + } + + m_log.InfoFormat("[Cashbook]: User: {0}, Total Sales:", userID); + foreach (var sale in totalSalesList) + { + m_log.InfoFormat("ObjectUUID: {0}, TotalCount: {1}, TotalAmount: {2}", sale.ObjectUUID, sale.TotalCount, sale.TotalAmount); + } + } + + + + public void GetCashbookTransactions(string userID) + { + List transactionList = new List(); + string query = "SELECT receiver, amount, senderBalance, receiverBalance, objectName, commonName, description FROM transactions WHERE sender = ?userID OR receiver = ?userID"; + + MySQLSuperManager dbm = ((MoneyDBService)m_moneyDBService).GetLockedConnection(); + + try + { + using (MySqlCommand cmd = new MySqlCommand(query, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?userID", userID); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + CashbookTransactionData data = new CashbookTransactionData + { + Receiver = reader.GetString("receiver"), + Amount = reader.GetInt32("amount"), + SenderBalance = reader.GetInt32("senderBalance"), + ReceiverBalance = reader.GetInt32("receiverBalance"), + ObjectName = reader.GetString("objectName"), + CommonName = reader.GetString("commonName"), + Description = reader.GetString("description") + }; + transactionList.Add(data); + m_log.InfoFormat("[Cashbook]: Found transaction: Receiver={0}, Amount={1}, SenderBalance={2}, ReceiverBalance={3}, ObjectName={4}, CommonName={5}, Description={6}", + data.Receiver, data.Amount, data.SenderBalance, data.ReceiverBalance, data.ObjectName, data.CommonName, data.Description); + } + } + } + } + catch (Exception ex) + { + m_log.ErrorFormat("[Cashbook]: Error getting transactions for userID: {0}. Exception: {1}", userID, ex); + } + finally + { + dbm.Release(); + } + + m_log.InfoFormat("[Cashbook]: User: {0}, Transactions:", userID); + foreach (var transaction in transactionList) + { + m_log.InfoFormat("Receiver: {0}, Amount: {1}, SenderBalance: {2}, ReceiverBalance: {3}, ObjectName: {4}, CommonName: {5}, Description: {6}", + transaction.Receiver, transaction.Amount, transaction.SenderBalance, transaction.ReceiverBalance, transaction.ObjectName, transaction.CommonName, transaction.Description); + } + } + + + + // Aktualisierte Methode zur Initialisierung der Konsolenbefehle + public void RegisterConsoleCommands(ICommandConsole console) + { + console.Commands.AddCommand( + "MoneyXmlRpcModule", + false, + "getbalance", + "getbalance or ", + "Get the balance for the specified user", + HandleGetCashbookBalance); + + console.Commands.AddCommand( + "MoneyXmlRpcModule", + false, + "gettotalsales", + "gettotalsales or ", + "Get the total sales for the specified user", + HandleGetCashbookTotalSales); + + console.Commands.AddCommand( + "MoneyXmlRpcModule", + false, + "gettransactions", + "gettransactions or ", + "Get the transactions for the specified user", + HandleGetCashbookTransactions); + } + + private void HandleGetCashbookBalance(string module, string[] cmdparams) + { + if (cmdparams.Length < 3) + { + m_log.Info("[Cashbook]: Usage: getbalance or "); + return; + } + + string userID = string.Join(" ", cmdparams, 2, cmdparams.Length - 2).Trim(); + + // Prüfen, ob userID eine gültige UUID ist + if (Guid.TryParse(userID, out Guid _)) + { + m_log.InfoFormat("[Cashbook]: userID is a valid UUID: {0}", userID); + GetCashbookBalance(userID); + } + else + { + m_log.InfoFormat("[Cashbook]: userID is a name: {0}", userID); + GetCashbookBalanceByName(userID); + } + } + + private void HandleGetCashbookTotalSales(string module, string[] cmdparams) + { + if (cmdparams.Length < 3) + { + m_log.Info("[Cashbook]: Usage: gettotalsales or "); + return; + } + + string userID = string.Join(" ", cmdparams, 2, cmdparams.Length - 2).Trim(); + + // Prüfen, ob userID eine gültige UUID ist + if (Guid.TryParse(userID, out Guid _)) + { + m_log.InfoFormat("[Cashbook]: userID is a valid UUID: {0}", userID); + GetCashbookTotalSales(userID); + } + else + { + m_log.InfoFormat("[Cashbook]: userID is a name: {0}", userID); + GetCashbookTotalSalesByName(userID); + } + } + + private void HandleGetCashbookTransactions(string module, string[] cmdparams) + { + if (cmdparams.Length < 3) + { + m_log.Info("[Cashbook]: Usage: gettransactions or "); + return; + } + + string userID = string.Join(" ", cmdparams, 2, cmdparams.Length - 2).Trim(); + + // Prüfen, ob userID eine gültige UUID ist + if (Guid.TryParse(userID, out Guid _)) + { + m_log.InfoFormat("[Cashbook]: userID is a valid UUID: {0}", userID); + GetCashbookTransactions(userID); + } + else + { + m_log.InfoFormat("[Cashbook]: userID is a name: {0}", userID); + GetCashbookTransactionsByName(userID); + } + } + + private void GetCashbookBalanceByName(string name) + { + string userID = GetUUIDFromName(name); + if (string.IsNullOrEmpty(userID)) + { + m_log.InfoFormat("[Cashbook]: No UUID found for user: {0}", name); + return; + } + GetCashbookBalance(userID); + } + + private void GetCashbookTotalSalesByName(string name) + { + string userID = GetUUIDFromName(name); + if (string.IsNullOrEmpty(userID)) + { + m_log.InfoFormat("[Cashbook]: No UUID found for user: {0}", name); + return; + } + GetCashbookTotalSales(userID); + } + + private void GetCashbookTransactionsByName(string name) + { + string userID = GetUUIDFromName(name); + if (string.IsNullOrEmpty(userID)) + { + m_log.InfoFormat("[Cashbook]: No UUID found for user: {0}", name); + return; + } + GetCashbookTransactions(userID); + } + + private string GetUUIDFromName(string name) + { + string query = "SELECT PrincipalID FROM UserAccounts WHERE CONCAT(FirstName, ' ', LastName) = ?name"; + string userID = null; + + MySQLSuperManager dbm = ((MoneyDBService)m_moneyDBService).GetLockedConnection(); + + try + { + using (MySqlCommand cmd = new MySqlCommand(query, dbm.Manager.dbcon)) + { + cmd.Parameters.AddWithValue("?name", name); + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + userID = reader.GetString("PrincipalID"); + } + } + } + } + catch (Exception ex) + { + m_log.ErrorFormat("[Cashbook]: Error getting UUID for name: {0}. Exception: {1}", name, ex); + } + finally + { + dbm.Release(); + } + + return userID; + } + + + + #endregion + + } + +} +// ################## classes ################## +#region classes + +public class CashbookTotalSalesData +{ + public string ObjectUUID { get; set; } + public int TotalCount { get; set; } + public int TotalAmount { get; set; } +} + +public class CashbookTransactionData +{ + public string Receiver { get; set; } + public int Amount { get; set; } + public int SenderBalance { get; set; } + public int ReceiverBalance { get; set; } + public string ObjectName { get; set; } + public string CommonName { get; set; } + public string Description { get; set; } +} + +public class CurrencyQuoteRequest +{ + public string AgentId { get; set; } + public int CurrencyBuy { get; set; } + public string Language { get; set; } + public string SecureSessionId { get; set; } + public string ViewerBuildVersion { get; set; } + public string ViewerChannel { get; set; } + public int ViewerMajorVersion { get; set; } + public int ViewerMinorVersion { get; set; } + public int ViewerPatchVersion { get; set; } +} + +public class LandPurchaseRequest +{ + public string AgentId { get; set; } + public int BillableArea { get; set; } + public int CurrencyBuy { get; set; } + public string Language { get; set; } + public string SecureSessionId { get; set; } +} + +public class BuyCurrencyRequest +{ + /// + /// Initializes a new instance of the class. + /// + public BuyCurrencyRequest() + { + // Initialize any default values or properties here + } + public string AgentId { get; set; } + public int CurrencyBuy { get; set; } + public string Language { get; set; } + public string SecureSessionId { get; set; } + public string ViewerBuildVersion { get; set; } + public string ViewerChannel { get; set; } + public int ViewerMajorVersion { get; set; } + public int ViewerMinorVersion { get; set; } + public int ViewerPatchVersion { get; set; } + + public decimal Amount { get; set; } + + public string CurrencyType { get; set; } + + public bool IsValid() + { + // Add validation logic here, e.g., check for null or empty values + return Amount > 0 && !string.IsNullOrEmpty(CurrencyType); + } +} +#endregion diff --git a/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/Program.cs b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/Program.cs new file mode 100644 index 0000000..74f7905 --- /dev/null +++ b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/Program.cs @@ -0,0 +1,74 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Funktion + Purpose: Dies ist der Einstiegspunkt (Main-Methode) für die Anwendung OpenSim.Grid.MoneyServer. + Ablauf: + Konfiguriert das Logging mit XmlConfigurator.Configure(). + Erstellt eine Instanz von MoneyServerBase. + Startet den Server mit app.Startup() und ruft danach app.Work() auf. + Bei Fehlern wird eine Exception abgefangen und die Fehlermeldung auf der Konsole ausgegeben. + +Null-Pointer-Prüfung und Fehlerquellen + app != null: Nach der Instanziierung von MoneyServerBase wird geprüft, ob die Instanz erfolgreich erstellt wurde (obwohl in C# ein Konstruktor normalerweise nicht null liefern kann, außer bei sehr speziellen Fällen wie einem Factory-Pattern). + Exception Handling: Der gesamte Ablauf ist in einem try-catch Block gekapselt. Jede Exception (auch NullReferenceException) wird abgefangen und eine lesbare Fehlermeldung auf die Konsole ausgegeben. + Fehlerfall: Sollte MoneyServerBase nicht erstellt werden können, gibt es eine eigene Fehlermeldung ("Failed to create MoneyServerBase instance."). + Keine weiteren Ressourcenlecks: Es gibt keine expliziten Ressourcen (wie Files/DB-Connections), die in dieser Datei geöffnet und geschlossen werden müssten. + +Zusammenfassung + NullPointer: Praktisch ausgeschlossen, da alle kritischen Objekte direkt initialisiert oder auf null geprüft werden. Fehler werden sauber abgefangen. + Fehlerquellen: Nur wenn der Konstruktor oder nachfolgende Aufrufe von Startup() oder Work() eine Exception werfen, erscheint eine Fehlermeldung auf der Konsole. + Funktion: Startet den MoneyServer und hält ihn im Betrieb. + +Fazit: +Der Code in Program.cs ist sehr robust gegen NullPointer und allgemeine Fehler. Die Funktion ist klar: Start und Lebenszyklusmanagement des MoneyServers. +Es gibt keine offensichtlichen Schwachstellen oder Verbesserungsbedarf in Bezug auf NullPointer oder Fehlerbehandlung. + */ + +using log4net.Config; + +using System; + +namespace OpenSim.Grid.MoneyServer +{ + class Program + { + /// Defines the entry point of the application. + /// The arguments. + public static void Main(string[] args) + { + try + { + XmlConfigurator.Configure(); + MoneyServerBase app = new MoneyServerBase(); + if (app != null) + { + app.Startup(); + app.Work(); + } + else + { + Console.WriteLine("Failed to create MoneyServerBase instance."); + } + } + catch (Exception ex) + { + Console.WriteLine("An error occurred: " + ex.Message); + // You can also log the exception here, e.g., using a logging framework + } + } + } +} diff --git a/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/Properties/AssemblyInfo.cs b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c5604a9 --- /dev/null +++ b/addon-modules/OpenSim-Grid-MoneyServer/OpenSim.Grid.MoneyServer/Properties/AssemblyInfo.cs @@ -0,0 +1,34 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Mono.Addins; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("OpenSim.Grid.MoneyServer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("OpenSim.Grid.MoneyServer")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2009")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f554c84a-d8c7-41ea-833d-2483cde29e0f")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/addon-modules/OpenSim-Grid-MoneyServer/prebuild-MoneyServer.xml b/addon-modules/OpenSim-Grid-MoneyServer/prebuild-MoneyServer.xml new file mode 100644 index 0000000..c285575 --- /dev/null +++ b/addon-modules/OpenSim-Grid-MoneyServer/prebuild-MoneyServer.xml @@ -0,0 +1,45 @@ + + + + + ../../bin/ + true + + + + + ../../bin/ + true + + + + ../../bin/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/addon-modules/OpenSim-Modules-Currency/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs b/addon-modules/OpenSim-Modules-Currency/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs new file mode 100644 index 0000000..0adea50 --- /dev/null +++ b/addon-modules/OpenSim-Modules-Currency/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs @@ -0,0 +1,2246 @@ +/* Modified by Fumi.Iseki for Unix/Linix http://www.nsl.tuis.ac.jp + * + * Copyright (c) Contributors, http://opensimulator.org/, http://www.nsl.tuis.ac.jp/ See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Funktion + DTLNSLMoneyModule ist ein OpenSim-Regionmodul für die Verwaltung eines eigenen Währungssystems und die Anbindung an einen externen MoneyServer. + Es implementiert die Schnittstellen IMoneyModule und ISharedRegionModule. + Kernfunktionen: + Verwaltung von Guthaben (Balance), Transaktionen und Gebühren für verschiedene In-World-Aktionen (z.B. Objektkauf, Landkauf, Uploads). + Kommunikation mit einem externen MoneyServer via XML-RPC (Transfer, Login, Logoff, Balance-Abfrage, etc.). + Ereignis- und Event-Handling für Inworld-Transaktionen. + Zertifikatsverwaltung für sichere Kommunikation. + +Null Pointer Checks + Konfigurationswerte: Werden mit Defaultwerten initialisiert (string.Empty, false, 0 usw.). Viele Konfigurationswerte werden mit Fallback-Werten geladen. + Methoden mit Objektrückgabe wie GetLocateClient, GetLocateScene, GetLocatePrim: + Es wird geprüft, ob Rückgabewerte null sind, bevor sie genutzt werden (z.B. in ObjectGiveMoney, Transfer, etc.). + Client- und Scene-Objekte: + Wird ein Objekt nicht gefunden, wird mit return oder Fehlerwerten sauber abgebrochen. + RPC-Handler: + Prüfen, ob erforderliche Felder im Parameter-Hashtable existieren, bevor sie genutzt werden. + Try-Catch bei XML-RPC: + Netzwerkfehler und Exceptions werden sauber abgefangen und führen zu Fehler-Hash als Rückgabe. + Event-Handler: + Überall wird geprüft, ob das Event-Objekt oder der Client existiert, bevor darauf zugegriffen wird. + Beispiel: + C# + + SceneObjectPart sceneObj = GetLocatePrim(objectID); + if (sceneObj == null) return false; + + Rückgabewerte können null sein: + Es wird überall mit null als Fehlerfall gerechnet (besonders bei Lookups und DB/Network-Kommunikation). + +Fehlerquellen und deren Behandlung + Konfiguration: + Fehlende oder falsche Konfiguration wird geloggt und führt zum Abbruch der Initialisierung. + RPC-Fehler: + Bei Netzwerkproblemen oder fehlerhaften Antworten vom MoneyServer wird immer ein Fehlerobjekt erstellt und ausführlich geloggt. + Fehlende Objekte/Clients: + Fast überall wird geprüft, ob Objekte, Clients oder Rückgabewerte wirklich existieren, bevor sie verwendet werden. + Fehlende Berechtigungen oder unzulässige Aktionen: + Z.B. if (!m_sellEnabled) return; – Aktionen werden abgebrochen, wenn sie nicht erlaubt sind. + Transaktionsfehler: + Jede Transaktion prüft, ob die Übertragung erfolgreich war, und gibt false zurück oder loggt den Fehler. + +Typische Pattern für Sicherheit und Fehlervermeidung + Locking: + Zugriff auf die Szenenliste ist mit lock (m_sceneList) geschützt. + Null-Checks: + Überall vor der Nutzung von Rückgabewerten, Event-Objekten, Parametern. + Logging: + Fehler und Sonderfälle werden ausführlich geloggt. + Try/Catch: + Insbesondere bei externen Netzwerkzugriffen. + +Zusammenfassung & Bewertung + Null Pointer: + Der gesamte Code ist sehr gewissenhaft in Bezug auf Null Pointer – überall werden Objekte auf null geprüft, bevor sie verwendet werden. + Fehlerquellen: + Externe Fehler (Netzwerk, Konfiguration) werden geloggt und führen zu sauberem Abbruch. Rückgabewerte im Fehlerfall (z.B. null oder false) sind klar definiert und werden behandelt. + Funktion: + Sehr umfangreiches Modul zur sicheren Verwaltung von Währungen und Transaktionen in OpenSim, mit Anbindung an einen externen Server und vielen Schutzmechanismen. + +Fazit: +Der Code ist robust gegenüber NullPointerException und typischen Fehlern. Fehlerquellen werden gut abgefangen, Logging ist umfassend. +Die gesamte Struktur entspricht gängiger C#-Best-Practice für modulare, fehlertolerante Server-Module. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Reflection; +using System.Net; +using System.Security.Cryptography; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using Mono.Addins; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Services.Interfaces; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; +using NSL.Certificate.Tools; +using NSL.Network.XmlRpc; +using System.IO; + + +[assembly: Addin("DTLNSLMoneyModule", "1.0")] +[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)] + +namespace OpenSim.Modules.Currency +{ + /// + /// Transaction Type + /// + public enum TransactionType : int + { + None = 0, + // Extend + BirthGift = 900, + AwardPoints = 901, + // One-Time Charges + ObjectClaim = 1000, + LandClaim = 1001, + GroupCreate = 1002, + GroupJoin = 1004, + TeleportCharge = 1100, + UploadCharge = 1101, + LandAuction = 1102, + ClassifiedCharge = 1103, + // Recurrent Charges + ObjectTax = 2000, + LandTax = 2001, + LightTax = 2002, + ParcelDirFee = 2003, + GroupTax = 2004, + ClassifiedRenew = 2005, + ScheduledFee = 2900, + // Inventory Transactions + GiveInventory = 3000, + // Transfers Between Users + ObjectSale = 5000, + Gift = 5001, + LandSale = 5002, + ReferBonus = 5003, + InvntorySale = 5004, + RefundPurchase = 5005, + LandPassSale = 5006, + DwellBonus = 5007, + PayObject = 5008, + ObjectPays = 5009, + BuyMoney = 5010, + MoveMoney = 5011, + SendMoney = 5012, + // Group Transactions + GroupLandDeed = 6001, + GroupObjectDeed = 6002, + GroupLiability = 6003, + GroupDividend = 6004, + GroupMembershipDues = 6005, + // Stipend Credits + StipendBasic = 10000 + } + + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "DTLNSLMoneyModule")] + public class DTLNSLMoneyModule : IMoneyModule, ISharedRegionModule + { + // Constant memebers + private const int MONEYMODULE_REQUEST_TIMEOUT = 10000; + + // Private data members. + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + //private bool m_enabled = true; + private bool m_sellEnabled = false; + private bool m_enable_server = true; // enable Money Server + + private IConfigSource m_config; + + private string m_moneyServURL = string.Empty; + public BaseHttpServer HttpServer; + + private string m_certFilename = ""; + private string m_certPassword = ""; + private bool m_checkServerCert = false; + private string m_cacertFilename = ""; + + private bool m_use_web_settle = false; + private string m_settle_url = ""; + private string m_settle_message = ""; + private bool m_settle_user = false; + + private int m_hg_avatarClass = (int)AvatarType.HG_AVATAR; + + private NSLCertificateVerify m_certVerify = new NSLCertificateVerify(); // For server authentication + + private Dictionary m_sceneList = new Dictionary(); + + private Dictionary m_moneyServer = new Dictionary(); + + // Events + public event ObjectPaid OnObjectPaid; + + // Price + private int ObjectCount = 0; + private int PriceEnergyUnit = 100; + private int PriceObjectClaim = 10; + private int PricePublicObjectDecay = 4; + private int PricePublicObjectDelete = 4; + private int PriceParcelClaim = 1; + private float PriceParcelClaimFactor = 1.0f; + private int PriceUpload = 0; + private int PriceRentLight = 5; + private float PriceObjectRent = 1.0f; + private float PriceObjectScaleFactor = 10.0f; + private int PriceParcelRent = 1; + private int PriceGroupCreate = 0; + private int TeleportMinPrice = 2; + private float TeleportPriceExponent = 2.0f; + private float EnergyEfficiency = 1.0f; + private int PriceLandTax = 0; + private int PriceLand = 0; + private int PriceCurrency = 0; + + /// The m RPC handlers + private Dictionary m_rpcHandlers; + + /// + /// Initializes the specified scene. + /// + /// The scene to initialize. + /// The source of the configuration. + /// + /// This method calls the Initialize method with the source parameter, + /// then checks if the money server URL is null or empty. If so, it sets + /// the enable_server flag to false. Finally, it adds the scene to the region. + /// + public void Initialise(Scene scene, IConfigSource source) + { + // Call the Initialize method with the source parameter + Initialise(source); + + // Check if the money server URL is null or empty + //if (string.IsNullOrEmpty(m_moneyServURL)) m_enable_server = false; + if (string.IsNullOrEmpty(m_moneyServURL)) + { + m_log.ErrorFormat("[MONEY MODULE]: CurrencyServer URL not set."); + m_enable_server = false; + } + + // Add the scene to the region + AddRegion(scene); + } + + /// + /// This is called to initialize the region module. For shared modules, this is called + /// exactly once, after creating the single (shared) instance. For non-shared modules, + /// this is called once on each instance, after the instace for the region has been created. + /// + /// A + public void Initialise(IConfigSource source) + { + m_log.InfoFormat("[MONEY MODULE]: Initialise started."); + + // Überprüfen, ob die Konfigurationsquelle null ist. + if (source == null) + { + m_log.ErrorFormat("[MONEY MODULE]: Initialise aborted - source is null."); + return; + } + + try + { + m_config = source; + + // Economy-Konfiguration abrufen + IConfig economyConfig = m_config.Configs["Economy"]; + if (economyConfig == null) + { + m_log.ErrorFormat("[MONEY MODULE]: Initialise aborted - [Economy] section is missing in configuration."); + return; + } + + // Überprüfen, ob das Modul aktiviert ist + if (economyConfig.GetString("EconomyModule") != Name) + { + m_log.InfoFormat("[MONEY MODULE]: Initialise - DTL/NSL MoneyModule is disabled."); + return; + } + + m_log.InfoFormat("[MONEY MODULE]: Initialise - DTL/NSL MoneyModule is enabled."); + + // Konfiguration für Verkauf und MoneyServer-URL + m_sellEnabled = economyConfig.GetBoolean("SellEnabled", m_sellEnabled); + m_log.InfoFormat("[MONEY MODULE]: SellEnabled set to {0}", m_sellEnabled); + + m_moneyServURL = economyConfig.GetString("CurrencyServer", m_moneyServURL); + m_log.InfoFormat("[MONEY MODULE]: CurrencyServer set to {0}", m_moneyServURL); + + // Konfiguration für Client-Zertifizierung + m_certFilename = economyConfig.GetString("ClientCertFilename", m_certFilename); + m_certPassword = economyConfig.GetString("ClientCertPassword", m_certPassword); + if (!string.IsNullOrEmpty(m_certFilename)) + { + m_certVerify.SetPrivateCert(m_certFilename, m_certPassword); + m_log.InfoFormat("[MONEY MODULE]: Client certificate set from file {0}", m_certFilename); + } + else + { + m_log.Warn("[MONEY MODULE]: No client certificate filename provided."); + } + + // Konfiguration für Server-Zertifikatüberprüfung + m_checkServerCert = economyConfig.GetBoolean("CheckServerCert", m_checkServerCert); + m_cacertFilename = economyConfig.GetString("CACertFilename", m_cacertFilename); + + if (!string.IsNullOrEmpty(m_cacertFilename)) + { + m_certVerify.SetPrivateCA(m_cacertFilename); + m_log.InfoFormat("[MONEY MODULE]: Server CA certificate loaded from {0}", m_cacertFilename); + } + else + { + m_checkServerCert = false; + m_log.Warn("[MONEY MODULE]: No CA certificate filename provided; server certificate check disabled."); + } + + // Konfiguration für Settlement + m_use_web_settle = economyConfig.GetBoolean("SettlementByWeb", m_use_web_settle); + m_log.InfoFormat("[MONEY MODULE]: SettlementByWeb set to {0}", m_use_web_settle); + + m_settle_url = economyConfig.GetString("SettlementURL", m_settle_url); + m_log.InfoFormat("[MONEY MODULE]: SettlementURL set to {0}", m_settle_url); + + m_settle_message = economyConfig.GetString("SettlementMessage", m_settle_message); + m_log.InfoFormat("[MONEY MODULE]: SettlementMessage set to {0}", m_settle_message); + + // Preise konfigurieren + PriceEnergyUnit = economyConfig.GetInt("PriceEnergyUnit", PriceEnergyUnit); + PriceObjectClaim = economyConfig.GetInt("PriceObjectClaim", PriceObjectClaim); + PricePublicObjectDecay = economyConfig.GetInt("PricePublicObjectDecay", PricePublicObjectDecay); + PricePublicObjectDelete = economyConfig.GetInt("PricePublicObjectDelete", PricePublicObjectDelete); + PriceParcelClaim = economyConfig.GetInt("PriceParcelClaim", PriceParcelClaim); + PriceParcelClaimFactor = economyConfig.GetFloat("PriceParcelClaimFactor", PriceParcelClaimFactor); + PriceUpload = economyConfig.GetInt("PriceUpload", PriceUpload); + PriceRentLight = economyConfig.GetInt("PriceRentLight", PriceRentLight); + PriceObjectRent = economyConfig.GetFloat("PriceObjectRent", PriceObjectRent); + PriceObjectScaleFactor = economyConfig.GetFloat("PriceObjectScaleFactor", PriceObjectScaleFactor); + PriceParcelRent = economyConfig.GetInt("PriceParcelRent", PriceParcelRent); + + PriceLand = economyConfig.GetInt("PriceLand", PriceLand); // Test + PriceCurrency = economyConfig.GetInt("PriceCurrency", PriceCurrency); // Test + PriceLandTax = economyConfig.GetInt("PriceLandTax", PriceLandTax); // Test + + PriceGroupCreate = economyConfig.GetInt("PriceGroupCreate", PriceGroupCreate); + TeleportMinPrice = economyConfig.GetInt("TeleportMinPrice", TeleportMinPrice); + TeleportPriceExponent = economyConfig.GetFloat("TeleportPriceExponent", TeleportPriceExponent); + EnergyEfficiency = economyConfig.GetFloat("EnergyEfficiency", EnergyEfficiency); + m_log.InfoFormat("[MONEY MODULE]: Price settings loaded successfully."); + + // Konfiguration für HG-Avatar-Typ + string avatarClass = economyConfig.GetString("HGAvatarAs", "HGAvatar").ToLower(); + m_hg_avatarClass = avatarClass switch + { + "localavatar" => (int)AvatarType.LOCAL_AVATAR, + "guestavatar" => (int)AvatarType.GUEST_AVATAR, + "hgavatar" => (int)AvatarType.HG_AVATAR, + "foreignavatar" => (int)AvatarType.FOREIGN_AVATAR, + _ => (int)AvatarType.UNKNOWN_AVATAR + }; + + m_log.InfoFormat("[MONEY MODULE]: Initialise - Configuration loaded successfully."); + } + catch (Exception ex) + { + m_log.ErrorFormat("[MONEY MODULE]: Initialise - Failed to load configuration. Error: {0}", ex); + } + } + + /// + /// This is called whenever a is added. For shared modules, this can happen several times. + /// For non-shared modules, this happens exactly once, after has been called. + /// + /// A + public void AddRegion(Scene scene) + { + m_log.InfoFormat("[MONEY MODULE]: AddRegion:"); + + if (scene == null) return; + + scene.RegisterModuleInterface(this); // Eliminate conflicting modules + + lock (m_sceneList) + { + if (m_sceneList.Count == 0) + { + if (m_enable_server) + { + HttpServer = new BaseHttpServer(9000); + HttpServer.AddStreamHandler(new Region.Framework.Scenes.RegionStatsHandler(scene.RegionInfo)); + + HttpServer.AddXmlRPCHandler("OnMoneyTransfered", OnMoneyTransferedHandler); + HttpServer.AddXmlRPCHandler("UpdateBalance", BalanceUpdateHandler); + HttpServer.AddXmlRPCHandler("UserAlert", UserAlertHandler); + HttpServer.AddXmlRPCHandler("GetBalance", GetBalanceHandler); + HttpServer.AddXmlRPCHandler("AddBankerMoney", AddBankerMoneyHandler); + HttpServer.AddXmlRPCHandler("SendMoney", SendMoneyHandler); + HttpServer.AddXmlRPCHandler("MoveMoney", MoveMoneyHandler); + + m_rpcHandlers = new Dictionary(); + + MainServer.Instance.AddXmlRPCHandler("OnMoneyTransfered", OnMoneyTransferedHandler); + MainServer.Instance.AddXmlRPCHandler("UpdateBalance", BalanceUpdateHandler); + MainServer.Instance.AddXmlRPCHandler("UserAlert", UserAlertHandler); + MainServer.Instance.AddXmlRPCHandler("GetBalance", GetBalanceHandler); + MainServer.Instance.AddXmlRPCHandler("AddBankerMoney", AddBankerMoneyHandler); + MainServer.Instance.AddXmlRPCHandler("SendMoney", SendMoneyHandler); + MainServer.Instance.AddXmlRPCHandler("MoveMoney", MoveMoneyHandler); + + } + } + + if (m_sceneList.ContainsKey(scene.RegionInfo.RegionHandle)) + { + m_sceneList[scene.RegionInfo.RegionHandle] = scene; + } + else + { + m_sceneList.Add(scene.RegionInfo.RegionHandle, scene); + } + } + + scene.EventManager.OnNewClient += OnNewClient; + scene.EventManager.OnMakeRootAgent += OnMakeRootAgent; + scene.EventManager.OnMakeChildAgent += MakeChildAgent; + + // for OpenSim + scene.EventManager.OnMoneyTransfer += MoneyTransferAction; + scene.EventManager.OnValidateLandBuy += ValidateLandBuy; + scene.EventManager.OnLandBuy += processLandBuy; + + m_log.InfoFormat("[MONEY MODULE]: AddRegion: {0}", scene.RegionInfo.RegionName); + + } + + + /// + /// This is called whenever a is removed. For shared modules, this can happen several times. + /// For non-shared modules, this happens exactly once, if the scene this instance is associated with is removed. + /// + /// A + public void RemoveRegion(Scene scene) + { + if (scene == null) return; + + lock (m_sceneList) + { + scene.EventManager.OnNewClient -= OnNewClient; + scene.EventManager.OnMakeRootAgent -= OnMakeRootAgent; + scene.EventManager.OnMakeChildAgent -= MakeChildAgent; + + // for OpenSim + scene.EventManager.OnMoneyTransfer -= MoneyTransferAction; + scene.EventManager.OnValidateLandBuy -= ValidateLandBuy; + scene.EventManager.OnLandBuy -= processLandBuy; + + m_log.InfoFormat("[MONEY MODULE]: RemoveRegion: {0}", scene.RegionInfo.RegionName); + } + } + + + /// + /// This will be called once for every scene loaded. In a shared module + /// this will be multiple times in one instance, while a nonshared + /// module instance will only be called once. + /// This method is called after AddRegion has been called in all + /// modules for that scene, providing an opportunity to request + /// another module's interface, or hook an event from another module. + /// + /// A + public void RegionLoaded(Scene scene) + { + m_log.InfoFormat("[MONEY MODULE] region loaded {0}", scene.RegionInfo.RegionID.ToString()); + } + + + /// + /// If this returns non-null, it is the type of an interface that + /// this module intends to register. + /// This will cause the loader to defer loading of this module + /// until all other modules have been loaded. If no other module + /// has registered the interface by then, this module will be + /// activated, else it will remain inactive, letting the other module + /// take over. This should return non-null ONLY in modules that are + /// intended to be easily replaceable, e.g. stub implementations + /// that the developer expects to be replaced by third party provided + /// modules. + /// + public Type ReplaceableInterface + { + get { return null; } + } + + + /// Gets a value indicating whether this instance is shared module. + /// + /// true if this instance is shared module; otherwise, false. + public bool IsSharedModule + { + get { return true; } + } + + + /// + ///
+ ///
+ /// The name of the module + public string Name + { + get { return "DTLNSLMoneyModule"; } + } + + + /// + /// This is called exactly once after all the shared region-modules have been instanciated and + /// d. + /// + public void PostInitialise() + { + + } + + + /// + /// This is the inverse to . After a Close(), this instance won't be usable anymore. + /// + public void Close() + { + + } + + /// Objects the give money. + /// The object identifier. + /// From identifier. + /// To identifier. + /// The amount. + /// The TXN. + /// The result. + /// + ///
+ ///
+ public bool ObjectGiveMoney(UUID objectID, UUID fromID, UUID toID, int amount, UUID txn, out string result) + { + result = string.Empty; + if (!m_sellEnabled) + { + result = "LINDENDOLLAR_INSUFFICIENTFUNDS"; + return false; + } + + string objName = string.Empty; + string avatarName = string.Empty; + + SceneObjectPart sceneObj = GetLocatePrim(objectID); + if (sceneObj == null) + { + result = "LINDENDOLLAR_INSUFFICIENTFUNDS"; + return false; + } + objName = sceneObj.Name; + + Scene scene = GetLocateScene(toID); + if (scene != null) + { + UserAccount account = scene.UserAccountService.GetUserAccount(scene.RegionInfo.ScopeID, toID); + if (account != null) + { + avatarName = account.FirstName + " " + account.LastName; + } + } + + bool ret = false; + string description = String.Format("Object {0} pays {1}", objName, avatarName); + + if (sceneObj.OwnerID == fromID) + { + ulong regionHandle = sceneObj.RegionHandle; + UUID regionUUID = sceneObj.RegionID; + if (GetLocateClient(fromID) != null) + { + ret = TransferMoney(fromID, toID, amount, (int)TransactionType.ObjectPays, objectID, regionHandle, regionUUID, description); + } + else + { + ret = ForceTransferMoney(fromID, toID, amount, (int)TransactionType.ObjectPays, objectID, regionHandle, regionUUID, description); + } + } + + if (!ret) result = "LINDENDOLLAR_INSUFFICIENTFUNDS"; + + m_log.InfoFormat("[MONEY MODULE] ObjectGiveMoney: {0} {1} {2} {3} {4} {5} {6}", objectID, fromID, toID, amount, txn, result, ret); + + return ret; + } + + + // + /// Gets the upload charge. + /// The upload charge. + public int UploadCharge + { + get { return PriceUpload; } + } + + + // + /// Gets the group creation charge. + /// The group creation charge. + public int GroupCreationCharge + { + get { return PriceGroupCreate; } + } + + + /// Gets the balance. + /// The agent identifier. + public int GetBalance(UUID agentID) + { + IClientAPI client = GetLocateClient(agentID); + return QueryBalanceFromMoneyServer(client); + } + + + /// Uploads the covered. + /// The agent identifier. + /// The amount. + public bool UploadCovered(UUID agentID, int amount) + { + IClientAPI client = GetLocateClient(agentID); + + if (m_enable_server || string.IsNullOrEmpty(m_moneyServURL)) + { + int balance = QueryBalanceFromMoneyServer(client); + if (balance >= amount) return true; + } + + m_log.InfoFormat("[MONEY MODULE] UploadCovered: {0} {1}", agentID, amount); + + return false; + } + + + /// Amounts the covered. + /// The agent identifier. + /// The amount. + public bool AmountCovered(UUID agentID, int amount) + { + IClientAPI client = GetLocateClient(agentID); + + if (m_enable_server || string.IsNullOrEmpty(m_moneyServURL)) + { + int balance = QueryBalanceFromMoneyServer(client); + if (balance >= amount) return true; + } + + m_log.InfoFormat("[MONEY MODULE] AmountCovered: {0} {1}", agentID, amount); + + return false; + } + + + /// Applies the upload charge. + /// The agent identifier. + /// The amount. + /// The text. + public void ApplyUploadCharge(UUID agentID, int amount, string text) + { + ulong regionHandle = GetLocateScene(agentID).RegionInfo.RegionHandle; + UUID regionUUID = GetLocateScene(agentID).RegionInfo.RegionID; + PayMoneyCharge(agentID, amount, (int)TransactionType.UploadCharge, regionHandle, regionUUID, text); + + m_log.InfoFormat("[MONEY MODULE] ApplyUploadCharge: {0} {1} {2}", agentID, amount, text); + } + + + /// Applies the charge. + /// The agent identifier. + /// The amount. + /// The type. + public void ApplyCharge(UUID agentID, int amount, MoneyTransactionType type) + { + ApplyCharge(agentID, amount, type, string.Empty); + + m_log.InfoFormat("[MONEY MODULE] ApplyCharge: {0} {1} {2}", agentID, amount, type); + } + + + /// Applies the charge. + /// The agent identifier. + /// The amount. + /// The type. + /// The text. + public void ApplyCharge(UUID agentID, int amount, MoneyTransactionType type, string text) + { + ulong regionHandle = GetLocateScene(agentID).RegionInfo.RegionHandle; + UUID regionUUID = GetLocateScene(agentID).RegionInfo.RegionID; + PayMoneyCharge(agentID, amount, (int)type, regionHandle, regionUUID, text); + + m_log.InfoFormat("[MONEY MODULE] ApplyCharge: {0} {1} {2} {3}", agentID, amount, type, text); + } + + + /// Transfers the specified from identifier. + /// From identifier. + /// To identifier. + /// The region handle. + /// The amount. + /// The type. + /// The text. + /// + ///
+ ///
+ public bool Transfer(UUID fromID, UUID toID, int regionHandle, int amount, MoneyTransactionType type, string text) + { + return TransferMoney(fromID, toID, amount, (int)type, UUID.Zero, (ulong)regionHandle, UUID.Zero, text); + } + + + /// Transfers the specified from identifier. + /// From identifier. + /// To identifier. + /// The object identifier. + /// The amount. + /// The type. + /// The text. + /// + ///
+ ///
+ public bool Transfer(UUID fromID, UUID toID, UUID objectID, int amount, MoneyTransactionType type, string text) + { + SceneObjectPart sceneObj = GetLocatePrim(objectID); + if (sceneObj == null) return false; + + ulong regionHandle = sceneObj.ParentGroup.Scene.RegionInfo.RegionHandle; + UUID regionUUID = sceneObj.ParentGroup.Scene.RegionInfo.RegionID; + return TransferMoney(fromID, toID, amount, (int)type, objectID, (ulong)regionHandle, regionUUID, text); + } + + + // for 0.8.3 over + /// Moves the money. + /// From agent identifier. + /// To agent identifier. + /// The amount. + /// The text. + public void MoveMoney(UUID fromAgentID, UUID toAgentID, int amount, string text) + { + ForceTransferMoney(fromAgentID, toAgentID, amount, (int)TransactionType.MoveMoney, UUID.Zero, (ulong)0, UUID.Zero, text); + + m_log.InfoFormat("[MONEY MODULE] MoveMoney: {0} {1} {2}", fromAgentID, toAgentID, amount); + } + + // for 0.9.1 over + /// Moves the money. + /// From agent identifier. + /// To agent identifier. + /// The amount. + /// The type. + /// The text. + /// + ///
+ ///
+ public bool MoveMoney(UUID fromAgentID, UUID toAgentID, int amount, MoneyTransactionType type, string text) + { + bool ret = ForceTransferMoney(fromAgentID, toAgentID, amount, (int)type, UUID.Zero, (ulong)0, UUID.Zero, text); + + m_log.InfoFormat("[MONEY MODULE] MoveMoney: {0} {1} {2} {3}", fromAgentID, toAgentID, amount, type); + + return ret; + } + + + + /// Called when [new client]. + /// The client. + private void OnNewClient(IClientAPI client) + { + client.OnEconomyDataRequest += OnEconomyDataRequest; + client.OnLogout += ClientClosed; + + client.OnMoneyBalanceRequest += OnMoneyBalanceRequest; + client.OnRequestPayPrice += OnRequestPayPrice; + client.OnObjectBuy += OnObjectBuy; + + m_log.InfoFormat("[MONEY MODULE] OnNewClient: {0}", client.AgentId); + } + + + /// Called when [make root agent]. + /// The agent. + public void OnMakeRootAgent(ScenePresence agent) + { + int balance = 0; + IClientAPI client = agent.ControllingClient; + + m_enable_server = LoginMoneyServer(agent, out balance); + client.SendMoneyBalance(UUID.Zero, true, new byte[0], balance, 0, UUID.Zero, false, UUID.Zero, false, 0, String.Empty); + + m_log.InfoFormat("[MONEY MODULE] OnMakeRootAgent: {0} {1}", client.AgentId, balance); + + } + + + // for OnClientClosed event + /// Clients the closed. + /// The client. + private void ClientClosed(IClientAPI client) + { + if (m_enable_server && client != null) + { + LogoffMoneyServer(client); + + m_log.InfoFormat("[MONEY MODULE] ClientClosed: {0}", client.AgentId); + } + } + + + // for OnMakeChildAgent event + /// Makes the child agent. + /// The avatar. + private void MakeChildAgent(ScenePresence avatar) + { + } + + + // for OnMoneyTransfer event + /// Moneys the transfer action. + /// The sender. + /// The money event. + private void MoneyTransferAction(Object sender, EventManager.MoneyTransferArgs moneyEvent) + { + if (!m_sellEnabled) return; + + // Check the money transaction is necessary. + if (moneyEvent.sender == moneyEvent.receiver) + { + return; + } + + UUID receiver = moneyEvent.receiver; + // Pay for the object. + if (moneyEvent.transactiontype == (int)TransactionType.PayObject) + { + SceneObjectPart sceneObj = GetLocatePrim(moneyEvent.receiver); + if (sceneObj != null) + { + receiver = sceneObj.OwnerID; + } + else + { + return; + } + } + + // Before paying for the object, save the object local ID for current transaction. + UUID objectID = UUID.Zero; + ulong regionHandle = 0; + UUID regionUUID = UUID.Zero; + + if (sender is Scene) + { + Scene scene = (Scene)sender; + regionHandle = scene.RegionInfo.RegionHandle; + regionUUID = scene.RegionInfo.RegionID; + + if (moneyEvent.transactiontype == (int)TransactionType.PayObject) + { + objectID = scene.GetSceneObjectPart(moneyEvent.receiver).UUID; + } + } + + TransferMoney(moneyEvent.sender, receiver, moneyEvent.amount, moneyEvent.transactiontype, objectID, regionHandle, regionUUID, "OnMoneyTransfer event"); + return; + } + + + // for OnValidateLandBuy event + /// Validates the land buy. + /// The sender. + /// The land buy event. + private void ValidateLandBuy(Object sender, EventManager.LandBuyArgs landBuyEvent) + { + IClientAPI senderClient = GetLocateClient(landBuyEvent.agentId); + if (senderClient != null) + { + int balance = QueryBalanceFromMoneyServer(senderClient); + if (balance >= landBuyEvent.parcelPrice) + { + lock (landBuyEvent) + { + landBuyEvent.economyValidated = true; + } + } + } + return; + } + + + // for LandBuy even + /// Processes the land buy. + /// The sender. + /// The land buy event. + private void processLandBuy(Object sender, EventManager.LandBuyArgs landBuyEvent) + { + if (!m_sellEnabled) return; + + lock (landBuyEvent) + { + if (landBuyEvent.economyValidated == true && landBuyEvent.transactionID == 0) + { + landBuyEvent.transactionID = Util.UnixTimeSinceEpoch(); + + ulong parcelID = (ulong)landBuyEvent.parcelLocalID; + UUID regionUUID = UUID.Zero; + if (sender is Scene) regionUUID = ((Scene)sender).RegionInfo.RegionID; + + if (TransferMoney(landBuyEvent.agentId, landBuyEvent.parcelOwnerID, + landBuyEvent.parcelPrice, (int)TransactionType.LandSale, regionUUID, parcelID, regionUUID, "Land Purchase")) + { + landBuyEvent.amountDebited = landBuyEvent.parcelPrice; + } + } + } + return; + } + + + // for OnObjectBuy event + /// Called when [object buy]. + /// The remote client. + /// The agent identifier. + /// The session identifier. + /// The group identifier. + /// The category identifier. + /// The local identifier. + /// Type of the sale. + /// The sale price. + public void OnObjectBuy(IClientAPI remoteClient, UUID agentID, UUID sessionID, UUID groupID, UUID categoryID, uint localID, byte saleType, int salePrice) + { + m_log.InfoFormat("[MONEY MODULE]: OnObjectBuy: agent = {0}, {1}", agentID, remoteClient.AgentId); + + // Handle the parameters error. + if (!m_sellEnabled) return; + if (remoteClient == null || salePrice < 0) return; + + // Get the balance from money server. + int balance = QueryBalanceFromMoneyServer(remoteClient); + if (balance < salePrice) + { + remoteClient.SendAgentAlertMessage("Unable to buy now. You don't have sufficient funds", false); + m_log.InfoFormat("[MONEY MODULE]: OnObjectBuy: agent = {0}, balance = {1}, salePrice = {2}", agentID, balance, salePrice); + return; + } + + Scene scene = GetLocateScene(remoteClient.AgentId); + if (scene != null) + { + SceneObjectPart sceneObj = scene.GetSceneObjectPart(localID); + if (sceneObj != null) + { + IBuySellModule mod = scene.RequestModuleInterface(); + if (mod != null) + { + UUID receiverId = sceneObj.OwnerID; + ulong regionHandle = sceneObj.RegionHandle; + UUID regionUUID = sceneObj.RegionID; + bool ret = false; + // + if (salePrice >= 0) + { + if (!string.IsNullOrEmpty(m_moneyServURL)) + { + ret = TransferMoney(remoteClient.AgentId, receiverId, salePrice, + (int)TransactionType.PayObject, sceneObj.UUID, regionHandle, regionUUID, "Object Buy"); + } + else if (salePrice == 0) + { // amount is 0 with No Money Server + ret = true; + } + } + if (ret) + { + mod.BuyObject(remoteClient, categoryID, localID, saleType, salePrice); + } + } + } + else + { + remoteClient.SendAgentAlertMessage("Unable to buy now. The object was not found", false); + m_log.InfoFormat("[MONEY MODULE]: OnObjectBuy: Unable to buy now. The object was not found"); + return; + } + } + return; + } + + + /// + /// Sends the the stored money balance to the client + /// + /// + /// + /// + /// + private void OnMoneyBalanceRequest(IClientAPI client, UUID agentID, UUID SessionID, UUID TransactionID) + { + m_log.InfoFormat("[MONEY MODULE]: OnMoneyBalanceRequest:"); + + if (client.AgentId == agentID && client.SessionId == SessionID) + { + int balance = 0; + // + if (m_enable_server) + { + balance = QueryBalanceFromMoneyServer(client); + } + + client.SendMoneyBalance(TransactionID, true, new byte[0], balance, 0, UUID.Zero, false, UUID.Zero, false, 0, String.Empty); + } + else + { + client.SendAlertMessage("Unable to send your money balance"); + } + } + + + /// Called when [request pay price]. + /// The client. + /// The object identifier. + private void OnRequestPayPrice(IClientAPI client, UUID objectID) + { + m_log.InfoFormat("[MONEY MODULE]: OnRequestPayPrice:"); + + Scene scene = GetLocateScene(client.AgentId); + if (scene == null) return; + SceneObjectPart sceneObj = scene.GetSceneObjectPart(objectID); + if (sceneObj == null) return; + SceneObjectGroup group = sceneObj.ParentGroup; + SceneObjectPart root = group.RootPart; + + client.SendPayPrice(objectID, root.PayPrice); + } + + + // + //private void OnEconomyDataRequest(UUID agentId) + /// Called when [economy data request]. + /// The user. + private void OnEconomyDataRequest(IClientAPI user) + { + if (user != null) + { + if (m_enable_server || string.IsNullOrEmpty(m_moneyServURL)) + { + //Scene s = GetLocateScene(user.AgentId); + Scene s = (Scene)user.Scene; + user.SendEconomyData(EnergyEfficiency, s.RegionInfo.ObjectCapacity, ObjectCount, PriceEnergyUnit, PriceGroupCreate, + PriceObjectClaim, PriceObjectRent, PriceObjectScaleFactor, PriceParcelClaim, PriceParcelClaimFactor, + PriceParcelRent, PricePublicObjectDecay, PricePublicObjectDelete, PriceRentLight, PriceUpload, + TeleportMinPrice, TeleportPriceExponent); + } + } + } + + + + // "OnMoneyTransfered" RPC from MoneyServer + /// Called when [money transfered handler]. + /// The request. + /// The remote client. + /// + ///
+ ///
+ public XmlRpcResponse OnMoneyTransferedHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + m_log.InfoFormat("[MONEY MODULE]: OnMoneyTransferedHandler:"); + + bool ret = false; + + if (request.Params.Count > 0) + { + Hashtable requestParam = (Hashtable)request.Params[0]; + if (requestParam.Contains("clientUUID") && requestParam.Contains("clientSessionID") && requestParam.Contains("clientSecureSessionID")) + { + UUID clientUUID = UUID.Zero; + UUID.TryParse((string)requestParam["clientUUID"], out clientUUID); + + if (clientUUID != UUID.Zero) + { + IClientAPI client = GetLocateClient(clientUUID); + string sessionid = (string)requestParam["clientSessionID"]; + string secureid = (string)requestParam["clientSecureSessionID"]; + if (client != null && secureid == client.SecureSessionId.ToString() && (sessionid == UUID.Zero.ToString() || sessionid == client.SessionId.ToString())) + { + if (requestParam.Contains("transactionType") && requestParam.Contains("objectID") && requestParam.Contains("amount")) + { + m_log.InfoFormat("[MONEY MODULE]: OnMoneyTransferedHandler: type = {0}", requestParam["transactionType"]); + + // Pay for the object. + if ((int)requestParam["transactionType"] == (int)TransactionType.PayObject) + { + // Send notify to the client(viewer) for Money Event Trigger. + ObjectPaid handlerOnObjectPaid = OnObjectPaid; + if (handlerOnObjectPaid != null) + { + UUID objectID = UUID.Zero; + UUID.TryParse((string)requestParam["objectID"], out objectID); + handlerOnObjectPaid(objectID, clientUUID, (int)requestParam["amount"]); // call Script Engine for LSL money() + } + ret = true; + } + } + } + } + } + } + + // Send the response to money server. + XmlRpcResponse resp = new XmlRpcResponse(); + Hashtable paramTable = new Hashtable(); + paramTable["success"] = ret; + + if (!ret) + { + m_log.ErrorFormat("[MONEY MODULE]: OnMoneyTransferedHandler: Transaction is failed. MoneyServer will rollback"); + } + resp.Value = paramTable; + + return resp; + } + + + // "UpdateBalance" RPC from MoneyServer or Script + /// Balances the update handler. + /// The request. + /// The remote client. + /// + ///
+ ///
+ public XmlRpcResponse BalanceUpdateHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + + bool ret = false; + + if (request.Params.Count > 0) + { + Hashtable requestParam = (Hashtable)request.Params[0]; + if (requestParam.Contains("clientUUID") && requestParam.Contains("clientSessionID") && requestParam.Contains("clientSecureSessionID")) + { + UUID clientUUID = UUID.Zero; + UUID.TryParse((string)requestParam["clientUUID"], out clientUUID); + // + if (clientUUID != UUID.Zero) + { + IClientAPI client = GetLocateClient(clientUUID); + string sessionid = (string)requestParam["clientSessionID"]; + string secureid = (string)requestParam["clientSecureSessionID"]; + if (client != null && secureid == client.SecureSessionId.ToString() && (sessionid == UUID.Zero.ToString() || sessionid == client.SessionId.ToString())) + { + // + if (requestParam.Contains("Balance")) + { + // Send notify to the client. + string msg = ""; + if (requestParam.Contains("Message")) msg = (string)requestParam["Message"]; + client.SendMoneyBalance(UUID.Random(), true, Utils.StringToBytes(msg), (int)requestParam["Balance"], + 0, UUID.Zero, false, UUID.Zero, false, 0, String.Empty); + // Dialog + if (msg != "") + { + Scene scene = (Scene)client.Scene; + IDialogModule dlg = scene.RequestModuleInterface(); + dlg.SendAlertToUser(client.AgentId, msg); + } + ret = true; + } + } + } + } + } + + + // Send the response to money server. + XmlRpcResponse resp = new XmlRpcResponse(); + Hashtable paramTable = new Hashtable(); + paramTable["success"] = ret; + + if (!ret) + { + m_log.ErrorFormat("[MONEY MODULE]: BalanceUpdateHandler: Cannot update client balance from MoneyServer"); + } + resp.Value = paramTable; + + return resp; + } + + + // "UserAlert" RPC from Script + /// Users the alert handler. + /// The request. + /// The remote client. + /// + ///
+ ///
+ public XmlRpcResponse UserAlertHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + m_log.InfoFormat("[MONEY MODULE]: UserAlertHandler:"); + + bool ret = false; + + if (request.Params.Count > 0) + { + Hashtable requestParam = (Hashtable)request.Params[0]; + if (requestParam.Contains("clientUUID") && requestParam.Contains("clientSessionID") && requestParam.Contains("clientSecureSessionID")) + { + UUID clientUUID = UUID.Zero; + UUID.TryParse((string)requestParam["clientUUID"], out clientUUID); + // + if (clientUUID != UUID.Zero) + { + IClientAPI client = GetLocateClient(clientUUID); + string sessionid = (string)requestParam["clientSessionID"]; + string secureid = (string)requestParam["clientSecureSessionID"]; + if (client != null && secureid == client.SecureSessionId.ToString() && (sessionid == UUID.Zero.ToString() || sessionid == client.SessionId.ToString())) + { + if (requestParam.Contains("Description")) + { + string description = (string)requestParam["Description"]; + // Show the notice dialog with money server message. + GridInstantMessage gridMsg = new GridInstantMessage(null, UUID.Zero, "MonyServer", new UUID(clientUUID.ToString()), + (byte)InstantMessageDialog.MessageFromAgent, description, false, new Vector3()); + client.SendInstantMessage(gridMsg); + ret = true; + } + } + } + } + } + + // Send the response to money server. + XmlRpcResponse resp = new XmlRpcResponse(); + Hashtable paramTable = new Hashtable(); + paramTable["success"] = ret; + + resp.Value = paramTable; + return resp; + } + + + // "GetBalance" RPC from Script + /// Gets the balance handler. + /// The request. + /// The remote client. + /// + ///
+ ///
+ public XmlRpcResponse GetBalanceHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + + bool ret = false; + int balance = -1; + + if (request.Params.Count > 0) + { + Hashtable requestParam = (Hashtable)request.Params[0]; + if (requestParam.Contains("clientUUID") && requestParam.Contains("clientSessionID") && requestParam.Contains("clientSecureSessionID")) + { + UUID clientUUID = UUID.Zero; + UUID.TryParse((string)requestParam["clientUUID"], out clientUUID); + // + if (clientUUID != UUID.Zero) + { + IClientAPI client = GetLocateClient(clientUUID); + string sessionid = (string)requestParam["clientSessionID"]; + string secureid = (string)requestParam["clientSecureSessionID"]; + if (client != null && secureid == client.SecureSessionId.ToString() && (sessionid == UUID.Zero.ToString() || sessionid == client.SessionId.ToString())) + { + balance = QueryBalanceFromMoneyServer(client); + } + } + } + } + + // Send the response to caller. + if (balance < 0) + { + m_log.ErrorFormat("[MONEY MODULE]: GetBalanceHandler: GetBalance transaction is failed"); + ret = false; + } + + XmlRpcResponse resp = new XmlRpcResponse(); + Hashtable paramTable = new Hashtable(); + paramTable["success"] = ret; + paramTable["balance"] = balance; + resp.Value = paramTable; + + return resp; + } + + + // "AddBankerMoney" RPC from Script + /// Adds the banker money handler. + /// The request. + /// The remote client. + /// + ///
+ ///
+ public XmlRpcResponse AddBankerMoneyHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + m_log.InfoFormat("[MONEY MODULE]: AddBankerMoneyHandler:"); + + bool ret = false; + + if (request.Params.Count > 0) + { + Hashtable requestParam = (Hashtable)request.Params[0]; + + if (requestParam.Contains("clientUUID") && requestParam.Contains("clientSessionID") && requestParam.Contains("clientSecureSessionID")) + { + UUID bankerUUID = UUID.Zero; + UUID.TryParse((string)requestParam["clientUUID"], out bankerUUID); + // + if (bankerUUID != UUID.Zero) + { + IClientAPI client = GetLocateClient(bankerUUID); + string sessionid = (string)requestParam["clientSessionID"]; + string secureid = (string)requestParam["clientSecureSessionID"]; + if (client != null && secureid == client.SecureSessionId.ToString() && (sessionid == UUID.Zero.ToString() || sessionid == client.SessionId.ToString())) + { + if (requestParam.Contains("amount")) + { + Scene scene = (Scene)client.Scene; + int amount = (int)requestParam["amount"]; + ulong regionHandle = scene.RegionInfo.RegionHandle; + UUID regionUUID = scene.RegionInfo.RegionID; + ret = AddBankerMoney(bankerUUID, amount, regionHandle, regionUUID); + + if (m_use_web_settle && m_settle_user) + { + ret = true; + IDialogModule dlg = scene.RequestModuleInterface(); + if (dlg != null) + { + dlg.SendUrlToUser(bankerUUID, "SYSTEM", UUID.Zero, UUID.Zero, false, m_settle_message, m_settle_url); + } + } + } + } + } + } + } + + if (!ret) m_log.ErrorFormat("[MONEY MODULE]: AddBankerMoneyHandler: Add Banker Money transaction is failed"); + + // Send the response to caller. + XmlRpcResponse resp = new XmlRpcResponse(); + Hashtable paramTable = new Hashtable(); + paramTable["settle"] = false; + paramTable["success"] = ret; + + if (m_use_web_settle && m_settle_user) paramTable["settle"] = true; + resp.Value = paramTable; + + return resp; + } + + + // "SendMoney" RPC from Script + /// Sends the money handler. + /// The request. + /// The remote client. + /// + ///
+ ///
+ public XmlRpcResponse SendMoneyHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + bool ret = false; + + if (request.Params.Count > 0) + { + Hashtable requestParam = (Hashtable)request.Params[0]; + if (requestParam.Contains("agentUUID") && requestParam.Contains("secretAccessCode")) + { + UUID agentUUID = UUID.Zero; + UUID.TryParse((string)requestParam["agentUUID"], out agentUUID); + + if (agentUUID != UUID.Zero) + { + if (requestParam.Contains("amount")) + { + int amount = (int)requestParam["amount"]; + int type = -1; + if (requestParam.Contains("type")) type = (int)requestParam["type"]; + string secretCode = (string)requestParam["secretAccessCode"]; + string scriptIP = remoteClient.Address.ToString(); + + MD5 md5 = MD5.Create(); + byte[] code = md5.ComputeHash(ASCIIEncoding.Default.GetBytes(secretCode + "_" + scriptIP)); + string hash = BitConverter.ToString(code).ToLower().Replace("-", ""); + m_log.InfoFormat("[MONEY MODULE]: SendMoneyHandler: SecretCode: {0} + {1} = {2}", secretCode, scriptIP, hash); + ret = SendMoneyTo(agentUUID, amount, type, hash); + } + } + else + { + m_log.ErrorFormat("[MONEY MODULE]: SendMoneyHandler: amount is missed"); + } + } + else + { + if (!requestParam.Contains("agentUUID")) + { + m_log.ErrorFormat("[MONEY MODULE]: SendMoneyHandler: agentUUID is missed"); + } + if (!requestParam.Contains("secretAccessCode")) + { + m_log.ErrorFormat("[MONEY MODULE]: SendMoneyHandler: secretAccessCode is missed"); + } + } + } + else + { + m_log.ErrorFormat("[MONEY MODULE]: SendMoneyHandler: Params count is under 0"); + } + + if (!ret) m_log.ErrorFormat("[MONEY MODULE]: SendMoneyHandler: Send Money transaction is failed"); + + // Send the response to caller. + XmlRpcResponse resp = new XmlRpcResponse(); + Hashtable paramTable = new Hashtable(); + paramTable["success"] = ret; + + resp.Value = paramTable; + + return resp; + } + + + // "MoveMoney" RPC from Script + /// Moves the money handler. + /// The request. + /// The remote client. + /// + ///
+ ///
+ public XmlRpcResponse MoveMoneyHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + bool ret = false; + + if (request.Params.Count > 0) + { + Hashtable requestParam = (Hashtable)request.Params[0]; + if ((requestParam.Contains("fromUUID") || requestParam.Contains("toUUID")) && requestParam.Contains("secretAccessCode")) + { + UUID fromUUID = UUID.Zero; + UUID toUUID = UUID.Zero; // UUID.Zero means System + if (requestParam.Contains("fromUUID")) UUID.TryParse((string)requestParam["fromUUID"], out fromUUID); + if (requestParam.Contains("toUUID")) UUID.TryParse((string)requestParam["toUUID"], out toUUID); + + if (requestParam.Contains("amount")) + { + int amount = (int)requestParam["amount"]; + string secretCode = (string)requestParam["secretAccessCode"]; + string scriptIP = remoteClient.Address.ToString(); + + MD5 md5 = MD5.Create(); + byte[] code = md5.ComputeHash(ASCIIEncoding.Default.GetBytes(secretCode + "_" + scriptIP)); + string hash = BitConverter.ToString(code).ToLower().Replace("-", ""); + m_log.InfoFormat("[MONEY MODULE]: MoveMoneyHandler: SecretCode: {0} + {1} = {2}", secretCode, scriptIP, hash); + ret = MoveMoneyFromTo(fromUUID, toUUID, amount, hash); + } + else + { + m_log.ErrorFormat("[MONEY MODULE]: MoveMoneyHandler: amount is missed"); + } + } + else + { + if (!requestParam.Contains("fromUUID") && !requestParam.Contains("toUUID")) + { + m_log.ErrorFormat("[MONEY MODULE]: MoveMoneyHandler: fromUUID and toUUID are missed"); + } + if (!requestParam.Contains("secretAccessCode")) + { + m_log.ErrorFormat("[MONEY MODULE]: MoveMoneyHandler: secretAccessCode is missed"); + } + } + } + else + { + m_log.ErrorFormat("[MONEY MODULE]: MoveMoneyHandler: Params count is under 0"); + } + + if (!ret) m_log.ErrorFormat("[MONEY MODULE]: MoveMoneyHandler: Move Money transaction is failed"); + + // Send the response to caller. + XmlRpcResponse resp = new XmlRpcResponse(); + Hashtable paramTable = new Hashtable(); + paramTable["success"] = ret; + + resp.Value = paramTable; + + return resp; + } + + + /// + /// Transfer the money from one user to another. Need to notify money server to update. + /// + /// + /// The amount of money. + /// + /// + /// return true, if successfully. + /// + private bool TransferMoney(UUID sender, UUID receiver, int amount, int type, UUID objectID, ulong regionHandle, UUID regionUUID, string description) + { + bool ret = false; + IClientAPI senderClient = GetLocateClient(sender); + + // Handle the illegal transaction. + // receiverClient could be null. + if (senderClient == null) + { + m_log.InfoFormat("[MONEY MODULE]: TransferMoney: Client {0} not found", sender.ToString()); + return false; + } + + if (QueryBalanceFromMoneyServer(senderClient) < amount) + { + m_log.InfoFormat("[MONEY MODULE]: TransferMoney: No insufficient balance in client [{0}]", sender.ToString()); + return false; + } + + if (m_enable_server) + { + string objName = string.Empty; + SceneObjectPart sceneObj = GetLocatePrim(objectID); + if (sceneObj != null) objName = sceneObj.Name; + + // Fill parameters for money transfer XML-RPC. + Hashtable paramTable = new Hashtable(); + paramTable["senderID"] = sender.ToString(); + paramTable["receiverID"] = receiver.ToString(); + paramTable["senderSessionID"] = senderClient.SessionId.ToString(); + paramTable["senderSecureSessionID"] = senderClient.SecureSessionId.ToString(); + paramTable["transactionType"] = type; + paramTable["objectID"] = objectID.ToString(); + paramTable["objectName"] = objName; + paramTable["regionHandle"] = regionHandle.ToString(); + paramTable["regionUUID"] = regionUUID.ToString(); + paramTable["amount"] = amount; + paramTable["description"] = description; + + // Generate the request for transfer. + Hashtable resultTable = genericCurrencyXMLRPCRequest(paramTable, "TransferMoney"); + + // Handle the return values from Money Server. + if (resultTable != null && resultTable.Contains("success")) + { + if ((bool)resultTable["success"] == true) + { + ret = true; + } + } + else m_log.ErrorFormat("[MONEY MODULE]: TransferMoney: Can not money transfer request from [{0}] to [{1}]", sender.ToString(), receiver.ToString()); + } + + return ret; + } + + + /// + /// Force transfer the money from one user to another. + /// This function does not check sender login. + /// Need to notify money server to update. + /// + /// + /// The amount of money. + /// + /// + /// return true, if successfully. + /// + private bool ForceTransferMoney(UUID sender, UUID receiver, int amount, int type, UUID objectID, ulong regionHandle, UUID regionUUID, string description) + { + bool ret = false; + + if (m_enable_server) + { + string objName = string.Empty; + SceneObjectPart sceneObj = GetLocatePrim(objectID); + if (sceneObj != null) objName = sceneObj.Name; + + // Fill parameters for money transfer XML-RPC. + Hashtable paramTable = new Hashtable(); + paramTable["senderID"] = sender.ToString(); + paramTable["receiverID"] = receiver.ToString(); + paramTable["transactionType"] = type; + paramTable["objectID"] = objectID.ToString(); + paramTable["objectName"] = objName; + paramTable["regionHandle"] = regionHandle.ToString(); + paramTable["regionUUID"] = regionUUID.ToString(); + paramTable["amount"] = amount; + paramTable["description"] = description; + + // Generate the request for transfer. + Hashtable resultTable = genericCurrencyXMLRPCRequest(paramTable, "ForceTransferMoney"); + + // Handle the return values from Money Server. + if (resultTable != null && resultTable.Contains("success")) + { + if ((bool)resultTable["success"] == true) + { + ret = true; + } + } + else m_log.ErrorFormat("[MONEY MODULE]: ForceTransferMoney: Can not money force transfer request from [{0}] to [{1}]", sender.ToString(), receiver.ToString()); + } + + return ret; + } + + + /// + /// Send the money to avatar. Need to notify money server to update. + /// + /// + /// The amount of money. + /// + /// + /// return true, if successfully. + /// + private bool SendMoneyTo(UUID avatarID, int amount, int type, string secretCode) + { + bool ret = false; + + if (m_enable_server) + { + // Fill parameters for money transfer XML-RPC. + if (type < 0) type = (int)TransactionType.ReferBonus; + Hashtable paramTable = new Hashtable(); + paramTable["receiverID"] = avatarID.ToString(); + paramTable["transactionType"] = type; + paramTable["amount"] = amount; + paramTable["secretAccessCode"] = secretCode; + paramTable["description"] = "Bonus to Avatar"; + + // Generate the request for transfer. + Hashtable resultTable = genericCurrencyXMLRPCRequest(paramTable, "SendMoney"); + + // Handle the return values from Money Server. + if (resultTable != null && resultTable.Contains("success")) + { + if ((bool)resultTable["success"] == true) + { + ret = true; + } + else m_log.ErrorFormat("[MONEY MODULE]: SendMoneyTo: Fail Message is {0}", resultTable["message"]); + } + else m_log.ErrorFormat("[MONEY MODULE]: SendMoneyTo: Money Server is not responce"); + } + + return ret; + } + + + /// + /// Move the money from avatar to other avatar. Need to notify money server to update. + /// + /// + /// The amount of money. + /// + /// + /// return true, if successfully. + /// + private bool MoveMoneyFromTo(UUID senderID, UUID receiverID, int amount, string secretCode) + { + bool ret = false; + + if (m_enable_server) + { + // Fill parameters for money transfer XML-RPC. + Hashtable paramTable = new Hashtable(); + paramTable["senderID"] = senderID.ToString(); + paramTable["receiverID"] = receiverID.ToString(); + paramTable["transactionType"] = (int)TransactionType.MoveMoney; + paramTable["amount"] = amount; + paramTable["secretAccessCode"] = secretCode; + paramTable["description"] = "Move Money"; + + // Generate the request for transfer. + Hashtable resultTable = genericCurrencyXMLRPCRequest(paramTable, "MoveMoney"); + + // Handle the return values from Money Server. + if (resultTable != null && resultTable.Contains("success")) + { + if ((bool)resultTable["success"] == true) + { + ret = true; + } + else m_log.ErrorFormat("[MONEY MODULE]: MoveMoneyFromTo: Fail Message is {0}", resultTable["message"]); + } + else m_log.ErrorFormat("[MONEY MODULE]: MoveMoneyFromTo: Money Server is not responce"); + } + + return ret; + } + + + /// + /// Add the money to banker avatar. Need to notify money server to update. + /// + /// + /// The amount of money. + /// + /// + /// return true, if successfully. + /// + private bool AddBankerMoney(UUID bankerID, int amount, ulong regionHandle, UUID regionUUID) + { + bool ret = false; + m_settle_user = false; + + if (m_enable_server) + { + // Fill parameters for money transfer XML-RPC. + Hashtable paramTable = new Hashtable(); + paramTable["bankerID"] = bankerID.ToString(); + paramTable["transactionType"] = (int)TransactionType.BuyMoney; + paramTable["amount"] = amount; + paramTable["regionHandle"] = regionHandle.ToString(); + paramTable["regionUUID"] = regionUUID.ToString(); + paramTable["description"] = "Add Money to Avatar"; + + // Generate the request for transfer. + Hashtable resultTable = genericCurrencyXMLRPCRequest(paramTable, "AddBankerMoney"); + + // Handle the return values from Money Server. + if (resultTable != null) + { + if (resultTable.Contains("success") && (bool)resultTable["success"] == true) + { + ret = true; + } + else + { + if (resultTable.Contains("banker")) + { + m_settle_user = !(bool)resultTable["banker"]; // If avatar is not banker, Web Settlement is used. + if (m_settle_user && m_use_web_settle) m_log.ErrorFormat("[MONEY MODULE]: AddBankerMoney: Avatar is not Banker. Web Settlemrnt is used."); + } + else m_log.ErrorFormat("[MONEY MODULE]: AddBankerMoney: Fail Message {0}", resultTable["message"]); + } + } + else m_log.ErrorFormat("[MONEY MODULE]: AddBankerMoney: Money Server is not responce"); + } + + return ret; + } + + + /// + /// Pay the money of charge. + /// + /// + /// The amount of money. + /// + /// + /// return true, if successfully. + /// + private bool PayMoneyCharge(UUID sender, int amount, int type, ulong regionHandle, UUID regionUUID, string description) + { + bool ret = false; + IClientAPI senderClient = GetLocateClient(sender); + + // Handle the illegal transaction. + // receiverClient could be null. + if (senderClient == null) + { + m_log.InfoFormat("[MONEY MODULE]: PayMoneyCharge: Client {0} is not found", sender.ToString()); + return false; + } + + if (QueryBalanceFromMoneyServer(senderClient) < amount) + { + m_log.InfoFormat("[MONEY MODULE]: PayMoneyCharge: No insufficient balance in client [{0}]", sender.ToString()); + return false; + } + + if (m_enable_server) + { + // Fill parameters for money transfer XML-RPC. + Hashtable paramTable = new Hashtable(); + paramTable["senderID"] = sender.ToString(); + paramTable["senderSessionID"] = senderClient.SessionId.ToString(); + paramTable["senderSecureSessionID"] = senderClient.SecureSessionId.ToString(); + paramTable["transactionType"] = type; + paramTable["amount"] = amount; + paramTable["regionHandle"] = regionHandle.ToString(); + paramTable["regionUUID"] = regionUUID.ToString(); + paramTable["description"] = description; + + // Generate the request for transfer. + Hashtable resultTable = genericCurrencyXMLRPCRequest(paramTable, "PayMoneyCharge"); + + // Handle the return values from Money Server. + if (resultTable != null && resultTable.Contains("success")) + { + if ((bool)resultTable["success"] == true) + { + ret = true; + } + } + else m_log.ErrorFormat("[MONEY MODULE]: PayMoneyCharge: Can not pay money of charge request from [{0}]", sender.ToString()); + } + + return ret; + } + + + /// Queries the balance from money server. + /// The client. + /// + ///
+ ///
+ private int QueryBalanceFromMoneyServer(IClientAPI client) + { + int balance = 0; + + if (client != null) + { + if (m_enable_server) + { + Hashtable paramTable = new Hashtable(); + paramTable["clientUUID"] = client.AgentId.ToString(); + paramTable["clientSessionID"] = client.SessionId.ToString(); + paramTable["clientSecureSessionID"] = client.SecureSessionId.ToString(); + + // Generate the request for transfer. + Hashtable resultTable = genericCurrencyXMLRPCRequest(paramTable, "GetBalance"); + + // Handle the return result + if (resultTable != null && resultTable.Contains("success")) + { + if ((bool)resultTable["success"] == true) + { + balance = (int)resultTable["clientBalance"]; + m_log.InfoFormat("[MONEY MODULE]: QueryBalanceFromMoneyServer: Balance {0}", balance); + } + } + } + else + { + if (m_moneyServer.ContainsKey(client.AgentId)) + { + balance = m_moneyServer[client.AgentId]; + m_log.InfoFormat("[MONEY MODULE]: QueryBalanceFromMoneyServer: Balance {0}", balance); + } + } + } + + return balance; + } + + + /// + /// Login the money server when the new client login. + /// + /// + /// Indicate user ID of the new client. + /// + /// + /// return true, if successfully. + /// + private bool LoginMoneyServer(ScenePresence avatar, out int balance) + { + balance = 0; + bool ret = false; + bool isNpc = avatar.IsNPC; + + IClientAPI client = avatar.ControllingClient; + + if (!string.IsNullOrEmpty(m_moneyServURL)) + { + Scene scene = (Scene)client.Scene; + string userName = string.Empty; + + // Get the username for the login user. + if (client.Scene is Scene) + { + if (scene != null) + { + UserAccount account = scene.UserAccountService.GetUserAccount(scene.RegionInfo.ScopeID, client.AgentId); + if (account != null) + { + userName = account.FirstName + " " + account.LastName; + m_log.InfoFormat("[MONEY MODULE]: LoginMoneyServer: User {0} logged in.", userName); + } + } + } + + + // User Universal Identifer for Grid Avatar, HG Avatar or NPC + string universalID = string.Empty; + string firstName = string.Empty; + string lastName = string.Empty; + string serverURL = string.Empty; + int avatarType = (int)AvatarType.LOCAL_AVATAR; + int avatarClass = (int)AvatarType.LOCAL_AVATAR; + + AgentCircuitData agent = scene.AuthenticateHandler.GetAgentCircuitData(client.AgentId); + + if (agent != null) + { + universalID = Util.ProduceUserUniversalIdentifier(agent); + if (!String.IsNullOrEmpty(universalID)) + { + UUID uuid; + string tmp; + Util.ParseUniversalUserIdentifier(universalID, out uuid, out serverURL, out firstName, out lastName, out tmp); + } + // if serverURL is empty, avatar is a NPC + if (isNpc || String.IsNullOrEmpty(serverURL)) + { + avatarType = (int)AvatarType.NPC_AVATAR; + } + // + if ((agent.teleportFlags & (uint)Constants.TeleportFlags.ViaHGLogin) != 0 || String.IsNullOrEmpty(userName)) + { + avatarType = (int)AvatarType.HG_AVATAR; + } + } + if (String.IsNullOrEmpty(userName)) + { + userName = firstName + " " + lastName; + m_log.InfoFormat("[MONEY MODULE]: LoginMoneyServer: User {0} logged in.", userName); + } + + // + avatarClass = avatarType; + if (avatarType == (int)AvatarType.NPC_AVATAR) return false; + if (avatarType == (int)AvatarType.HG_AVATAR) avatarClass = m_hg_avatarClass; + + // + // Login the Money Server. + Hashtable paramTable = new Hashtable(); + paramTable["openSimServIP"] = scene.RegionInfo.ServerURI.Replace(scene.RegionInfo.InternalEndPoint.Port.ToString(), + scene.RegionInfo.HttpPort.ToString()); + paramTable["avatarType"] = avatarType.ToString(); + paramTable["avatarClass"] = avatarClass.ToString(); + paramTable["userName"] = userName; + paramTable["universalID"] = universalID; + paramTable["clientUUID"] = client.AgentId.ToString(); + paramTable["clientSessionID"] = client.SessionId.ToString(); + paramTable["clientSecureSessionID"] = client.SecureSessionId.ToString(); + + // Generate the request for transfer. + Hashtable resultTable = genericCurrencyXMLRPCRequest(paramTable, "ClientLogin"); + + // Handle the return result + if (resultTable != null && resultTable.Contains("success")) + { + if ((bool)resultTable["success"] == true) + { + balance = (int)resultTable["clientBalance"]; + m_log.InfoFormat("[MONEY MODULE]: LoginMoneyServer: Client [{0}] login Money Server {1}", client.AgentId.ToString(), m_moneyServURL); + ret = true; + } + } + else m_log.ErrorFormat("[MONEY MODULE]: LoginMoneyServer: Unable to login Money Server {0} for client [{1}]", m_moneyServURL, client.AgentId.ToString()); + } + else m_log.ErrorFormat("[MONEY MODULE]: LoginMoneyServer: Money Server is not available!!"); + + + // Notifies the Viewer of the setting. + if (ret || string.IsNullOrEmpty(m_moneyServURL)) + { + OnEconomyDataRequest(client); + } + + return ret; + } + + + /// + /// Log off from the money server. + /// + /// + /// Indicate user ID of the new client. + /// + /// + /// return true, if successfully. + /// + private bool LogoffMoneyServer(IClientAPI client) + { + bool ret = false; + + if (!string.IsNullOrEmpty(m_moneyServURL)) + { + // Log off from the Money Server. + Hashtable paramTable = new Hashtable(); + paramTable["clientUUID"] = client.AgentId.ToString(); + paramTable["clientSessionID"] = client.SessionId.ToString(); + paramTable["clientSecureSessionID"] = client.SecureSessionId.ToString(); + + // Generate the request for transfer. + Hashtable resultTable = genericCurrencyXMLRPCRequest(paramTable, "ClientLogout"); + // Handle the return result + if (resultTable != null && resultTable.Contains("success")) + { + if ((bool)resultTable["success"] == true) + { + ret = true; + } + } + m_log.InfoFormat("[MONEY MODULE]: LogoffMoneyServer: Client [{0}] logoff Money Server {1}", client.AgentId.ToString(), m_moneyServURL); + } + + return ret; + } + + + // + /// Gets the transaction information. + /// The client. + /// The transaction identifier. + /// + ///
+ ///
+ private EventManager.MoneyTransferArgs GetTransactionInfo(IClientAPI client, string transactionID) + { + EventManager.MoneyTransferArgs args = null; + + if (m_enable_server) + { + Hashtable paramTable = new Hashtable(); + paramTable["clientUUID"] = client.AgentId.ToString(); + paramTable["clientSessionID"] = client.SessionId.ToString(); + paramTable["clientSecureSessionID"] = client.SecureSessionId.ToString(); + paramTable["transactionID"] = transactionID; + + // Generate the request for transfer. + Hashtable resultTable = genericCurrencyXMLRPCRequest(paramTable, "GetTransaction"); + + // Handle the return result + if (resultTable != null && resultTable.Contains("success")) + { + if ((bool)resultTable["success"] == true) + { + int amount = (int)resultTable["amount"]; + int type = (int)resultTable["type"]; + string desc = (string)resultTable["description"]; + UUID sender = UUID.Zero; + UUID recver = UUID.Zero; + UUID.TryParse((string)resultTable["sender"], out sender); + UUID.TryParse((string)resultTable["receiver"], out recver); + args = new EventManager.MoneyTransferArgs(sender, recver, amount, type, desc); + } + else + { + m_log.ErrorFormat("[MONEY MODULE]: GetTransactionInfo: GetTransactionInfo: Fail to Request. {0}", (string)resultTable["description"]); + } + } + else + { + m_log.ErrorFormat("[MONEY MODULE]: GetTransactionInfo: Invalid Response"); + } + } + else + { + m_log.ErrorFormat("[MONEY MODULE]: GetTransactionInfo: Invalid Money Server URL"); + } + + return args; + } + + + /// + /// Generic XMLRPC client abstraction + /// + /// Hashtable containing parameters to the method + /// Method to invoke + /// Hashtable with success=>bool and other values + private Hashtable genericCurrencyXMLRPCRequest(Hashtable reqParams, string method) + { + if (reqParams.Count <= 0 || string.IsNullOrEmpty(method)) return null; + + if (m_checkServerCert) + { + if (!m_moneyServURL.StartsWith("https://")) + { + m_log.InfoFormat("[MONEY MODULE]: genericCurrencyXMLRPCRequest: CheckServerCert is true, but protocol is not HTTPS. Please check INI file"); + //return null; + } + } + else + { + if (!m_moneyServURL.StartsWith("https://") && !m_moneyServURL.StartsWith("http://")) + { + m_log.ErrorFormat("[MONEY MODULE]: genericCurrencyXMLRPCRequest: Invalid Money Server URL: {0}", m_moneyServURL); + return null; + } + } + + ArrayList arrayParams = new ArrayList(); + arrayParams.Add(reqParams); + XmlRpcResponse moneyServResp = null; + try + { + NSLXmlRpcRequest moneyModuleReq = new NSLXmlRpcRequest(method, arrayParams); + //moneyServResp = moneyModuleReq.certSend(m_moneyServURL, m_cert, m_checkServerCert, MONEYMODULE_REQUEST_TIMEOUT); + moneyServResp = moneyModuleReq.certSend(m_moneyServURL, m_certVerify, m_checkServerCert, MONEYMODULE_REQUEST_TIMEOUT); + } + catch (Exception ex) + { + m_log.ErrorFormat("[MONEY MODULE]: genericCurrencyXMLRPCRequest: Unable to connect to Money Server {0}", m_moneyServURL); + m_log.ErrorFormat("[MONEY MODULE]: genericCurrencyXMLRPCRequest: {0}", ex); + + Hashtable ErrorHash = new Hashtable(); + ErrorHash["success"] = false; + ErrorHash["errorMessage"] = "Unable to manage your money at this time. Purchases may be unavailable"; + ErrorHash["errorURI"] = ""; + return ErrorHash; + } + + if (moneyServResp == null || moneyServResp.IsFault) + { + Hashtable ErrorHash = new Hashtable(); + ErrorHash["success"] = false; + ErrorHash["errorMessage"] = "Unable to manage your money at this time. Purchases may be unavailable"; + ErrorHash["errorURI"] = ""; + return ErrorHash; + } + + Hashtable moneyRespData = (Hashtable)moneyServResp.Value; + return moneyRespData; + } + + + /// Locates a IClientAPI for the client specified + /// + /// + /// + private IClientAPI GetLocateClient(UUID AgentID) + { + IClientAPI client = null; + + lock (m_sceneList) + { + if (m_sceneList.Count > 0) + { + foreach (Scene _scene in m_sceneList.Values) + { + ScenePresence tPresence = (ScenePresence)_scene.GetScenePresence(AgentID); + if (tPresence != null && !tPresence.IsChildAgent) + { + IClientAPI rclient = tPresence.ControllingClient; + if (rclient != null) + { + client = rclient; + break; + } + } + } + } + } + + m_log.DebugFormat("[MONEY MODULE]: GetLocateClient: {0}", client); + + return client; + } + + + /// Gets the locate scene. + /// The agent identifier. + /// + ///
+ ///
+ private Scene GetLocateScene(UUID AgentId) + { + Scene scene = null; + + lock (m_sceneList) + { + if (m_sceneList.Count > 0) + { + foreach (Scene _scene in m_sceneList.Values) + { + ScenePresence tPresence = (ScenePresence)_scene.GetScenePresence(AgentId); + if (tPresence != null && !tPresence.IsChildAgent) + { + scene = _scene; + break; + } + } + } + } + + m_log.DebugFormat("[MONEY MODULE]: GetLocateScene: {0}", scene); + + return scene; + } + + + /// Gets the locate prim. + /// The object identifier. + /// + ///
+ ///
+ private SceneObjectPart GetLocatePrim(UUID objectID) + { + SceneObjectPart sceneObj = null; + + lock (m_sceneList) + { + if (m_sceneList.Count > 0) + { + foreach (Scene _scene in m_sceneList.Values) + { + SceneObjectPart part = (SceneObjectPart)_scene.GetSceneObjectPart(objectID); + if (part != null) + { + sceneObj = part; + break; + } + } + } + } + + + + return sceneObj; + } + + } + +} diff --git a/addon-modules/OpenSim-Modules-Currency/OpenSim.Modules.Currency/NSLCertificateTools.cs b/addon-modules/OpenSim-Modules-Currency/OpenSim.Modules.Currency/NSLCertificateTools.cs new file mode 100644 index 0000000..d27dd9e --- /dev/null +++ b/addon-modules/OpenSim-Modules-Currency/OpenSim.Modules.Currency/NSLCertificateTools.cs @@ -0,0 +1,308 @@ +/* + * Copyright (c) Contributors, http://www.nsl.tuis.ac.jp + +Funktion + +Diese Klasse dient zur Verwaltung und Überprüfung von SSL/TLS-Zertifikaten in C# (z. B. für einen MoneyServer). Sie bietet Methoden, um: + Eigene Zertifikate (CA und Client) zu laden + Certificate Revocation Lists (CRL) zu laden + Zertifikatsketten zu prüfen + Server- und Client-Zertifikate im Rahmen einer SSL/TLS-Verbindung zu validieren + +Null Pointer Checks + +1. Member-Initialisierung + Alle Felder (m_chain, m_cacert, m_mycert, m_clientcrl) sind nullable und werden mit null initialisiert. + +2. Set- und Get-Methoden + Beim Laden von Zertifikaten (SetPrivateCert, SetPrivateCA, SetPrivateCRL) wird jeder Fehler per try-catch abgefangen. Im Fehlerfall wird das entsprechende Feld auf null gesetzt und geloggt. + +3. Zertifikatsprüfungen + In CheckPrivateChain wird vor der Nutzung von m_chain und m_cacert explizit auf null geprüft: + C# + + if (m_chain == null || m_cacert == null) return false; + + In den Callback-Methoden zur Zertifikatsvalidierung (ValidateServerCertificate, ValidateClientCertificate) wird jeweils geprüft, ob ein Zertifikat vorhanden ist (certificate == null). + +4. CRL-Prüfung + Die CRL wird nur geprüft, wenn sie geladen wurde (if (m_clientcrl != null)). + +Fehlerquellen und ihre Behandlung + Dateizugriffe: Wenn Zertifikats- oder CRL-Dateien nicht lesbar oder ungültig sind, wird dies sauber abgefangen, das jeweilige Objekt auf null gesetzt und ein Fehler geloggt. + Chain-Validation: Wenn die Kette nicht validiert werden kann, wird dies mit Rückgabewerten behandelt (false) und geloggt. + SSL Policy Errors: Policy-Fehler werden geloggt und führen zum Abbruch der Validierung. + CRL-Eintrag: Wird ein Zertifikat als widerrufen erkannt, wird dies geloggt und die Validierung schlägt fehl. + Header "NoVerifyCert": Bei explizitem Wunsch per Header wird die Zertifikatsprüfung übersprungen. + +Zusammenfassung + Null Pointer: Sehr sorgfältige Behandlung. Überall werden Objekte vor Nutzung geprüft, alle Fehlerfälle führen zu Logging und setzen der Objekte auf null, Rückgabe erfolgt immer sicher. + Fehlerquellen: Hauptsächlich externe Faktoren wie ungültige oder fehlende Dateien, fehlerhafte Zertifikate oder widerrufene Zertifikate. Alles wird abgefangen. + Funktion: Die Klasse ist robust und für produktiven Serverbetrieb geeignet. Sie bietet vollständige und sichere Zertifikatsprüfung für Server- und Client-Zertifikate inkl. CRL-Unterstützung. + +Fazit: +Die Klasse ist sicher gegenüber NullPointer-Exceptions und behandelt alle Fehlerfälle sauber mit Logging. +Sie erfüllt alle Anforderungen für sichere Zertifikatsvalidierung in einem OpenSim-Serverumfeld. + */ + +using System; +using System.Net; +using System.Net.Security; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; + +using log4net; + + +namespace NSL.Certificate.Tools +{ + /// + /// class NSL Certificate Verify + /// + public class NSLCertificateVerify + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private X509Chain m_chain = null; + private X509Certificate2 m_cacert = null; + private X509Certificate2 m_mycert = null; + + private Mono.Security.X509.X509Crl m_clientcrl = null; + + + /// NSL Certificate Verify + public NSLCertificateVerify() + { + m_chain = null; + m_cacert = null; + m_clientcrl = null; + + // m_log.InfoFormat("[NSL CERT VERIFY]: NSLCertificateVerify()"); + } + + + /// + /// NSL Certificate Verify + /// + /// + public NSLCertificateVerify(string certfile) + { + SetPrivateCA(certfile); + + // m_log.InfoFormat("[NSL CERT VERIFY]: NSLCertificateVerify()"); + } + + + /// + /// NSL Certificate Verify + /// + /// + /// + public NSLCertificateVerify(string certfile, string crlfile) + { + SetPrivateCA(certfile); + SetPrivateCRL(crlfile); + + // m_log.InfoFormat("[NSL CERT VERIFY]: NSLCertificateVerify()"); + } + + + /// + /// Set Private Certificate + /// + /// + /// + public void SetPrivateCert(string certfile, string passwd) + { + try + { + m_mycert = new X509Certificate2(certfile, passwd); + } + catch (Exception ex) + { + m_mycert = null; + m_log.ErrorFormat("[SET PRIVATE CERT]: Cert File setting error [{0}]. {1}", certfile, ex); + } + } + + + /// + /// Get Private Certificate + /// + public X509Certificate2 GetPrivateCert() + { + return m_mycert; + } + + + + /// + /// Set Private CA + /// + /// + public void SetPrivateCA(string certfile) + { + try + { + m_cacert = new X509Certificate2(certfile); + } + catch (Exception ex) + { + m_cacert = null; + m_log.ErrorFormat("[SET PRIVATE CA]: CA File reading error [{0}]. {1}", certfile, ex); + } + + if (m_cacert != null) + { + m_chain = new X509Chain(); + m_chain.ChainPolicy.ExtraStore.Add(m_cacert); + m_chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + m_chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag; + } + } + + /// Sets the private CRL. + /// The crlfile. + public void SetPrivateCRL(string crlfile) + { + try + { + m_clientcrl = Mono.Security.X509.X509Crl.CreateFromFile(crlfile); + } + catch (Exception ex) + { + m_clientcrl = null; + m_log.ErrorFormat("[SET PRIVATE CRL]: CRL File reading error [{0}]. {1}", crlfile, ex); + } + } + + + /// + /// Check Private Chain + /// + /// + /// + public bool CheckPrivateChain(X509Certificate2 cert) + { + if (m_chain == null || m_cacert == null) + { + return false; + } + + bool ret = m_chain.Build((X509Certificate2)cert); + if (ret) + { + return true; + } + + for (int i = 0; i < m_chain.ChainStatus.Length; i++) + { + if (m_chain.ChainStatus[i].Status == X509ChainStatusFlags.UntrustedRoot) return true; + } + + return false; + } + + + /// + /// Validate Server Certificate Callback Function + /// + /// + /// + /// + /// + /// + public bool ValidateServerCertificate(object obj, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + m_log.InfoFormat("[NSL SERVER CERT VERIFY]: ValidateServerCertificate: Policy is ({0})", sslPolicyErrors); + + if (obj is HttpWebRequest) + { + HttpWebRequest Request = (HttpWebRequest)obj; + string noVerify = Request.Headers.Get("NoVerifyCert"); + if ((noVerify != null) && (noVerify.ToLower() == "true")) + { + m_log.InfoFormat("[NSL SERVER CERT VERIFY]: ValidateServerCertificate: No Verify Server Certificate."); + return true; + } + } + + X509Certificate2 certificate2 = new X509Certificate2(certificate); + string commonname = certificate2.GetNameInfo(X509NameType.SimpleName, false); + m_log.InfoFormat("[NSL SERVER CERT VERIFY]: ValidateServerCertificate: Common Name is \"{0}\"", commonname); + + // None, ChainErrors Error except for. + if ((sslPolicyErrors != SslPolicyErrors.None) && (sslPolicyErrors != SslPolicyErrors.RemoteCertificateChainErrors)) + { + m_log.InfoFormat("[NSL SERVER CERT VERIFY]: ValidateServerCertificate: Policy Error! {0}", sslPolicyErrors); + return false; + } + + bool valid = CheckPrivateChain(certificate2); + if (valid) + { + m_log.InfoFormat("[NSL SERVER CERT VERIFY]: ValidateServerCertificate: Valid Server Certification for \"{0}\"", commonname); + } + else + { + m_log.InfoFormat("[NSL SERVER CERT VERIFY]: ValidateServerCertificate: Failed to Verify Server Certification for \"{0}\"", commonname); + } + return valid; + } + + + /// + /// Validate Client Certificate Callback Function + /// + /// + /// + /// + /// + /// + public bool ValidateClientCertificate(object obj, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + m_log.InfoFormat("[NSL CLIENT CERT VERIFY]: ValidateClientCertificate: Start."); + + if (certificate == null) + { + m_log.InfoFormat("[NSL CLIENT CERT VERIFY]: ValidateClientCertificate: Client does not have a Certificate!"); + return false; + } + + X509Certificate2 certificate2 = new X509Certificate2(certificate); + string commonname = certificate2.GetNameInfo(X509NameType.SimpleName, false); + m_log.InfoFormat("[NSL CLIENT CERT VERIFY]: ValidateClientCertificate: Common Name is \"{0}\"", commonname); + + // None, ChainErrors Anything other than that is an error. + if (sslPolicyErrors != SslPolicyErrors.None && sslPolicyErrors != SslPolicyErrors.RemoteCertificateChainErrors) + { + m_log.InfoFormat("[NSL CLIENT CERT VERIFY]: ValidateClientCertificate: Policy Error! {0}", sslPolicyErrors); + return false; + } + + // check CRL + if (m_clientcrl != null) + { + Mono.Security.X509.X509Certificate monocert = new Mono.Security.X509.X509Certificate(certificate.GetRawCertData()); + Mono.Security.X509.X509Crl.X509CrlEntry entry = m_clientcrl.GetCrlEntry(monocert); + if (entry != null) + { + m_log.InfoFormat("[NSL CLIENT CERT VERIFY]: Common Name \"{0}\" was revoked at {1}", commonname, entry.RevocationDate.ToString()); + return false; + } + } + + bool valid = CheckPrivateChain(certificate2); + if (valid) + { + m_log.InfoFormat("[NSL CLIENT CERT VERIFY]: Valid Client Certification for \"{0}\"", commonname); + } + else + { + m_log.InfoFormat("[NSL CLIENT CERT VERIFY]: Failed to Verify Client Certification for \"{0}\"", commonname); + } + return valid; + } + } + +} diff --git a/addon-modules/OpenSim-Modules-Currency/OpenSim.Modules.Currency/NSLXmlRpc.cs b/addon-modules/OpenSim-Modules-Currency/OpenSim.Modules.Currency/NSLXmlRpc.cs new file mode 100644 index 0000000..36e5f6b --- /dev/null +++ b/addon-modules/OpenSim-Modules-Currency/OpenSim.Modules.Currency/NSLXmlRpc.cs @@ -0,0 +1,216 @@ +/* + * Copyright (c) Contributors, http://www.nsl.tuis.ac.jp + * + +Funktion +Die Klasse NSLXmlRpcRequest erweitert XmlRpcRequest und implementiert eine Möglichkeit, XML-RPC-Requests (z.B. für einen MoneyServer) mit optionaler Client-Zertifikatsunterstützung und Serverzertifikatsprüfung zu senden. Sie serialisiert den Request, sendet ihn per HTTP(S) und deserialisiert die Antwort. +Null Pointer Checks + +1. Konstruktoren: + Initialisieren immer das Parameter-Array (_params). + Keine Gefahr für NullPointer in der Instanz selbst. + +2. certSend-Methode: + Prüft, ob das erzeugte HttpWebRequest-Objekt null ist. + Holt das Zertifikat via certVerify.GetPrivateCert(). Wenn dieses null ist, wird kein Zertifikat angehängt (kein Fehler). + Prüft, ob ein Zertifikats-Validator gesetzt werden muss. + Holt den Stream für den Request im Try/Catch. Bei Fehler wird geloggt und null zurückgegeben. + Holt die Response im Try/Catch. Fehler werden geloggt, aber das Programm läuft weiter. + Rückgabe von null, wenn der Stream nicht geöffnet werden kann. + Liest den Response-Stream, ohne expliziten Null-Check auf response. Wenn GetResponse() fehlschlägt, bleibt das Response-Objekt null, was zu einem Fehler führen kann, falls nicht abgefangen. + +Potenzielle Null Pointer: + Nach einem Fehler bei GetResponse() bleibt response null, aber danach wird direkt response.GetResponseStream() aufgerufen. + → Es fehlt ein expliziter Null-Check für response nach der Fehlerbehandlung! Das ist eine Schwachstelle: + C# + + HttpWebResponse response = null; + try { response = (HttpWebResponse)request.GetResponse(); } + catch (Exception ex) { ... } + // Kein Check auf response == null vor response.GetResponseStream() + +Fehlerquellen und Fehlerbehandlung + Netzwerkfehler: Werden beim Öffnen von Streams und Response sauber per Try/Catch behandelt und geloggt. + Serialisierung: Falls der Request-Stream nicht geöffnet werden kann, wird null zurückgegeben. + Fehlerhafte Zertifikate: Wenn kein Zertifikatsprüfer übergeben wird, wird die Serverzertifikatsprüfung ausgeschaltet (per Header). + Fehler beim Deserialisieren: Keine explizite Fehlerbehandlung beim Deserialisieren des Response-Streams, aber vorherige Fehler führen dazu, dass diese Zeile nicht ausgeführt wird. + +Zusammenfassung + Null Pointer: + Fast überall sicher behandelt, aber ein (kleiner) Fehler: Nach einem Fehler bei GetResponse() kann response null sein, was zu einem NullReferenceException bei response.GetResponseStream() führen kann. + → Empfohlen: Vor dem Streamzugriff explizit prüfen, ob response != null. + Fehlerquellen: + Netzwerk- und Zertifikatsfehler werden gut geloggt und führen zu Rückgabe von null. + Funktion: + Sichert und erweitert XML-RPC-Requests um Zertifikats- und Sicherheitsoptionen. + +Fazit: +Die Klasse ist robust, bis auf den fehlenden Null-Check für response nach GetResponse(). Ansonsten werden Null Pointer und Fehlerquellen sorgfältig behandelt. Logging ist umfassend. + +Empfehlung: +Füge vor dem Zugriff auf den Response-Stream einen Check ein: +C# + +if (response == null) return null; + + */ + + +using System; +using System.Collections; +using System.IO; +using System.Xml; +using System.Net; +using System.Text; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using log4net; +using Nwc.XmlRpc; +using System.Net.Security; +using NSL.Certificate.Tools; + + +namespace NSL.Network.XmlRpc +{ + public class NSLXmlRpcRequest : XmlRpcRequest + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + // The encoding + private Encoding _encoding = new UTF8Encoding(); + // The serializer + private XmlRpcRequestSerializer _serializer = new XmlRpcRequestSerializer(); + // The deserializer + private XmlRpcResponseDeserializer _deserializer = new XmlRpcResponseDeserializer(); + + + /// + /// Initializes a new instance of the class. + /// + /// + /// This constructor initializes the request with an empty parameter list. + /// + public NSLXmlRpcRequest() + { + // Initialize the parameter list as an empty ArrayList + _params = new ArrayList(); + } + + + /// + /// Initializes a new instance of the class. + /// + /// Name of the method to be invoked in the XML-RPC request. + /// The parameters to be passed to the method. + public NSLXmlRpcRequest(String methodName, IList parameters) + { + // Set the method name for the XML-RPC request + MethodName = methodName; + + // Set the parameters for the XML-RPC request + _params = parameters; + } + + + /// + /// Sends a certificate-based XML-RPC request to the specified URL. + /// + /// The URL of the XML-RPC server. + /// The certificate verification object. + /// Whether to check the server's certificate. + /// The timeout for the request in milliseconds. + /// The XML-RPC response from the server. + /// Thrown if there is an error with the request. + public XmlRpcResponse certSend(String url, NSLCertificateVerify certVerify, bool checkServerCert, Int32 timeout) + { + // Log the request URL + m_log.InfoFormat("[MONEY NSL XMLRPC]: XmlRpcResponse certSend: connect to {0}", url); + + // Create a new HTTP web request + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); + if (request == null) + { + // Throw an exception if the request cannot be created + throw new XmlRpcException(XmlRpcErrorCodes.TRANSPORT_ERROR, XmlRpcErrorCodes.TRANSPORT_ERROR_MSG + ": Could not create request with " + url); + } + + // Set the request method and content type + request.Method = "POST"; + request.ContentType = "text/xml"; + request.AllowWriteStreamBuffering = true; + request.Timeout = timeout; + request.UserAgent = "NSLXmlRpcRequest"; + + // Add the client certificate if provided + X509Certificate2 clientCert = null; + if (certVerify != null) + { + clientCert = certVerify.GetPrivateCert(); + if (clientCert != null) request.ClientCertificates.Add(clientCert); // Own certificate + request.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(certVerify.ValidateServerCertificate); + } + else + { + // Disable server certificate checking if no verification object is provided + checkServerCert = false; + } + + // Disable server certificate checking if requested + if (!checkServerCert) + { + request.Headers.Add("NoVerifyCert", "true"); // Do not verify the certificate of the other party + } + + // Get the request stream + Stream stream = null; + try + { + stream = request.GetRequestStream(); + } + catch (Exception ex) + { + // Log any errors getting the request stream + m_log.ErrorFormat("[MONEY NSL XMLRPC]: GetRequestStream Error: {0}", ex); + stream = null; + } + + // Return null if the request stream could not be obtained + if (stream == null) return null; + + // Serialize the request to the stream + XmlTextWriter xml = new XmlTextWriter(stream, _encoding); + _serializer.Serialize(xml, this); + xml.Flush(); + xml.Close(); + + // Get the response from the server + HttpWebResponse response = null; + try + { + response = (HttpWebResponse)request.GetResponse(); + } + catch (Exception ex) + { + // Log any errors getting the response + m_log.ErrorFormat("[MONEY NSL XMLRPC]: XmlRpcResponse certSend: GetResponse Error: {0}", ex.ToString()); + } + + // Return null if the response could not be obtained + if (response == null) + return null; + + // Deserialize the response from the server + StreamReader input = new StreamReader(response.GetResponseStream()); + string inputXml = input.ReadToEnd(); + XmlRpcResponse resp = (XmlRpcResponse)_deserializer.Deserialize(inputXml); + + // Close the input and response streams + input.Close(); + response.Close(); + + // Return the deserialized response + return resp; + } + } + +} diff --git a/addon-modules/OpenSim-Modules-Currency/OpenSim.Modules.Currency/Properties/AssemblyInfo.cs b/addon-modules/OpenSim-Modules-Currency/OpenSim.Modules.Currency/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e11b288 --- /dev/null +++ b/addon-modules/OpenSim-Modules-Currency/OpenSim.Modules.Currency/Properties/AssemblyInfo.cs @@ -0,0 +1,34 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Mono.Addins; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("OpenSim.Modules.Currency")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("DTL/NSL Group")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2009")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2363e3c9-7be0-4b9b-84ac-82923d5021f4")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/addon-modules/OpenSim-Modules-Currency/prebuild-OpenSimModulesCurrency.xml b/addon-modules/OpenSim-Modules-Currency/prebuild-OpenSimModulesCurrency.xml new file mode 100644 index 0000000..8b7b71e --- /dev/null +++ b/addon-modules/OpenSim-Modules-Currency/prebuild-OpenSimModulesCurrency.xml @@ -0,0 +1,45 @@ + + + + + ../../bin/ + true + + + + + ../../bin/ + true + + + + ../../bin/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addon-modules/README b/addon-modules/README new file mode 100644 index 0000000..0c722c9 --- /dev/null +++ b/addon-modules/README @@ -0,0 +1,9 @@ +In this directory you can place addon modules for OpenSim + +Each module should be in it's own tree and the root of the tree +should contain a file named "prebuild.xml", which will be included in the +main prebuild file. + +The prebuild.xml should only contain and associated child tags. +The , , and tags should not be +included since the add-on modules prebuild.xml will be inserted directly into the main prebuild.xml diff --git a/bin/MoneyServer.dll.config b/bin/MoneyServer.dll.config new file mode 100644 index 0000000..1f31302 --- /dev/null +++ b/bin/MoneyServer.dll.config @@ -0,0 +1,29 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/MoneyServer.ini.example b/bin/MoneyServer.ini.example new file mode 100644 index 0000000..db21b4a --- /dev/null +++ b/bin/MoneyServer.ini.example @@ -0,0 +1,99 @@ +[Startup] +; Path to create the PID file for identifying the process. +PIDFile = "/tmp/money.pid" + +[MySql] +; MySQL database connection settings: +hostname = "localhost" ; Hostname of the MySQL server. +database = "robust" ; Name of the database to use. +username = "opensim" ; Username for database access. +password = "opensim" ; Password for database access. +pooling = "true" ; Enable connection pooling (true/false). +port = "3306" ; MySQL server port (default: 3306). +MaxConnection = "25" ; Maximum number of connections in the pool. + +[MoneyServer] +; Port number on which the MoneyServer listens. +ServerPort = 8008 + +; Enable or disable the currency system ("on" to enable, "off" to disable). +CurrencyOnOff = on + +; Maximum amount of currency a user can hold. +CurrencyMaximum = 20000 + +; URL or IP address where the MoneyServer can be reached. +MoneyServerIPaddress = "http://127.0.0.1:8008" + +; Default starting balance for new users. +DefaultBalance = 1000 + +; Restrict currency purchases to members of a specific group (true/false). +CurrencyGroupOnly = true +; Group ID required for currency purchases (UUID format). +CurrencyGroupID = "00000000-0000-0000-0000-000000000000" + +; Require email verification for currency purchases (true/false). +UserMailLock = true + +; Allow transactions with an amount of zero (true/false, default: false). +EnableAmountZero = true + +; Cost multiplier for each unit of currency purchased (integer value). +CalculateCurrency = 10 + +; UUID of the avatar designated as the "banker". +; Use "00000000-0000-0000-0000-000000000000" to allow all avatars, or leave blank to disable the banker function. +BankerAvatar = "00000000-0000-0000-0000-000000000000" + +; Allow forced money transfers via scripts (true/false). +EnableForceTransfer = true +; Allow scripts to send money (true/false). +EnableScriptSendMoney = true + +; Default balances for HyperGrid and guest users. +HGAvatarDefaultBalance = 500 +GuestAvatarDefaultBalance = 500 + +; Access key and IP address for MoneyScript integration. +MoneyScriptAccessKey = "123456789" +MoneyScriptIPaddress = "127.0.0.1" + +; Enable currency support for HyperGrid and guest avatars (true/false). +EnableHGAvatar = true +EnableGuestAvatar = true + +; Messages shown to users when their balance is updated. +; Use {0} for the amount, {1} for the other party's name, and {2} for object names. +BalanceMessageSendGift = "Sent Gift L${0} to {1}." +BalanceMessageReceiveGift = "Received Gift L${0} from {1}." +BalanceMessagePayCharge = "Paid the Money L${0} for creation." +BalanceMessageBuyObject = "Bought the Object {2} from {1} by L${0}." +BalanceMessageSellObject = "{1} bought the Object {2} by L${0}." +BalanceMessageLandSale = "Paid the Money L${0} for Land." +BalanceMessageScvLandSale = "Paid the Money L${0} for Land." +BalanceMessageGetMoney = "Got the Money L${0} from {1}." +BalanceMessageBuyMoney = "Bought the Money L${0}." +BalanceMessageRollBack = "RollBack the Transaction: L${0} from/to {1}." +BalanceMessageSendMoney = "Paid the Money L${0} to {1}." +BalanceMessageReceiveMoney = "Received L${0} from {1}." + +[Certificate] +; Certificate settings for secure connections: + +; CA certificate for verifying client/server certificates. +;CACertFilename = "cacert.crt" + +; Server SSL certificate (for HTTPS server mode). +;ServerCertFilename = "server_cert.p12" +;ServerCertPassword = "opensim" + +; Enable verification of client certificates (true/false). +CheckClientCert = false + +; Enable verification of server certificates (true/false). +CheckServerCert = false + +; Client certificate (for authentication with other servers). +;ClientCertFilename = "client_cert.p12" +;ClientCertPassword = "opensim" diff --git a/bin/OpenSim.ini.sample b/bin/OpenSim.ini.sample new file mode 100644 index 0000000..bb28f44 --- /dev/null +++ b/bin/OpenSim.ini.sample @@ -0,0 +1,55 @@ +[Economy] + ;; Enables selling objects in-world. Set to false to disable all selling functionality. + SellEnabled = true + + ;CurrencyServer = "" ;; Example: "https://opensim.net:8008/" Default is "" + ;; The URL of your external Money Server. The placeholder "${Const|BaseURL}" will be replaced at runtime with your actual base URL. + EconomyModule = DTLNSLMoneyModule + CurrencyServer = "${Const|BaseURL}:8008/" + ;; The UserServer is typically on port 8002, used for avatar and user account management. + UserServer = "${Const|BaseURL}:8002/" + + ;; Set to true to enable SSL/TLS certificate verification for the Money Server. For testing, false is safer. For production, true is recommended. + CheckServerCert = false + + ;; The fee (in in-world currency units) to upload textures, animations etc. Default is 0 (free uploads). + PriceUpload = 0 + + ;; Mesh upload cost multipliers. Adjust these to change the price for uploading mesh models and textures. + ;; 1.0 means default cost, higher values increase the cost. + MeshModelUploadCostFactor = 1.0 + MeshModelUploadTextureCostFactor = 1.0 + MeshModelMinCostFactor = 1.0 + + ;; The fee (in in-world currency units) to create a new group. Default is 0 (creating groups is free). + PriceGroupCreate = 0 + + ;; The following values are sent to the viewer, but are not always used by OpenSim itself. + ;; Their impact may depend on the viewer or custom modules—change only if you know what you want to achieve! + ObjectCount = 0 ;; Number of allowed objects (sent to client, often ignored) + PriceEnergyUnit = 0 ;; Cost per energy unit (seldom used) + PriceObjectClaim = 0 ;; Cost to claim an object (usually zero) + PricePublicObjectDecay = 0 ;; Cost when a public object decays (rarely used) + PricePublicObjectDelete = 0 ;; Cost to delete a public object (rarely used) + PriceParcelClaim = 0 ;; Cost to claim a parcel of land + PriceParcelClaimFactor = 1 ;; Multiplier for parcel claim cost + + PriceRentLight = 0 ;; Cost to rent a light (seldom used) + TeleportMinPrice = 0 ;; Minimum cost for teleportation (usually zero) + TeleportPriceExponent = 2 ;; Exponent for calculating teleport costs (rarely changed) + EnergyEfficiency = 1 ;; Efficiency factor, usually 1 (default) + PriceObjectRent = 0 ;; Cost to rent an object (seldom used) + PriceObjectScaleFactor = 10 ;; Scale factor for object-related costs (adjust as needed) + PriceParcelRent = 0 ;; Cost to rent a parcel (usually zero) + + ; Mesh upload settings, these options work regardless of the economy module you use. + + ; If true, textures uploaded with a mesh model are also added to the user's inventory. + ; Default is false (textures are not added separately). + ; MeshModelAllowTextureToInventory = true + + ;; Avatar Class for HG Avatar: + ;; Possible values: ForeignAvatar, HGAvatar, GuestAvatar, LocalAvatar. + ;; Default is HGAvatar. This setting determines how HyperGrid avatars are handled. + ;; The actual processing for each avatar class depends on the configuration of your Money Server. + ;HGAvatarAs = "HGAvatar" diff --git a/bin/SineWaveCert.pfx b/bin/SineWaveCert.pfx new file mode 100644 index 0000000000000000000000000000000000000000..11c8ae5402e10c59fe8013a175bc57b096aeb9e8 GIT binary patch literal 1755 zcmY*Y2~?9;7Jm7Y{1707KmfO3PdZ&$l?IvLPca1=C^02o}PQ|x!?QlckeszocAF@_z{G` zB0@L|CRmABqD3M`1tS&0XK_MUfYQl`5c}{?Eo?JRh;2maduZ0c>i?V4P{Tk{A%v1p z4PpqZ{hvmcYvNQ(?H)9~m~AwNAeBCx5K3>ZQSb7luB*FuIJ4rT()-Q$l2fXhBg8P) zqv#*LPTM=(wl{`w$twTy# zJ4;MoN%Gz_Y|INZW2l;s=WuEj2b6ZHBJTWhAmwvUCSaZ)iR-;mS@93dGmU?KSikI; z-TIT-u(~xqZ>)~=x@BI@sE#&bIbF=t7 z$_xCYLY2Thwhfa|y5^r8Z+Wrp&Iv86+_vtFmUlJMJW2;y9BI^0HLzeMXsSvIkBJ_Z zs7oho?i67ie;j9g=CL!(d-Z;3mfS7WJhfz5W$ZDT7;CF+G}qrY*nYrovr%iXv>WI9 zsI*o2izmzIwXD;*BHKjV1kXd0SLw@irE{&XPTe!=-{9Rz>Z)GE#@-#R3rFr(G;+({ zyjl$DZ!2@GV`{fo8BE3G+f#0pxk%p#*;>H?lSRJOqH&vRc4- zI9k}|(8L&4j*strCEDOJJ9lOAjNiSS*!e|?yD&tr*E`=Ni&a<~v@zkYP5n(cUsu>h z8NzLO;uz6Nnhy{XV-xGPJP$35jA)RstlA~@i4mLBNLqdMDav%&21sPjJv9fcrl$nX@*taH<+sx_?tXnqm8D*&4hiK0zXev2*W)>sPT|&(l3zrG*2{K^AO&`vNJVXyd_|E<4?0SNuZlq7+UvpCGI3zc0&b zN%z?8$G&qA&gzvvw`h^L(de64`2JO%X^RtVUOxW5AZw8JZ|jl&_*@hxS(UKAxjlNk z_}(okkGA*e?2b#xSVl$rIIW{?aCCE>IKW?F@$_xWh0WnX3!bbLlVbAa%G%9-YnFnC zv*a6cudk9FOf~GuqxZk)eVZTL;Pi0rZi2B~F>YbHA76Z`s4VSmhRcf7Fn{}BinDh; z&{@dIF#Cm#PaOYgr0m+dkN7aFK(5l)>~WQ@EI+y+YuQTDK0AQ>mv&UY?bcX6V``+N zE9}?#=^h5*>l}<$^4>RndvvxSrc27)#gvNc=@jw_C1HL^-!ivuF1$2B&qY0v&|hL; znc(y&MET9FJ@((%BPVLKtnT(q&XB7wcb-;8u4&8TISs=_ik(B5pSib9uk0&ezvwbn z!$VIrwnqa!fe+W2t1)ox`8^%}7oa+?)w-jg#QQVg8bi+r<+?TFyLJ`>N-IQ(vnFk;zL6ule2qNQ0Hn*Tj-&@^x2 z1LbLg;JvPI=7!Ybl13cGgOl>r?(_Yn6?9F~@BzdHF-BBiJWUe|8Q>^aRxK(yikD== p$QvtXovt~Hm|8H7vRLF;GA+9q_dNCZ?6>~OyWU|gm3{o6{{=7<-unOm literal 0 HcmV?d00001 diff --git a/bin/server_cert.p12 b/bin/server_cert.p12 new file mode 100644 index 0000000000000000000000000000000000000000..16adb0142af1ca8eef9c0fe727f52b561dff1867 GIT binary patch literal 2685 zcmV-@3WD`8f(m&80Ru3C3O@!3Duzgg_YDCD0ic2kFa&}MEHHu!C@_KtCk6>BhDe6@ z4FLxRpn?Y&FoFjZ0s#Opf(H!-2`Yw2hW8Bt2LUh~1_~;MNQUMX)+E!KX?8BywRHkLKF; z5=r_>RqJTh`9|entB1V}372SWBjxdbm#K${KuGqH+gz_;rs}?Awk;v=D(11-SqeWM z3@Sxuhoe7d+zpuBuvpdun@hnn+;R0HIb~mZJvr3x+q(Qa6MMW7bB6~_tsx;F)Uz7b z17T7g>_#Gl^|32e#JZP*{bFw9)WZ}PgSU&Yi)xcFax<0`ZfQ#s;Z1dHK)fDEDSZG| zdWr}}LK>a8ih=JcejBc!Ww7tY=kpr$m&?%tb7(Akhqmx@KY&NOi^nQtGp;mEoetu= zKBndMvL0JjNnu6w73rNS2h?~8(Bq6Dr}Dlnc^@PO8K=waczG0if>LafTRV8GY2nN| zCo3(y;KF*!1#4N;wBf>pOj>h{@;9oYOq+E2AG#4e`_Qdn(n+4?ow7bJ=2rN zN&3UdPApOG)qjbR(Dzd9yQ^t?Y$kQsJByfUI{@{lrJ1acjN1azMg>u-%{M=8KO0={ z>-BEjbcMA~e-Jnzw9cmxqt=$6MbdtALm!5bkv#n3oGc06=3QT%Kh~ELOq*w$71J z2YPq*D;mh-jMoHU6T=2aT520n%aDydJEI|G3JQG&wN^c4{nie;&`L|9^ z-St}eqi8ZJF9B|2=AoC4gGTfXgYF87xX`Gn-QLmTEys7r*&A*PZdGH0?Eo?+qk8|YruMTery{LQW~oyp4}hq}Llq!v&qVjX_S*pe zu0t$xSz2&o6LYL>j>ykhofhaFY!Wh#dzN6HL6Y$qGG-_U7Vh_)-;vp62Klm$x2re7 zkn5yFw3=j~^Z+{8USwkv@H)Ru1^`C=A_|45R$s+58p~X5N2{2?ELFHz-PaT!{hmJ@18aznK55gl_zJN z;c_{8sa?+OQEmw%rW~^^54=o6X)e-{--BIK!N(Zdue45Mey!mEjw^kqUkjv-JDsnk z4=X7tB6iH>g^}IGre|PI$%FY>CkYX@g8T*AN2~x$chAO z**$7~iMyJebC8FEc+cRf*k%W?R}gkyNl0MQlUj3s@1je5L4PUXSXMiUaNFeZ0nk;8&5f> zm{X!YEwWB;B*`BoW(fJpr2Ma{Q`4Z2{QE!GDqze>ocWZLBj5~VjxN$~ zw%@xia)J=IcA<=z?=l{B6oN`P%wa>?IAB){Rv&iL>&k7mOd&f`1LQwXYQ)oyn=G+V zmIqY_7v(dm=i0XwU$UNu<+>}QENks$W+ABUy;wKGKFDJ{rCrbV!7Zy9E-P?`RI6O% zm~OK#u|dw*0qG}F+kZN`O=Y()LHV)4no z`n@t0FSDAPj;$C`B>382k+!3VD{LwIt)`PjBzGxWIzpPf{7*M1{U@!tJvY$eBJ*F+ z54VVs?iYA{c)mkp0mQd*MUoH3wP~N1gv9?YwI6;sp_#pfu!hg13p0XYGV4rwO3}tu zG+8L=7AqraZFx^JwF^5_@Nx8s+}h=r-n_jcZfh0CcqXpv)5*yO|D!gBP|~z1h`g{K zeiv4IFoFX41_>&LNQUgrADs44$(@&Pfi17uwKslr&RR-aIz_|by}$!bJsEK9npV?7fe~*k`eVDG=Cyy za7%m-Yg#XM=w$Y^8QpqS8^t#lf@4q@)YyRs#@OTY#K>Sj7 zQK;4!9JU8WerhfRKLhU@A51K;5<{xc(@35}Ju%OMXLY@?p@*sg*dd_c z=Js*<1v6Xr%p2L6;R`5^xEUmpP)DV(^y^H zhkTh?_JbPIGfVHa*hzmI4r~%yQ)xAg1a)q_-rV(K8%M?@~S4jDZ!eaPEy!?q9Om4KB zsY@>mWKmvN&3eAY6s{wRAwY3%%*$@X+oGX>@YTFE=s<&+^?#8#5q{pQ5vQRtr9K@H z;JUR&b0+;3c&30mVDdrH+q6p!t$O=oD<~@wqaddj1$+^qI6_>3Sw)HoN5e7XdMf4GuYpbm&kYZR+DnYTZ2b;2AQ1%*bu&G>$GhJ>8M#9I}`j5lF;rM6H)?c7_gZ* z9OW@3Fe3&DDuzgg_YDCF6)_eB6t5cAjKi+?L{Q~X`;j2Mtk~JrnlLdiAutIB1uG5% r0vZJX1QakQk%+#Kt1Hd-uP}^eq!;o4Mf(H@T1wDXXhUL$0s;sCstfVb literal 0 HcmV?d00001