From 8f5edd57c4d63d29b325cb64befea62377f25bbf Mon Sep 17 00:00:00 2001 From: root Date: Wed, 20 Dec 2023 10:03:33 +0900 Subject: [PATCH] first --- .../IMoneyManager.cs | 71 + .../MySQLMoneyManager.cs | 1627 ++++++++++++++ .../MySQLSuperManager.cs | 53 + .../Properties/AssemblyInfo.cs | 36 + .../TransactionData.cs | 237 ++ OpenSim.Grid.MoneyServer/IMoneyDBService.cs | 77 + OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs | 52 + OpenSim.Grid.MoneyServer/MoneyDBService.cs | 584 +++++ OpenSim.Grid.MoneyServer/MoneyServerBase.cs | 407 ++++ OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs | 1807 +++++++++++++++ OpenSim.Grid.MoneyServer/Program.cs | 42 + .../Properties/AssemblyInfo.cs | 36 + OpenSim.Modules.Currency/DTLNSLMoneyModule.cs | 1920 ++++++++++++++++ .../NSLCertificateTools.cs | 291 +++ OpenSim.Modules.Currency/NSLXmlRpc.cs | 115 + .../Properties/AssemblyInfo.cs | 36 + README | 127 ++ README.jp | 138 ++ Release.txt | 16 + bin/net6.0/MoneyServer | Bin 0 -> 142840 bytes bin/net6.0/MoneyServer.dll | Bin 0 -> 73728 bytes bin/net6.0/MoneyServer.runtimeconfig.json | 13 + ...enSim.Data.MySQL.MySQLMoneyDataWrapper.dll | Bin 0 -> 49152 bytes bin/net6.0/OpenSim.Modules.Currency.dll | Bin 0 -> 57344 bytes build.net.sh | 45 + build.sh | 43 + build_opensim.sh | 60 + clean.sh | 8 + config/MoneyServer.exe.config | 29 + config/MoneyServer.ini | 105 + config/OpenSim.ini.sample | 43 + config/ResetDB_to_0.8.2.sql | 7 + config/SineWaveCert.pfx | Bin 0 -> 1755 bytes config/opensim_money.net.service | 30 + config/server_cert.p12 | Bin 0 -> 2685 bytes .../original/source/HttpClientContext.cs | 578 +++++ .../original/source/HttpContextFactory.cs | 354 +++ .../original/source/IHttpClientContext.cs | 143 ++ old/HttpServer_OpenSim/patch/HttpServer.patch | 116 + .../source/HttpClientContext.cs | 599 +++++ .../source/HttpContextFactory.cs | 379 ++++ .../source/IHttpClientContext.cs | 148 ++ patch/HttpServer-20130201.patch | 109 + patch/HttpServer-20131020.patch | 112 + patch/HttpServer-20140307.patch | 116 + patch/opensim-0.7.5.patch | 26 + patch/opensim-0.7.6.patch | 26 + patch/opensim-0.8.0.patch | 26 + patch/opensim-0.8.1.patch | 26 + patch/opensim-0.8.2.patch | 26 + patch/opensim-0.9.0.patch | 46 + patch/opensim-0.9.1.patch | 15 + patch/opensim-0.9.2.2.patch | 30 + patch/opensim-0.9.2.patch | 19 + patch/opensim_server.patch | 1 + prebuild.net.xml | 142 ++ prebuild.xml | 159 ++ release/0.8.2/bin/MoneyServer.exe | Bin 0 -> 53248 bytes ...enSim.Data.MySQL.MySQLMoneyDataWrapper.dll | Bin 0 -> 29184 bytes .../0.8.2/bin/OpenSim.Modules.Currency.dll | Bin 0 -> 39936 bytes .../IMoneyManager.cs | 71 + .../MySQLMoneyManager.cs | 1133 ++++++++++ .../MySQLSuperManager.cs | 52 + .../Properties/AssemblyInfo.cs | 36 + .../TransactionData.cs | 160 ++ .../IMoneyDBService.cs | 72 + .../IMoneyServiceCore.cs | 44 + .../MoneyDBService.cs | 496 +++++ .../MoneyServerBase.cs | 323 +++ .../MoneyXmlRpcModule.cs | 1761 +++++++++++++++ .../src/OpenSim.Grid.MoneyServer/Program.cs | 42 + .../Properties/AssemblyInfo.cs | 36 + .../DTLNSLMoneyModule.cs | 1940 +++++++++++++++++ .../NSLCertificateTools.cs | 215 ++ .../src/OpenSim.Modules.Currency/NSLXmlRpc.cs | 88 + .../Properties/AssemblyInfo.cs | 36 + release/0.9.1/bin/MoneyServer.exe | Bin 0 -> 58880 bytes ...enSim.Data.MySQL.MySQLMoneyDataWrapper.dll | Bin 0 -> 38912 bytes .../0.9.1/bin/OpenSim.Modules.Currency.dll | Bin 0 -> 43008 bytes .../IMoneyManager.cs | 71 + .../MySQLMoneyManager.cs | 1622 ++++++++++++++ .../MySQLSuperManager.cs | 52 + .../Properties/AssemblyInfo.cs | 36 + .../TransactionData.cs | 237 ++ .../IMoneyDBService.cs | 70 + .../IMoneyServiceCore.cs | 52 + .../MoneyDBService.cs | 570 +++++ .../MoneyServerBase.cs | 338 +++ .../MoneyXmlRpcModule.cs | 1740 +++++++++++++++ .../src/OpenSim.Grid.MoneyServer/Program.cs | 42 + .../Properties/AssemblyInfo.cs | 36 + .../DTLNSLMoneyModule.cs | 1903 ++++++++++++++++ .../NSLCertificateTools.cs | 226 ++ .../src/OpenSim.Modules.Currency/NSLXmlRpc.cs | 96 + .../Properties/AssemblyInfo.cs | 36 + release/0.9.1_1/bin/MoneyServer.exe | Bin 0 -> 59904 bytes ...enSim.Data.MySQL.MySQLMoneyDataWrapper.dll | Bin 0 -> 38912 bytes .../0.9.1_1/bin/OpenSim.Modules.Currency.dll | Bin 0 -> 43008 bytes .../IMoneyManager.cs | 71 + .../MySQLMoneyManager.cs | 1622 ++++++++++++++ .../MySQLSuperManager.cs | 52 + .../TransactionData.cs | 237 ++ .../IMoneyDBService.cs | 70 + .../IMoneyServiceCore.cs | 52 + .../MoneyDBService.cs | 570 +++++ .../MoneyServerBase.cs | 339 +++ .../MoneyXmlRpcModule.cs | 1758 +++++++++++++++ .../src/OpenSim.Grid.MoneyServer/Program.cs | 42 + .../DTLNSLMoneyModule.cs | 1903 ++++++++++++++++ .../NSLCertificateTools.cs | 226 ++ .../src/OpenSim.Modules.Currency/NSLXmlRpc.cs | 96 + release/0.9.2/bin/MoneyServer.exe | Bin 0 -> 61440 bytes ...enSim.Data.MySQL.MySQLMoneyDataWrapper.dll | Bin 0 -> 38912 bytes .../0.9.2/bin/OpenSim.Modules.Currency.dll | Bin 0 -> 45056 bytes .../IMoneyManager.cs | 71 + .../MySQLMoneyManager.cs | 1627 ++++++++++++++ .../MySQLSuperManager.cs | 53 + .../TransactionData.cs | 237 ++ .../IMoneyDBService.cs | 77 + .../IMoneyServiceCore.cs | 52 + .../MoneyDBService.cs | 584 +++++ .../MoneyServerBase.cs | 407 ++++ .../MoneyXmlRpcModule.cs | 1807 +++++++++++++++ .../src/OpenSim.Grid.MoneyServer/Program.cs | 42 + .../DTLNSLMoneyModule.cs | 1920 ++++++++++++++++ .../NSLCertificateTools.cs | 291 +++ .../src/OpenSim.Modules.Currency/NSLXmlRpc.cs | 115 + runprebuild.bat | 61 + runprebuild.net.bat | 18 + runprebuild.net.sh | 6 + runprebuild.sh | 4 + 131 files changed, 39293 insertions(+) create mode 100644 OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs create mode 100644 OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs create mode 100644 OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs create mode 100644 OpenSim.Data.MySQL.MySQLMoneyDataWrapper/Properties/AssemblyInfo.cs create mode 100644 OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs create mode 100644 OpenSim.Grid.MoneyServer/IMoneyDBService.cs create mode 100644 OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs create mode 100644 OpenSim.Grid.MoneyServer/MoneyDBService.cs create mode 100644 OpenSim.Grid.MoneyServer/MoneyServerBase.cs create mode 100644 OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs create mode 100644 OpenSim.Grid.MoneyServer/Program.cs create mode 100644 OpenSim.Grid.MoneyServer/Properties/AssemblyInfo.cs create mode 100644 OpenSim.Modules.Currency/DTLNSLMoneyModule.cs create mode 100644 OpenSim.Modules.Currency/NSLCertificateTools.cs create mode 100644 OpenSim.Modules.Currency/NSLXmlRpc.cs create mode 100644 OpenSim.Modules.Currency/Properties/AssemblyInfo.cs create mode 100644 README create mode 100644 README.jp create mode 100644 Release.txt create mode 100755 bin/net6.0/MoneyServer create mode 100644 bin/net6.0/MoneyServer.dll create mode 100644 bin/net6.0/MoneyServer.runtimeconfig.json create mode 100644 bin/net6.0/OpenSim.Data.MySQL.MySQLMoneyDataWrapper.dll create mode 100644 bin/net6.0/OpenSim.Modules.Currency.dll create mode 100755 build.net.sh create mode 100755 build.sh create mode 100755 build_opensim.sh create mode 100755 clean.sh create mode 100644 config/MoneyServer.exe.config create mode 100644 config/MoneyServer.ini create mode 100644 config/OpenSim.ini.sample create mode 100644 config/ResetDB_to_0.8.2.sql create mode 100644 config/SineWaveCert.pfx create mode 100644 config/opensim_money.net.service create mode 100644 config/server_cert.p12 create mode 100644 old/HttpServer_OpenSim/original/source/HttpClientContext.cs create mode 100644 old/HttpServer_OpenSim/original/source/HttpContextFactory.cs create mode 100644 old/HttpServer_OpenSim/original/source/IHttpClientContext.cs create mode 100644 old/HttpServer_OpenSim/patch/HttpServer.patch create mode 100644 old/HttpServer_OpenSim/source/HttpClientContext.cs create mode 100644 old/HttpServer_OpenSim/source/HttpContextFactory.cs create mode 100644 old/HttpServer_OpenSim/source/IHttpClientContext.cs create mode 100644 patch/HttpServer-20130201.patch create mode 100644 patch/HttpServer-20131020.patch create mode 100644 patch/HttpServer-20140307.patch create mode 100644 patch/opensim-0.7.5.patch create mode 100644 patch/opensim-0.7.6.patch create mode 100644 patch/opensim-0.8.0.patch create mode 100644 patch/opensim-0.8.1.patch create mode 100644 patch/opensim-0.8.2.patch create mode 100644 patch/opensim-0.9.0.patch create mode 100644 patch/opensim-0.9.1.patch create mode 100644 patch/opensim-0.9.2.2.patch create mode 100644 patch/opensim-0.9.2.patch create mode 120000 patch/opensim_server.patch create mode 100644 prebuild.net.xml create mode 100644 prebuild.xml create mode 100755 release/0.8.2/bin/MoneyServer.exe create mode 100755 release/0.8.2/bin/OpenSim.Data.MySQL.MySQLMoneyDataWrapper.dll create mode 100755 release/0.8.2/bin/OpenSim.Modules.Currency.dll create mode 100644 release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs create mode 100644 release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs create mode 100644 release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs create mode 100644 release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/Properties/AssemblyInfo.cs create mode 100644 release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs create mode 100644 release/0.8.2/src/OpenSim.Grid.MoneyServer/IMoneyDBService.cs create mode 100644 release/0.8.2/src/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs create mode 100644 release/0.8.2/src/OpenSim.Grid.MoneyServer/MoneyDBService.cs create mode 100644 release/0.8.2/src/OpenSim.Grid.MoneyServer/MoneyServerBase.cs create mode 100644 release/0.8.2/src/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs create mode 100644 release/0.8.2/src/OpenSim.Grid.MoneyServer/Program.cs create mode 100644 release/0.8.2/src/OpenSim.Grid.MoneyServer/Properties/AssemblyInfo.cs create mode 100644 release/0.8.2/src/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs create mode 100644 release/0.8.2/src/OpenSim.Modules.Currency/NSLCertificateTools.cs create mode 100644 release/0.8.2/src/OpenSim.Modules.Currency/NSLXmlRpc.cs create mode 100644 release/0.8.2/src/OpenSim.Modules.Currency/Properties/AssemblyInfo.cs create mode 100644 release/0.9.1/bin/MoneyServer.exe create mode 100644 release/0.9.1/bin/OpenSim.Data.MySQL.MySQLMoneyDataWrapper.dll create mode 100644 release/0.9.1/bin/OpenSim.Modules.Currency.dll create mode 100644 release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs create mode 100644 release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs create mode 100644 release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs create mode 100644 release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/Properties/AssemblyInfo.cs create mode 100644 release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs create mode 100644 release/0.9.1/src/OpenSim.Grid.MoneyServer/IMoneyDBService.cs create mode 100644 release/0.9.1/src/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs create mode 100644 release/0.9.1/src/OpenSim.Grid.MoneyServer/MoneyDBService.cs create mode 100644 release/0.9.1/src/OpenSim.Grid.MoneyServer/MoneyServerBase.cs create mode 100644 release/0.9.1/src/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs create mode 100644 release/0.9.1/src/OpenSim.Grid.MoneyServer/Program.cs create mode 100644 release/0.9.1/src/OpenSim.Grid.MoneyServer/Properties/AssemblyInfo.cs create mode 100644 release/0.9.1/src/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs create mode 100644 release/0.9.1/src/OpenSim.Modules.Currency/NSLCertificateTools.cs create mode 100644 release/0.9.1/src/OpenSim.Modules.Currency/NSLXmlRpc.cs create mode 100644 release/0.9.1/src/OpenSim.Modules.Currency/Properties/AssemblyInfo.cs create mode 100644 release/0.9.1_1/bin/MoneyServer.exe create mode 100644 release/0.9.1_1/bin/OpenSim.Data.MySQL.MySQLMoneyDataWrapper.dll create mode 100644 release/0.9.1_1/bin/OpenSim.Modules.Currency.dll create mode 100644 release/0.9.1_1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs create mode 100644 release/0.9.1_1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs create mode 100644 release/0.9.1_1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs create mode 100644 release/0.9.1_1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs create mode 100644 release/0.9.1_1/src/OpenSim.Grid.MoneyServer/IMoneyDBService.cs create mode 100644 release/0.9.1_1/src/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs create mode 100644 release/0.9.1_1/src/OpenSim.Grid.MoneyServer/MoneyDBService.cs create mode 100644 release/0.9.1_1/src/OpenSim.Grid.MoneyServer/MoneyServerBase.cs create mode 100644 release/0.9.1_1/src/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs create mode 100644 release/0.9.1_1/src/OpenSim.Grid.MoneyServer/Program.cs create mode 100644 release/0.9.1_1/src/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs create mode 100644 release/0.9.1_1/src/OpenSim.Modules.Currency/NSLCertificateTools.cs create mode 100644 release/0.9.1_1/src/OpenSim.Modules.Currency/NSLXmlRpc.cs create mode 100644 release/0.9.2/bin/MoneyServer.exe create mode 100644 release/0.9.2/bin/OpenSim.Data.MySQL.MySQLMoneyDataWrapper.dll create mode 100644 release/0.9.2/bin/OpenSim.Modules.Currency.dll create mode 100644 release/0.9.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs create mode 100644 release/0.9.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs create mode 100644 release/0.9.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs create mode 100644 release/0.9.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs create mode 100644 release/0.9.2/src/OpenSim.Grid.MoneyServer/IMoneyDBService.cs create mode 100644 release/0.9.2/src/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs create mode 100644 release/0.9.2/src/OpenSim.Grid.MoneyServer/MoneyDBService.cs create mode 100644 release/0.9.2/src/OpenSim.Grid.MoneyServer/MoneyServerBase.cs create mode 100644 release/0.9.2/src/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs create mode 100644 release/0.9.2/src/OpenSim.Grid.MoneyServer/Program.cs create mode 100644 release/0.9.2/src/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs create mode 100644 release/0.9.2/src/OpenSim.Modules.Currency/NSLCertificateTools.cs create mode 100644 release/0.9.2/src/OpenSim.Modules.Currency/NSLXmlRpc.cs create mode 100755 runprebuild.bat create mode 100755 runprebuild.net.bat create mode 100755 runprebuild.net.sh create mode 100755 runprebuild.sh diff --git a/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs b/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs new file mode 100644 index 0000000..f807ecb --- /dev/null +++ b/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs @@ -0,0 +1,71 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +//using System.Linq; +using System.Text; +using OpenMetaverse; + +namespace OpenSim.Data.MySQL.MySQLMoneyDataWrapper +{ + public interface IMoneyManager + { + int getBalance(string userID); + + //int getBalanceStatus(string userID); + + //bool updateBalanceStatus(string userID,int status); + + bool withdrawMoney(UUID transactionID, string senderID, int amount); + + bool giveMoney(UUID transactionID, string receiverID, int amount); + + bool addTransaction(TransactionData transaction); + + bool updateTransactionStatus(UUID transactionID, int status, string description); + + TransactionData FetchTransaction(UUID transactionID); + + TransactionData[] FetchTransaction(string userID, int startTime, int endTime, uint index, uint retNum); + + int getTransactionNum(string userID, int startTime, int endTime); + + bool addUser(string userID, int balance, int status, int type); + + bool SetTransExpired(int deadTime); + + bool ValidateTransfer(string secureCode, UUID transactionID); + + bool addUserInfo(UserInfo user); + + UserInfo fetchUserInfo(string userID); + + bool updateUserInfo(UserInfo user); + + } +} diff --git a/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs b/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs new file mode 100644 index 0000000..d260035 --- /dev/null +++ b/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs @@ -0,0 +1,1627 @@ +/* + * 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. + */ + +using System; +using System.Data; +using System.Reflection; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using log4net; +using MySql.Data.MySqlClient; +using OpenMetaverse; + + +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; + private MySqlConnection dbcon; + + + public MySQLMoneyManager(string hostname,string database,string username ,string password,string cpooling, string port) + { + string s = "Server=" + hostname + ";Port=" + port + ";Database=" + database + + ";User ID=" + username + ";Password=" + password + ";Pooling=" + cpooling + ";"; + Initialise(s); + } + + + public MySQLMoneyManager(string connect) + { + Initialise(connect); + } + + + private void Initialise(string connect) + { + try { + connectString = connect; + dbcon = new MySqlConnection(connectString); + try { + dbcon.Open(); + } + catch (Exception e) { + throw new Exception("[MONEY MANAGER]: Connection error while using connection string ["+connectString+"]", e); + } + //m_log.Info("[MONEY MANAGER]: Connection established"); + } + + catch(Exception e) { + throw new Exception("[MONEY MANAGER]: Error initialising MySql Database: " + e.ToString()); + } + + try { + Dictionary tableList = new Dictionary(); + tableList = CheckTables(); + + // + // Balances Table + 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; + } + } + + // + // UserInfo Table + 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; + } + } + + // + // Transactions Table + if (!tableList.ContainsKey(Table_of_Transactions)) { + try { + CreateTransactionsTable(); + } + catch (Exception e) { + throw new Exception("[MONEY MANAGER]: Error creating transactions table: " + e.ToString()); + } + } + // check transactions table version + 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; + } + } + + // + // TotalSales Table + 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; + } + } + } + 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 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; + } + + + + /////////////////////////////////////////////////////////////////////// + // create Tables + + 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(); + } + + + 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(); + } + + + 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(); + } + + + 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(); + } + + + + /////////////////////////////////////////////////////////////////////// + // update Balances Table + + 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=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 uuid, url, name, pass + bool updatedb = true; + for (int i=0; i + /// 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() + { + //m_log.Info("[MONEY MANAGER]: Converting Transaction Table..."); + 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 + /// 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() + { + //m_log.Info("[MONEY MANAGER]: Converting Transaction Table..."); + 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 + /// 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 + + 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(); + } + + + 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(); + } + + + /////////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////////// + // + + 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; + } + } + + + /// + /// Reconnect to the database + /// + 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()); + } + } + } + + + + /////////////////////////////////////////////////////////////////////// + // + // balances + // + + /// + /// 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"]); + } + } +#pragma warning disable CS0168 // The variable 'e' is declared but never +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception e) { + m_log.ErrorFormat("[MoneyDB]: MySql failed to fetch balance {0}.", userID); + retValue = -2; + } +#pragma warning restore CA1031 // Do not catch general exception types +#pragma warning restore CS0168 // The variable 'e' is declared but never + + dbReader.Close(); + } + cmd.Dispose(); + + return retValue; + } + + + 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; + } + + + 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; + } + + + + /////////////////////////////////////////////////////////////////////// + // + // totalsales + // + 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 0) bRet = true; + + cmd.Dispose(); + return bRet; + } + + + 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; + + } + + + 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 0) bRet = true; + + cmd.Dispose(); + return bRet; + } + + + 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; + } + + + 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; + } + + + 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; + } + + + 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(); + } + + + 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 + // + public bool addUserInfo(UserInfo userInfo) + { + //m_log.Error("[MONEY MANAGER]: Adding UserInfo: " + userInfo.UserID); + + 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; + } + + + public UserInfo fetchUserInfo(string userID) + { + //m_log.Error("[MONEY MANAGER]: Fetching UserInfo: " + 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; + } + + + public bool updateUserInfo(UserInfo userInfo) + { + //m_log.Error("[MONEY MANAGER]: Updating UserInfo: " + userInfo.UserID); + + 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; + } + + } +} diff --git a/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs b/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs new file mode 100644 index 0000000..26974c8 --- /dev/null +++ b/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs @@ -0,0 +1,53 @@ +/* + * 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 OpenSimulator 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. + */ + +using System; +using System.Threading; + +namespace OpenSim.Data.MySQL.MySQLMoneyDataWrapper +{ + // This bit of code is from OpenSim.Data.MySQLSuperManager + public class MySQLSuperManager + { + public bool Locked; + private readonly Mutex m_lock = new Mutex(false); + public MySQLMoneyManager Manager; + public string Running; + + public void GetLock() + { + Locked = true; + m_lock.WaitOne(); + } + + public void Release() + { + m_lock.ReleaseMutex(); + Locked = false; + } + } +} diff --git a/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/Properties/AssemblyInfo.cs b/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f4a2a82 --- /dev/null +++ b/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +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 +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs b/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs new file mode 100644 index 0000000..abb0fd1 --- /dev/null +++ b/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs @@ -0,0 +1,237 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +using System.Text; +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_objectID = "00000000-0000-0000-0000-000000000000"; + 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; + +/* + public TransactionData(string uuid, string sender, string receiver, + int amount, int time, int status, string description) + { + this.m_uuid = uuid; + this.m_sender = sender; + this.m_receiver = receiver; + this.m_amount = amount; + } +*/ + + public UUID TransUUID + { + get { return m_uuid; } + set { m_uuid = value; } + } + + public string Sender + { + get { return m_sender; } + set { m_sender = value; } + } + + public string Receiver + { + get { return m_receiver; } + set { m_receiver = value; } + } + + public int Amount + { + get { return m_amount; } + set { m_amount = value; } + } + + public int SenderBalance + { + get { return m_senderBalance; } + set { m_senderBalance = value; } + } + + public int ReceiverBalance + { + get { return m_receiverBalance; } + set { m_receiverBalance = value; } + } + + public int Type + { + get { return m_type; } + set { m_type = value; } + } + + public int Time + { + get { return m_time; } + set { m_time = value; } + } + + public int Status + { + get { return m_status; } + set { m_status = value; } + } + + public string Description + { + get { return m_description; } + set { m_description = value; } + } + + public string ObjectUUID + { + get { return m_objectID; } + set { m_objectID = value; } + } + + public string ObjectName + { + get { return m_objectName; } + set { m_objectName = value; } + } + + public string RegionHandle + { + get { return m_regionHandle; } + set { m_regionHandle = value; } + } + + public string RegionUUID + { + get { return m_regionUUID; } + set { m_regionUUID = value; } + } + + public string SecureCode + { + get { return m_secureCode; } + set { m_secureCode = value; } + } + + 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; + + public string UserID + { + get { return m_userID; } + set { m_userID = value; } + } + + public string SimIP + { + get { return m_simIP; } + set { m_simIP = value; } + } + + public string Avatar + { + get { return m_avatarName; } + set { m_avatarName = value; } + } + + public string PswHash + { + get { return m_passwordHash; } + set { m_passwordHash = value; } + } + + public int Type + { + get { return m_avatarType; } + set { m_avatarType = value; } + } + + public int Class + { + get { return m_avatarClass; } + set { m_avatarClass = value; } + } + + public string ServerURL + { + get { return m_serverURL; } + set { m_serverURL = value; } + } + } +} diff --git a/OpenSim.Grid.MoneyServer/IMoneyDBService.cs b/OpenSim.Grid.MoneyServer/IMoneyDBService.cs new file mode 100644 index 0000000..2043719 --- /dev/null +++ b/OpenSim.Grid.MoneyServer/IMoneyDBService.cs @@ -0,0 +1,77 @@ +/* + * 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. + */ + +#pragma warning disable IDE1006 + +#pragma warning disable S1128 // Unused "using" should be removed +using System; +using System.Collections.Generic; +using System.Text; +using OpenMetaverse; +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; +#pragma warning restore S1128 // Unused "using" should be removed + + +namespace OpenSim.Grid.MoneyServer +{ + /// + /// IMoney DB Service + /// + public interface IMoneyDBService + { + int getBalance(string userID); + + bool withdrawMoney(UUID transactionID, string senderID, int amount); + + bool giveMoney(UUID transactionID, string receiverID, int amount); + + bool addTransaction(TransactionData transaction); + + bool addUser(string userID, int balance, int status, int type); + + bool updateTransactionStatus(UUID transactionID, int status, string description); + + bool SetTransExpired(int deadTime); + + bool ValidateTransfer(string secureCode, UUID transactionID); + + TransactionData FetchTransaction(UUID transactionID); + + TransactionData FetchTransaction(string userID, int startTime, int endTime, int lastIndex); + + int getTransactionNum(string userID, int startTime, int endTime); + + bool DoTransfer(UUID transactionUUID); + + bool DoAddMoney(UUID transactionUUID); // Added by Fumi.Iseki + + bool TryAddUserInfo(UserInfo user); + + UserInfo FetchUserInfo(string userID); + + } +} diff --git a/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs b/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs new file mode 100644 index 0000000..7f84903 --- /dev/null +++ b/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs @@ -0,0 +1,52 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +//using System.Linq; +using System.Text; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; + +using Nini.Config; + + +namespace OpenSim.Grid.MoneyServer +{ + public interface IMoneyServiceCore + { + BaseHttpServer GetHttpServer(); + Dictionary GetSessionDic(); + Dictionary GetSecureSessionDic(); + Dictionary GetWebSessionDic(); + + // + IConfig GetServerConfig(); + IConfig GetCertConfig(); + bool IsCheckClientCert(); + } +} diff --git a/OpenSim.Grid.MoneyServer/MoneyDBService.cs b/OpenSim.Grid.MoneyServer/MoneyDBService.cs new file mode 100644 index 0000000..103647e --- /dev/null +++ b/OpenSim.Grid.MoneyServer/MoneyDBService.cs @@ -0,0 +1,584 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; +using OpenSim.Modules.Currency; +using log4net; +using System.Reflection; +using OpenMetaverse; + + +namespace OpenSim.Grid.MoneyServer +{ + class MoneyDBService: IMoneyDBService + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private string m_connect; + //private MySQLMoneyManager m_moneyManager; + 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 void Initialise(string connectionString, int maxDBConnections) + { + m_connect = connectionString; + m_maxConnections = maxDBConnections; + if (connectionString != string.Empty) { + //m_moneyManager = new MySQLMoneyManager(connectionString); + + //m_log.Info("Creating " + m_maxConnections + " DB connections..."); + for (int i=0; im_maxConnections) { + lockedCons = 0; + System.Threading.Thread.Sleep(1000); // Wait some time before searching them again. + m_log.Debug("WARNING: All threads are in use. Probable cause: Something didnt release a mutex properly, or high volume of requests inbound."); + } + } + } + + + public int getBalance(string userID) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.getBalance(userID); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + return dbm.Manager.getBalance(userID); + } + catch(Exception e) { + m_log.Error(e.ToString()); + return 0; + } + finally { + dbm.Release(); + } + } + + + 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) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + return dbm.Manager.withdrawMoney(transactionID, senderID, amount); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool giveMoney(UUID transactionID, string receiverID, int amount) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.giveMoney(transactionID, receiverID, amount); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + return dbm.Manager.giveMoney(transactionID, receiverID, amount); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool setTotalSale(TransactionData transaction) + { + if (transaction.Receiver==transaction.Sender) return false; + if (transaction.Sender==UUID.Zero.ToString()) return false; + + MySQLSuperManager dbm = GetLockedConnection(); + + 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) { + m_log.Info(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.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool addTransaction(TransactionData transaction) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.addTransaction(transaction); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + return dbm.Manager.addTransaction(transaction); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool addUser(string userID, int balance, int status, int type) + { + 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) return false; + + // + MySQLSuperManager dbm = GetLockedConnection(); + + try { + ret = dbm.Manager.addUser(userID, 0, status, type); // make Balance Table + } + catch (MySql.Data.MySqlClient.MySqlException e) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + ret = dbm.Manager.addUser(userID, 0, status, type); // make Balance Table + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + + // + if (ret) ret = giveMoney(transaction.TransUUID, userID, balance); + return ret; + } + + + 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) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + return dbm.Manager.updateTransactionStatus(transactionID, status, description); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool SetTransExpired(int deadTime) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.SetTransExpired(deadTime); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + return dbm.Manager.SetTransExpired(deadTime); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool ValidateTransfer(string secureCode, UUID transactionID) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.ValidateTransfer(secureCode, transactionID); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + return dbm.Manager.ValidateTransfer(secureCode, transactionID); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public TransactionData FetchTransaction(UUID transactionID) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.FetchTransaction(transactionID); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + return dbm.Manager.FetchTransaction(transactionID); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return null; + } + finally { + dbm.Release(); + } + } + + + 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) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + arrTransaction = dbm.Manager.FetchTransaction(userID, startTime, endTime, index, 1); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return null; + } + finally { + dbm.Release(); + } + + // + if (arrTransaction.Length > 0) { + return arrTransaction[0]; + } + else { + return null; + } + } + + + public bool DoTransfer(UUID transactionUUID) + { + 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 DB. {0}", transaction.Receiver); + 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); + } + + return do_trans; + } + + + // by Fumi.Iseki + public bool DoAddMoney(UUID transactionUUID) + { + 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 DB. {0}", transaction.Receiver); + return false; + } + // + if (giveMoney(transactionUUID, transaction.Receiver, transaction.Amount)) { + setTotalSale(transaction); + 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()); + } + + return false; + } + + + /////////////////////////////////////////////////////////////////////////////////////////////////////// + // + // userinfo + // + + public bool TryAddUserInfo(UserInfo user) + { + MySQLSuperManager dbm = GetLockedConnection(); + + UserInfo userInfo = null; + + try { + userInfo = dbm.Manager.fetchUserInfo(user.UserID); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + userInfo = dbm.Manager.fetchUserInfo(user.UserID); + } + catch (Exception e) { + m_log.Error(e.ToString()); + 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(); + } + } + + + public UserInfo FetchUserInfo(string userID) + { + UserInfo userInfo = null; + MySQLSuperManager dbm = GetLockedConnection(); + + try { + userInfo = dbm.Manager.fetchUserInfo(userID); + return userInfo; + } + catch (MySql.Data.MySqlClient.MySqlException e) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + userInfo = dbm.Manager.fetchUserInfo(userID); + return userInfo; + } + catch (Exception e) { + m_log.Error(e.ToString()); + return null; + } + finally { + dbm.Release(); + } + } + + + 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) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + return dbm.Manager.getTransactionNum(userID,startTime,endTime); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return -1; + } + finally { + dbm.Release(); + } + } + } +} diff --git a/OpenSim.Grid.MoneyServer/MoneyServerBase.cs b/OpenSim.Grid.MoneyServer/MoneyServerBase.cs new file mode 100644 index 0000000..deddd76 --- /dev/null +++ b/OpenSim.Grid.MoneyServer/MoneyServerBase.cs @@ -0,0 +1,407 @@ +/* + * 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. + */ + +#pragma warning disable S1128 // Unused "using" should be removed +using System; +using System.IO; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using System.Net; +using System.Net.Security; +using System.Reflection; +using System.Timers; +using OpenSim.Framework.Servers.HttpServer; +using Nini.Config; +using log4net; +using OpenSim.Framework; +using OpenSim.Framework.Console; +using OpenSim.Framework.Servers; +using OpenSim.Data; +using NSL.Certificate.Tools; + +using System.Threading; +using System.Security.Cryptography.X509Certificates; +using log4net.Appender; +using log4net.Core; +using log4net.Repository; +using OpenMetaverse; +using Timer = System.Timers.Timer; +#pragma warning restore S1128 // Unused "using" should be removed + + +/// +/// OpenSim Grid MoneyServer +/// +namespace OpenSim.Grid.MoneyServer +{ + /// + /// class MoneyServerBase : BaseOpenSimServer, IMoneyServiceCore + /// Manni internal class + /// + internal class MoneyServerBase : BaseOpenSimServer, IMoneyServiceCore + { + 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 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; + +#pragma warning disable S1450 // Private fields only used as local variables in methods should become local variables + private MoneyXmlRpcModule m_moneyXmlRpcModule; +#pragma warning restore S1450 // Private fields only used as local variables in methods should become local variables + private MoneyDBService m_moneyDBService; + + private NSLCertificateVerify m_certVerify = new NSLCertificateVerify(); // Client Certificate + +#pragma warning disable S2933 // Fields that are only assigned in the constructor should be "readonly" + private Dictionary m_sessionDic = new Dictionary(); + private Dictionary m_secureSessionDic = new Dictionary(); + private Dictionary m_webSessionDic = new Dictionary(); +#pragma warning restore S2933 // Fields that are only assigned in the constructor should be "readonly" + + IConfig m_server_config; + IConfig m_cert_config; + + + /// + /// Money Server Base + /// + public MoneyServerBase() + { + m_console = new LocalConsole("MoneyServer "); + MainConsole.Instance = m_console; + } + + + /// + /// Work + /// + public void Work() + { + //The timer checks the transactions table every 60 seconds + Timer checkTimer = new Timer + { + Interval = 60 * 1000, + Enabled = true + }; + checkTimer.Elapsed += new ElapsedEventHandler(CheckTransaction); + checkTimer.Start(); + + while (true) { + m_console.Prompt(); + } + } + + + /// + /// Check the transactions table, set expired transaction state to failed + /// + private void CheckTransaction(object sender, ElapsedEventArgs e) + { + 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); + } + + + /// + /// 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"); + + /* + Type typeBaseHttpServer = typeof(BaseHttpServer); // BaseHttpServer.cs にパッチがあたっていない場合のため + PropertyInfo pinfo = typeBaseHttpServer.GetProperty("CertificateValidationCallback"); + + if (pinfo!=null) { + pinfo.SetValue(m_httpServer, (RemoteCertificateValidationCallback)m_certVerify.ValidateClientCertificate, null); + m_log.Info ("[MONEY SERVER]: Set RemoteCertificateValidationCallback"); + } + else { + m_log.Error("[MONEY SERVER]: StartupSpecific: CheckClientCert is true. But this MoneyServer does not support CheckClientCert!!"); + } + */ + } + } + 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); + } + } + + + /// + /// Read Ini Config + /// + protected 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); + + // + // [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); + } + } + } + + catch (Exception) + { + m_log.Error("[MONEY SERVER]: ReadIniConfig: Fail to setup configure. Please check MoneyServer.ini. Exit"); + 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; + } +#pragma warning disable S2486 // Generic exceptions should not be ignored +#pragma warning disable CA1031 // Do not catch general exception types +#pragma warning disable S108 // Nested blocks of code should not be left empty + catch (Exception) { } +#pragma warning restore S108 // Nested blocks of code should not be left empty +#pragma warning restore CA1031 // Do not catch general exception types +#pragma warning restore S2486 // Generic exceptions should not be ignored + } + + + /// + /// Setup Money Services + /// + 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); + + m_moneyXmlRpcModule = new MoneyXmlRpcModule(); + m_moneyXmlRpcModule.Initialise(m_version, m_moneyDBService, this); + m_moneyXmlRpcModule.PostInitialise(); + } + + + /// + /// Is Check Client Cert + /// + public bool IsCheckClientCert() + { + return m_checkClientCert; + } + + + /// + /// Get Server Config + /// + public IConfig GetServerConfig() + { + return m_server_config; + } + + + /// + /// Get Cert Config + /// + public IConfig GetCertConfig() + { + return m_cert_config; + } + + + /// + /// Get Http Server + /// + public BaseHttpServer GetHttpServer() + { + return m_httpServer; + } + + + /// + /// Get Session Dic + /// + public Dictionary GetSessionDic() + { + return m_sessionDic; + } + + /// + /// Get Secure Session Dic + /// + public Dictionary GetSecureSessionDic() + { + return m_secureSessionDic; + } + + + /// + /// Get Web Session Dic + /// + public Dictionary GetWebSessionDic() + { + return m_webSessionDic; + } + + } + + + + /// + /// class Money Server Config Source + /// + class MoneyServerConfigSource + { + /// + /// Ini Config Source + /// + public IniConfigSource m_config; + + /// + /// Money Server Config Source + /// + public MoneyServerConfigSource() + { + string configPath = Path.Combine(Directory.GetCurrentDirectory(), "MoneyServer.ini"); + if (File.Exists(configPath)) + { + m_config = new IniConfigSource(configPath); + } +#pragma warning disable S108 // Nested blocks of code should not be left empty + else { } +#pragma warning restore S108 // Nested blocks of code should not be left empty + } + + + /// + /// Save config + /// + public void Save(string path) + { + m_config.Save(path); + } + + } +} diff --git a/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs b/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs new file mode 100644 index 0000000..bb957cb --- /dev/null +++ b/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs @@ -0,0 +1,1807 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Reflection; +using System.Collections; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +using log4net; +using Nini.Config; +using Nwc.XmlRpc; + +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; +using OpenSim.Modules.Currency; +using OpenSim.Region.Framework.Scenes; + +using NSL.Network.XmlRpc; +using NSL.Certificate.Tools; + + +namespace OpenSim.Grid.MoneyServer +{ + class MoneyXmlRpcModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private int m_defaultBalance = 1000; + // + private bool m_forceTransfer = false; + private string m_bankerAvatar = ""; + + private bool m_scriptSendMoney = false; + private string m_scriptAccessKey = ""; + private string m_scriptIPaddress = "127.0.0.1"; + + 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 bool m_checkServerCert = false; + private string m_cacertFilename = ""; + + private string m_certFilename = ""; + private string m_certPassword = ""; + //private X509Certificate2 m_clientCert = null; + + private string m_sslCommonName = ""; + + /// + /// For server authentication + /// + private NSLCertificateVerify m_certVerify = new NSLCertificateVerify(); + + /// + /// Update Balance Messages + /// + 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; + 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; + + + public MoneyXmlRpcModule() + { + } + + + public void Initialise(string opensimVersion, IMoneyDBService moneyDBService, IMoneyServiceCore moneyCore) + { + //m_opensimVersion = opensimVersion; + m_moneyDBService = moneyDBService; + m_moneyCore = moneyCore; + m_server_config = m_moneyCore.GetServerConfig(); // [MoneyServer] Section + m_cert_config = m_moneyCore.GetCertConfig(); // [Certificate] Section + + //////////////////////////////////////////////////////////////////////// + // [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); + + // 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_clientCert = new X509Certificate2(m_certFilename, m_certPassword); + //m_clientCert = new X509Certificate2(m_certFilename, m_certPassword, X509KeyStorageFlags.MachineKeySet); + m_log.Info("[MONEY XMLRPC]: Initialise: Issue Authentication of Client. Cert file is " + m_certFilename); + } + + // Server Authentication + // CA : MoneyServer が XMLRPC のWebサーバのサーバ証明書をチェックするための設定 + 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]: Initialise: No check XMLRPC Server or CACertFilename is empty. CheckServerCert is false."); + } + + //ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(m_certVerify.ValidateServerCertificate); + //ServicePointManager.UseNagleAlgorithm = false; + //ServicePointManager.Expect100Continue = false; + + m_sessionDic = m_moneyCore.GetSessionDic(); + m_secureSessionDic = m_moneyCore.GetSecureSessionDic(); + m_webSessionDic = m_moneyCore.GetWebSessionDic(); + RegisterHandlers(); + } + + + public void PostInitialise() + { + } + + + 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); // added + m_httpServer.AddXmlRPCHandler("MoveMoney", handleScriptTransaction); // added + + // 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_func); + m_httpServer.AddXmlRPCHandler("buyLandPrep", landBuy_func); + + // Currency Buy Test + m_httpServer.AddXmlRPCHandler("getCurrencyQuote", quote_func); + m_httpServer.AddXmlRPCHandler("buyCurrency", buy_func); + } + + + private XmlRpcResponse buy_func(XmlRpcRequest request, IPEndPoint client) + { + m_log.InfoFormat("[MONEY XMLRPC]: handleClient buyCurrency."); + throw new NotImplementedException(); + } + + + private XmlRpcResponse quote_func(XmlRpcRequest request, IPEndPoint client) + { + m_log.InfoFormat("[MONEY XMLRPC]: handleClient getCurrencyQuote."); + throw new NotImplementedException(); + } + + + private XmlRpcResponse landBuy_func(XmlRpcRequest request, IPEndPoint client) + { + m_log.InfoFormat("[MONEY XMLRPC]: handleClient buyLandPrep."); + throw new NotImplementedException(); + } + + + private XmlRpcResponse preflightBuyLandPrep_func(XmlRpcRequest request, IPEndPoint client) + { + m_log.InfoFormat("[MONEY XMLRPC]: handleClient preflightBuyLandPrep."); + throw new NotImplementedException(); + } + + + // + 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; + } + + + // + public string GetSSLCommonName() + { + return m_sslCommonName; + } + + + /// + /// Get the user balance when user entering a parcel. + /// + /// + /// + 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 { + if (commonName=="SSLCN:unknownName") { + m_log.InfoFormat("[MONEY XMLRPC]: handleClientLogin: SSL Common Name is \"{0}\", but this name is ignored.", commonName); + } + 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); + + //if (String.IsNullOrEmpty(serverURL)) { + // responseData["description"] = "Server URL is empty. Avatar is a NPC?"; + // m_log.InfoFormat("[MONEY XMLRPC]: handleClientLogon: {0}", responseData["description"]); + // return response; + //} + + // + // 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) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleClientLogout:"); + + //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; + } + + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // + /// + /// handle incoming transaction + /// + /// + /// + 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; + } + + + // + // added by Fumi.Iseki + // + /// + /// handle incoming force transaction. no check senderSessionID and senderSecureSessionID + /// + /// + /// + public XmlRpcResponse handleForceTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleForceTransaction:"); + + 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; + } + + + // + // added by Fumi.Iseki + // + /// + /// handle scripted sending money transaction. + /// + /// + /// + 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; + } + + + // + // added by Fumi.Iseki + // + /// + /// handle adding money transaction. + /// + /// + /// + public XmlRpcResponse handleAddBankerMoney(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleAddBankerMoney:"); + + 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; + } + + + // + // added by Fumi.Iseki + // + /// + /// handle pay charge transaction. no check receiver information. + /// + /// + /// + public XmlRpcResponse handlePayMoneyCharge(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handlePayMoneyCharge:"); + + 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(); + + 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); + m_log.InfoFormat("[MONEY XMLRPC]: handlePayMoneyCharge: 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]: handlePayMoneyCharge: Pay from {0}", senderID); + 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(); + + 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 message = string.Format(m_BalanceMessagePayCharge, amount, "SYSTEM", ""); + responseData["success"] = NotifyTransfer(transactionUUID, message, "", ""); + } + 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]: handlePayMoneyCharge: Pay money transaction for user {0} failed.", senderID); + } + return response; + } + catch (Exception e) { + m_log.Error("[MONEY XMLRPC]: handlePayMoneyCharge: Exception occurred while pay money transaction: " + e.ToString()); + } + return response; + } + } + + m_log.Error("[MONEY XMLRPC]: handlePayMoneyCharge: Session authentication failure for sender " + senderID); + responseData["message"] = "Session check failure, please re-login later!"; + return response; + } + + + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // + // added by Fumi.Iseki + // + /// + /// Continue transaction with no confirm. + /// + /// + /// + 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) return true; + 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; + } + + + /// + /// Get the user balance. + /// + /// + /// + public XmlRpcResponse handleGetBalance(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleGetBalance:"); + + 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; + } + + + /// + /// 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, 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_clientCert, m_checkServerCert, MONEYMODULE_REQUEST_TIMEOUT); + 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; + } + + + /// + /// Update the client balance.We don't care about the result. + /// + /// + private void UpdateBalance(string userID, string message) + { + //m_log.InfoFormat("[MONEY XMLRPC]: UpdateBalance: ID = {0}, Message = {1}", userID, message); + + string sessionID = string.Empty; + string secureID = string.Empty; + + if (m_sessionDic.ContainsKey(userID) && m_secureSessionDic.ContainsKey(userID)) { + sessionID = m_sessionDic[userID]; + secureID = m_secureSessionDic[userID]; + + Hashtable requestTable = new Hashtable(); + requestTable["clientUUID"] = userID; + requestTable["clientSessionID"] = sessionID; + requestTable["clientSecureSessionID"] = secureID; + requestTable["Balance"] = m_moneyDBService.getBalance(userID); + 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: Sended UpdateBalance Request to {0}", user.SimIP.ToString()); + } + } + } + + + /// + /// RollBack the transaction if user failed to get the object paid + /// + /// + /// + protected bool RollBackTransaction(TransactionData transaction) + { + //m_log.InfoFormat("[MONEY XMLRPC]: RollBackTransaction:"); + + 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; + } + + + // + public XmlRpcResponse handleCancelTransfer(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleCancelTransfer:"); + + 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) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleGetTransaction:"); + + 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; + } + + + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + // In development + // + + public XmlRpcResponse handleWebLogin(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleWebLogin: Start."); + + 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) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleWebLogout:"); + + 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; + } + + + /// + /// Get balance method for web pages. + /// + /// + /// + public XmlRpcResponse handleWebGetBalance(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleWebGetBalance:"); + + 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; + } + + + /// + /// Get transaction for web pages + /// + /// + /// + public XmlRpcResponse handleWebGetTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleWebGetTransaction:"); + + 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; + } + + + /// + /// Get total number of transactions for web pages. + /// + /// + /// + public XmlRpcResponse handleWebGetTransactionNum(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleWebGetTransactionNum:"); + + 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; + } + } + +} + diff --git a/OpenSim.Grid.MoneyServer/Program.cs b/OpenSim.Grid.MoneyServer/Program.cs new file mode 100644 index 0000000..3fbd12d --- /dev/null +++ b/OpenSim.Grid.MoneyServer/Program.cs @@ -0,0 +1,42 @@ +/* + * 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. + */ + +using log4net.Config; + +namespace OpenSim.Grid.MoneyServer +{ + class Program + { + public static void Main(string[] args) + { + XmlConfigurator.Configure(); + MoneyServerBase app = new MoneyServerBase(); + app.Startup(); + app.Work(); + } + } +} diff --git a/OpenSim.Grid.MoneyServer/Properties/AssemblyInfo.cs b/OpenSim.Grid.MoneyServer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..407ecee --- /dev/null +++ b/OpenSim.Grid.MoneyServer/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +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.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 +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs b/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs new file mode 100644 index 0000000..9bfca89 --- /dev/null +++ b/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs @@ -0,0 +1,1920 @@ +// * 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. +// */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Reflection; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +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; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; +using NSL.Certificate.Tools; +using NSL.Network.XmlRpc; + + + +[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 + } + + +/* + // Refer to OpenMetaverse + public enum OpenMetaverse.MoneyTransactionType : int + { + None = 0, + FailSimulatorTimeout = 1, + FailDataserverTimeout = 2, + ObjectClaim = 1000, + LandClaim = 1001, + GroupCreate = 1002, + ObjectPublicClaim = 1003, + GroupJoin = 1004, + TeleportCharge = 1100, + UploadCharge = 1101, + LandAuction = 1102, + ClassifiedCharge = 1103, + ObjectTax = 2000, + LandTax = 2001, + LightTax = 2002, + ParcelDirFee = 2003, + GroupTax = 2004, + ClassifiedRenew = 2005, + GiveInventory = 3000, + ObjectSale = 5000, + Gift = 5001, + LandSale = 5002, + ReferBonus = 5003, + InventorySale = 5004, + RefundPurchase = 5005, + LandPassSale = 5006, + DwellBonus = 5007, + PayObject = 5008, + ObjectPays = 5009, + GroupLandDeed = 6001, + GroupObjectDeed = 6002, + GroupLiability = 6003, + GroupDividend = 6004, + GroupMembershipDues = 6005, + ObjectRelease = 8000, + LandRelease = 8001, + ObjectDelete = 8002, + ObjectPublicDecay = 8003, + ObjectPublicDelete = 8004, + LindenAdjustment = 9000, + LindenGrant = 9001, + LindenPenalty = 9002, + EventFee = 9003, + EventPrize = 9004, + StipendBasic = 10000, + StipendDeveloper = 10001, + StipendAlways = 10002, + StipendDaily = 10003, + StipendRating = 10004, + StipendDelta = 10005 + } +*/ + + + + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "DTLNSLMoneyModule")] + public class DTLNSLMoneyModule : IMoneyModule, ISharedRegionModule + { + #region Constant numbers and members. + + // 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 X509Certificate2 m_cert = null; + + 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 + + + /// + /// Scene dictionary indexed by Region Handle + /// + private Dictionary m_sceneList = new Dictionary(); + + /// + /// To cache the balance data while the money server is not available. + /// + 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; + + #endregion + + + /// + /// Initialise + /// + /// + /// + public void Initialise(Scene scene, IConfigSource source) + { + Initialise(source); + if (string.IsNullOrEmpty(m_moneyServURL)) m_enable_server = false; + // + AddRegion(scene); + } + + + #region ISharedRegionModule interface + + public void Initialise(IConfigSource source) + { + //m_log.InfoFormat("[MONEY MODULE]: Initialise:"); + + // Handle the parameters errors. + if (source==null) return; + + try { + m_config = source; + + // [Economy] section + IConfig economyConfig = m_config.Configs["Economy"]; + + if (economyConfig.GetString("EconomyModule")!=Name && economyConfig.GetString("economymodule")!=Name) { + //m_enabled = false; + m_log.InfoFormat("[MONEY MODULE]: Initialise: The DTL/NSL MoneyModule is disabled"); + return; + } + else { + m_log.InfoFormat("[MONEY MODULE]: Initialise: The DTL/NSL MoneyModule is enabled"); + } + + m_sellEnabled = economyConfig.GetBoolean("SellEnabled", m_sellEnabled); + m_moneyServURL = economyConfig.GetString("CurrencyServer", m_moneyServURL); + + // Client Certification // クライアント証明書 + m_certFilename = economyConfig.GetString("ClientCertFilename", m_certFilename); + m_certPassword = economyConfig.GetString("ClientCertPassword", m_certPassword); + if (m_certFilename!="") { + m_certVerify.SetPrivateCert(m_certFilename, m_certPassword); + //m_cert = new X509Certificate2(m_certFilename, m_certPassword); + //m_cert = new X509Certificate2(m_certFilename, m_certPassword, X509KeyStorageFlags.MachineKeySet); + m_log.Info("[MONEY MODULE]: Initialise: Issue Authentication of Client. Cert File is " + m_certFilename); + } + + // Server Authentication // MoneyServer のサーバ証明書のチェック + m_checkServerCert = economyConfig.GetBoolean("CheckServerCert", m_checkServerCert); + m_cacertFilename = economyConfig.GetString ("CACertFilename", m_cacertFilename); + + if (m_cacertFilename != "") { + m_certVerify.SetPrivateCA(m_cacertFilename); + } + else { + m_checkServerCert = false; + } + + if (m_checkServerCert) { + m_log.Info("[MONEY MODULE]: Initialise: Execute Authentication of Server. CA Cert File is " + m_cacertFilename); + } + else { + m_log.Info("[MONEY MODULE]: Initialise: No check Money Server or CACertFilename is empty. CheckServerCert is false."); + } + + //ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(m_certVerify.ValidateServerCertificate); + //ServicePointManager.UseNagleAlgorithm = false; + //ServicePointManager.Expect100Continue = false; + + // Settlement + m_use_web_settle = economyConfig.GetBoolean("SettlementByWeb", m_use_web_settle); + m_settle_url = economyConfig.GetString ("SettlementURL", m_settle_url); + m_settle_message = economyConfig.GetString ("SettlementMessage", m_settle_message); + + // Price + 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); + PriceGroupCreate = economyConfig.GetInt ("PriceGroupCreate", PriceGroupCreate); + TeleportMinPrice = economyConfig.GetInt ("TeleportMinPrice", TeleportMinPrice); + TeleportPriceExponent = economyConfig.GetFloat("TeleportPriceExponent", TeleportPriceExponent); + EnergyEfficiency = economyConfig.GetFloat("EnergyEfficiency", EnergyEfficiency); + + // for HG Avatar + string avatar_class = economyConfig.GetString("HGAvatarAs", "HGAvatar").ToLower(); + if (avatar_class=="localavatar") m_hg_avatarClass = (int)AvatarType.LOCAL_AVATAR; + else if (avatar_class=="guestavatar") m_hg_avatarClass = (int)AvatarType.GUEST_AVATAR; + else if (avatar_class=="hgavatar") m_hg_avatarClass = (int)AvatarType.HG_AVATAR; + else if (avatar_class=="foreignavatar") m_hg_avatarClass = (int)AvatarType.FOREIGN_AVATAR; + else m_hg_avatarClass = (int)AvatarType.UNKNOWN_AVATAR; + + } + catch { + m_log.ErrorFormat("[MONEY MODULE]: Initialise: Faile to read configuration file"); + } + } + + + public void AddRegion(Scene scene) + { + //m_log.InfoFormat("[MONEY MODULE]: AddRegion:"); + + if (scene==null) return; + + scene.RegisterModuleInterface(this); // 競合するモジュールの排除 + + 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); // added + HttpServer.AddXmlRPCHandler("AddBankerMoney", AddBankerMoneyHandler); // added + HttpServer.AddXmlRPCHandler("SendMoney", SendMoneyHandler); // added + HttpServer.AddXmlRPCHandler("MoveMoney", MoveMoneyHandler); // added + + MainServer.Instance.AddXmlRPCHandler("OnMoneyTransfered", OnMoneyTransferedHandler); + MainServer.Instance.AddXmlRPCHandler("UpdateBalance", BalanceUpdateHandler); + MainServer.Instance.AddXmlRPCHandler("UserAlert", UserAlertHandler); + MainServer.Instance.AddXmlRPCHandler("GetBalance", GetBalanceHandler); // added + MainServer.Instance.AddXmlRPCHandler("AddBankerMoney", AddBankerMoneyHandler); // added + MainServer.Instance.AddXmlRPCHandler("SendMoney", SendMoneyHandler); // added + MainServer.Instance.AddXmlRPCHandler("MoveMoney", MoveMoneyHandler); // added + } + } + + 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; + } + + + 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; + } + } + + + public void RegionLoaded(Scene scene) + { + //m_log.InfoFormat("[MONEY MODULE]: RegionLoaded:"); + } + + + public Type ReplaceableInterface + { + //get { return typeof(IMoneyModule); } + get { return null; } + } + + + public bool IsSharedModule + { + get { return true; } + } + + + public string Name + { + get { return "DTLNSLMoneyModule"; } + } + + + public void PostInitialise() + { + //m_log.InfoFormat("[MONEY MODULE]: PostInitialise:"); + } + + + public void Close() + { + //m_log.InfoFormat("[MONEY MODULE]: Close:"); + } + + #endregion + + + #region IMoneyModule interface. + + // for LSL llGiveMoney() function + public bool ObjectGiveMoney(UUID objectID, UUID fromID, UUID toID, int amount, UUID txn, out string result) + { + //m_log.InfoFormat("[MONEY MODULE]: ObjectGiveMoney: LSL ObjectGiveMoney. UUID = {0}", objectID.ToString()); + + 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"; + return ret; + } + + + // + public int UploadCharge + { + get { return PriceUpload; } + } + + + // + public int GroupCreationCharge + { + get { return PriceGroupCreate; } + } + + + public int GetBalance(UUID agentID) + { + IClientAPI client = GetLocateClient(agentID); + return QueryBalanceFromMoneyServer(client); + } + + + 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; + } + return false; + } + + + 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; + } + return false; + } + + + 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); + } + + + public void ApplyCharge(UUID agentID, int amount, MoneyTransactionType type) + { + ApplyCharge(agentID, amount, type, string.Empty); + } + + + 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); + } + + + 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); + } + + + 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 + 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); + } + + // for 0.9.1 over + 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); + return ret; + } + + #endregion + + + #region MoneyModule event handlers + + // + private void OnNewClient(IClientAPI client) + { + //m_log.InfoFormat("[MONEY MODULE]: OnNewClient"); + + client.OnEconomyDataRequest += OnEconomyDataRequest; + client.OnLogout += ClientClosed; + + client.OnMoneyBalanceRequest += OnMoneyBalanceRequest; + client.OnRequestPayPrice += OnRequestPayPrice; + client.OnObjectBuy += OnObjectBuy; + } + + + public void OnMakeRootAgent(ScenePresence agent) + { + //m_log.InfoFormat("[MONEY MODULE]: OnMakeRootAgent:"); + + 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); + + //client.OnMoneyBalanceRequest += OnMoneyBalanceRequest; + //client.OnRequestPayPrice += OnRequestPayPrice; + //client.OnObjectBuy += OnObjectBuy; + } + + + // for OnClientClosed event + private void ClientClosed(IClientAPI client) + { + //m_log.InfoFormat("[MONEY MODULE]: ClientClosed:"); + + if (m_enable_server && client!=null) { + LogoffMoneyServer(client); + } + } + + + // for OnMakeChildAgent event + private void MakeChildAgent(ScenePresence avatar) + { + //m_log.InfoFormat("[MONEY MODULE]: MakeChildAgent:"); + } + + + // for OnMoneyTransfer event + private void MoneyTransferAction(Object sender, EventManager.MoneyTransferArgs moneyEvent) + { + //m_log.InfoFormat("[MONEY MODULE]: MoneyTransferAction: type = {0}", moneyEvent.transactiontype); + + 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 + private void ValidateLandBuy(Object sender, EventManager.LandBuyArgs landBuyEvent) + { + //m_log.InfoFormat("[MONEY MODULE]: ValidateLandBuy:"); + + 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 + private void processLandBuy(Object sender, EventManager.LandBuyArgs landBuyEvent) + { + //m_log.InfoFormat("[MONEY MODULE]: processLandBuy:"); + + 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 + 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(); + 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); + 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"); + } + } + + + 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) + private void OnEconomyDataRequest(IClientAPI user) + { + //m_log.InfoFormat("[MONEY MODULE]: OnEconomyDataRequest:"); + //IClientAPI user = GetLocateClient(agentId); + + 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); + } + } + } + + #endregion + + + #region MoneyModule XML-RPC Handler + + // "OnMoneyTransfered" RPC from MoneyServer + 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 + public XmlRpcResponse BalanceUpdateHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY MODULE]: BalanceUpdateHandler:"); + + bool ret = false; + + #region Update the balance from money server. + + 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; + } + } + } + } + } + + #endregion + + // 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 + public XmlRpcResponse UserAlertHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY MODULE]: UserAlertHandler:"); + + bool ret = false; + + #region confirm the request and show the notice from money server. + + 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; + } + } + } + } + } + // + #endregion + + // 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 + public XmlRpcResponse GetBalanceHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY MODULE]: GetBalanceHandler:"); + + 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 + 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 + public XmlRpcResponse SendMoneyHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY MODULE]: SendMoneyHandler:"); + + 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 + public XmlRpcResponse MoveMoneyHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY MODULE]: MoveMoneyHandler:"); + + 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; + } + + #endregion + + + #region MoneyModule private help functions + + /// + /// 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) + { + //m_log.InfoFormat("[MONEY MODULE]: TransferMoney:"); + + 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) + /// 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) + { + //m_log.InfoFormat("[MONEY MODULE]: ForceTransferMoney:"); + + bool ret = false; + + #region Force send transaction request to money server and parse the resultes. + + 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()); + } + //else m_log.ErrorFormat("[MONEY MODULE]: ForceTransferMoney: Money Server is not available!!"); + + #endregion + + 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) + { + //m_log.InfoFormat("[MONEY MODULE]: SendMoneyTo:"); + + 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"); + } + //else m_log.ErrorFormat("[MONEY MODULE]: SendMoneyTo: Money Server is not available!!"); + + 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) + { + //m_log.InfoFormat("[MONEY MODULE]: MoveMoneyFromTo:"); + + 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"); + } + //else m_log.ErrorFormat("[MONEY MODULE]: MoveMoneyFromTo: Money Server is not available!!"); + + 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) + { + //m_log.InfoFormat("[MONEY MODULE]: AddBankerMoney:"); + + 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"); + } + //else m_log.ErrorFormat("[MONEY MODULE]: AddBankerMoney: Money Server is not available!!"); + + 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) + { + //m_log.InfoFormat("[MONEY MODULE]: PayMoneyCharge:"); + + 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) + /// 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) + { + //m_log.InfoFormat("[MONEY MODULE]: LoginMoneyServer:"); + + balance = 0; + bool ret = false; + bool isNpc = avatar.IsNPC; + + IClientAPI client = avatar.ControllingClient; + + #region Send money server the client info for login. + + 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; + } + } + } + + ////////////////////////////////////////////////////////////// + // 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; + } + + // + 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!! Server URL is Null or empty!!"); + + #endregion + + // Viewerへ設定を通知する. + 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) + { + //m_log.InfoFormat("[MONEY MODULE]: LogoffMoneyServer:"); + + 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; + } + } + } + + return ret; + } + + + // + private EventManager.MoneyTransferArgs GetTransactionInfo(IClientAPI client, string transactionID) + { + //m_log.InfoFormat("[MONEY MODULE]: GetTransactionInfo:"); + + 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) + { + //m_log.InfoFormat("[MONEY MODULE]: genericCurrencyXMLRPCRequest:"); + + 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; + } + } + } + } + } + + return client; + } + + + 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; + } + } + } + } + + return scene; + } + + + 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; + } + + #endregion + } + +} diff --git a/OpenSim.Modules.Currency/NSLCertificateTools.cs b/OpenSim.Modules.Currency/NSLCertificateTools.cs new file mode 100644 index 0000000..b0c3034 --- /dev/null +++ b/OpenSim.Modules.Currency/NSLCertificateTools.cs @@ -0,0 +1,291 @@ +/* + * Copyright (c) Contributors, http://www.nsl.tuis.ac.jp + * + */ + +#pragma warning disable S1128 // Unused "using" should be removed +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using System.Net; +using System.Net.Security; +using System.Text; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using log4net; +#pragma warning restore S1128 // Unused "using" should be removed + + +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; + } + + + /// + /// NSL Certificate Verify + /// + /// + public NSLCertificateVerify(string certfile) + { + SetPrivateCA(certfile); + } + + + /// + /// NSL Certificate Verify + /// + /// + /// + public NSLCertificateVerify(string certfile, string crlfile) + { + SetPrivateCA (certfile); + SetPrivateCRL(crlfile); + } + + + /// + /// 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; + } + } + + + // + 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 + /// 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); + + /* + // RemoteCertificateNotAvailableはエラーとする. + if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateNotAvailable)==SslPolicyErrors.RemoteCertificateNotAvailable) { + m_log.InfoFormat("[NSL SERVER CERT VERIFY]: ValidateServerCertificate: Policy Error! {0}", sslPolicyErrors); + return false; + } + */ + + // None, ChainErrors Error except for. // None, ChainErrors 以外は全てエラーとする. + 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 以外は全てエラーとする. + 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; + } + } + + +/* + /// + /// class NSL Certificate Policy + /// + public class NSLCertificatePolicy : ICertificatePolicy + { + /// + /// Check Validation Result + /// + /// + /// + /// + /// + /// + public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) + { + if (certificateProblem == 0 || // normal // 正常 + certificateProblem == -2146762487 || // Not trusted // 信頼されてない + certificateProblem == -2146762495 || // Expired // 期限切れ + certificateProblem == -2146762481) { // Incorrect name // 名前不正 + return true; + } + else { + return false; + } + } + } +*/ +} diff --git a/OpenSim.Modules.Currency/NSLXmlRpc.cs b/OpenSim.Modules.Currency/NSLXmlRpc.cs new file mode 100644 index 0000000..025236a --- /dev/null +++ b/OpenSim.Modules.Currency/NSLXmlRpc.cs @@ -0,0 +1,115 @@ +/* + * Copyright (c) Contributors, http://www.nsl.tuis.ac.jp + * + */ + + +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); + + private Encoding _encoding = new UTF8Encoding(); + private XmlRpcRequestSerializer _serializer = new XmlRpcRequestSerializer(); + private XmlRpcResponseDeserializer _deserializer = new XmlRpcResponseDeserializer(); + + + public NSLXmlRpcRequest() + { + _params = new ArrayList(); + } + + + public NSLXmlRpcRequest(String methodName, IList parameters) + { + MethodName = methodName; + _params = parameters; + } + + + //public XmlRpcResponse certSend(String url, X509Certificate2 myClientCert, bool checkServerCert, Int32 timeout) + public XmlRpcResponse certSend(String url, NSLCertificateVerify certVerify, bool checkServerCert, Int32 timeout) + { + m_log.InfoFormat("[MONEY NSL XMLRPC]: XmlRpcResponse certSend: connect to {0}", url); + +//#pragma warning disable SYSLIB0014 // Use HttpClient instead of WebRequest.Create. + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); +//#pragma warning restore SYSLIB0014 + if (request==null) { + throw new XmlRpcException(XmlRpcErrorCodes.TRANSPORT_ERROR, XmlRpcErrorCodes.TRANSPORT_ERROR_MSG +": Could not create request with " + url); + } + + X509Certificate2 clientCert = null; + + request.Method = "POST"; + request.ContentType = "text/xml"; + request.AllowWriteStreamBuffering = true; + request.Timeout = timeout; + request.UserAgent = "NSLXmlRpcRequest"; + + if (certVerify != null) { + clientCert = certVerify.GetPrivateCert(); + if (clientCert != null) request.ClientCertificates.Add(clientCert); // Own certificate // 自身の証明書 + request.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(certVerify.ValidateServerCertificate); + } + else { + checkServerCert = false; + } + // + if (!checkServerCert) { + request.Headers.Add("NoVerifyCert", "true"); // Do not verify the certificate of the other party // 相手の証明書を検証しない + } + + // + Stream stream = null; + try { + stream = request.GetRequestStream(); + } +#pragma warning disable CS0168 + catch (Exception ex) { +#pragma warning restore CS0168 + m_log.ErrorFormat("[MONEY NSL XMLRPC]: GetRequestStream Error: {0}", ex); + stream = null; + } + if (stream==null) return null; + + // + XmlTextWriter xml = new XmlTextWriter(stream, _encoding); + _serializer.Serialize(xml, this); + xml.Flush(); + xml.Close(); + + HttpWebResponse response = null; + try { + response = (HttpWebResponse)request.GetResponse(); + } + catch (Exception ex) { + m_log.ErrorFormat("[MONEY NSL XMLRPC]: XmlRpcResponse certSend: GetResponse Error: {0}", ex.ToString()); + } + StreamReader input = new StreamReader(response.GetResponseStream()); + + string inputXml = input.ReadToEnd(); + XmlRpcResponse resp = (XmlRpcResponse)_deserializer.Deserialize(inputXml); + + input.Close(); + response.Close(); + return resp; + } + } +} diff --git a/OpenSim.Modules.Currency/Properties/AssemblyInfo.cs b/OpenSim.Modules.Currency/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..59897cb --- /dev/null +++ b/OpenSim.Modules.Currency/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +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.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 +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/README b/README new file mode 100644 index 0000000..f01d4f4 --- /dev/null +++ b/README @@ -0,0 +1,127 @@ + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// DTL/NSL Secure Money Server for Linux/Unix +// by Fumi.Iseki and NSL +// http://www.nsl.tuis.ac.jp + +0. Outline + + This Money Server is modified version of DTL Currency Processing. + As for this, some bug fixes and some functionality expansions are done. And this can be operated by OpenSim 0.9.x. + But, Web Monitor function (ASP.NET) is removed from original DTL Currency. So, this version is less secure than original + version!! Please use this at Your Own Risk!! + +Attention) + Client(SIM) certification causes an error in mono-5.16 and above. + + +1. Install + +1) Compile Opensim or Prepare compiled binary Opensim + +2) When you use mono + # cd opensim + # svn co http://www.nsl.tuis.ac.jp/svn/opensim/opensim.currency/trunk opensim.currency + # cd opensim.currensy + # ./build.sh + +3) When you use dotnet + # cd opensim + # svn co http://www.nsl.tuis.ac.jp/svn/opensim/opensim.currency/trunk opensim.currency + # cd opensim.currensy + # ./build.net.sh + + +2. Setting + +2-1. Money Server + + # cd opensim-0.9.x-source + # vi bin/MoneyServer.ini + + - Plese set hostname, database, username and password of MySQL at [MySql] section. + - If you use Banker Avatar, please set UUID of Banker Avatar to "BankerAvatar" in MoneyServer.ini. + Banker Avatar can buy money from system with no cost. + When 00000000-0000-0000-0000-000000000000 is specified as UUID, all avatars can get money from system. + - If you want to normally use llGiveMoney() function even when payer doesn't login to OpenSim, + you must set "true" to "enableForceTransfer" in MoneyServer.ini. + - If you want to send money to anotger avatar by PHP script, you must set "true" to "enableScriptSendMoney" + And please set "MoneyScriptAccessKey" and "MoneyScriptIPaddress", too. + "MoneyScriptAccessKey" is Secret key of Helper Script. Specify same key in include/config.php or WI(XoopenSim/Modlos) + "MoneyScriptIPaddress" is IP address of server that Helper Script execute at. Not specify 127.0.0.1. + - If you want to change Update Balance Messages (blue dialog), pleaase enable and rewrite "BalanceMessage..." valiables. + - Please see also: http://www.nsl.tuis.ac.jp/xoops/modules/xpwiki/?OpenSim%2FMoneyServer%2FMoneyServer.ini + + +2-2. Region Server + + # cd opensim-0.9.x-source + # vi bin/OpenSim.ini + + [Economy] + SellEnabled = "true" + CurrencyServer = "https://(MoneyServer's Name or IP):8008/" + UserServer = "http://(UserServer's Name or IP):8002/" + EconomyModule = DTLNSLMoneyModule + + ;; Money Unit fee to upload textures, animations etc + PriceUpload = 10 + + ;; Money Unit fee to create groups + PriceGroupCreate = 100 + + + Attention) + - Module name was changed from DTLMoneyModule to DTLNSLMoneyModule + - Not use 127.0.0.1 or localhost for UserServer's address and CurrencyServer's address. + This address is used for identification of user on Money Server. + - Please see also: http://www.nsl.tuis.ac.jp/xoops/modules/xpwiki/?OpenSim%2FMoneyServer%2FOpenSim.ini + + +2-3. Helper Script + + - If you do not use XoopenSim or Modlos (Web Interface), you should setup helper scripts by manual. + - Please copy Scripts/* to any Web contents directory, and execute setup_sripts.sh shell script. + And next, edit include/config.php to rewrite ENV_HELPER_URL, ENV_HELPER_PATH, DB information, and etc.etc. + - Please see also: http://www.nsl.tuis.ac.jp/xoops/modules/xpwiki/?OpenSim%2FMoneyServer%2FHelper%20Script + + ex.) + # mkdir /var/www/currency + # cp -Rpd Scripts/* /var/www/currency + # cd /var/www/currency + # ./setup_sripts.sh + # chown -R www-data.www-data . + # vi include/config.php + + - Please execute viewer with "-helperuri [ENV_HELPER_URL/]" option. + Here, ENV_HELPER_URL is helper directory url in include/config.php. + - ex.) -helperuri http://localhost/currency/helper/ need last '/' + + +5. License. + + MIT License + + +6. Attention. + + This is unofficial software. Please do not inquire to OpenSim development team or DTL Currency Processing + development team about this software. + + +7. Exemption from responsibility. + + This software is not guaranteed at all. The author doesn't assume the responsibility for the + problem that occurs along with use, remodeling, and the re-distribution of this software at all. + Please use everything by the self-responsibility. + + +8. Address of thanks. + + This Money Server is modified version of DTL Currency Processing. + + About this project, Milo did a lot of advice and donation to us. + + Thank you very much!! + diff --git a/README.jp b/README.jp new file mode 100644 index 0000000..5e8d305 --- /dev/null +++ b/README.jp @@ -0,0 +1,138 @@ + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// OpenSim DTL/NSL Secure Money Server for Linux/Unix +// by Fumi.Iseki and NSL +// http://www.nsl.tuis.ac.jp + +0. はじめに + + これは DTL Currency Processing Project から Web Monitor を取り外して,いくつかのバグフィックスと +0.9.xで動作するようにパッチを充てた物です. + また幾つかの機能拡張も行っています.Web Monitor を取り外したのは,我々のターゲットが主に +Linuxであるのに対して,この機能 ASP.NETで作動しており,Linux上で動作させるのが困難だったためです. + + 従って,セキュリティ的には本来の DTL Money Serverよりも低下していると思われます.実際,この +Money Serverを誤魔化すことも不可能ではありません.つまり,このシステムについては厳密に仮想通貨 +を取り扱いたい環境での使用はお勧めできません.本システムを使用する場合は,あくまでも自己責任で +お使いください. + + +1. インストール + +1) OpenSim をコンパイルするか,コンポいる済みの OpenSim を用意します. + +2) Mono を使用する場合 + # cd opensim + # svn co http://www.nsl.tuis.ac.jp/svn/opensim/opensim.currency/trunk opensim.currency + # cd opensim.currensy + # ./build.sh + +2) dotnet を使用する場合 + # cd opensim + # svn co http://www.nsl.tuis.ac.jp/svn/opensim/opensim.currency/trunk opensim.currency + # cd opensim.currensy + # ./build.net.sh + + +2. 設定 + +2-1. Money Server + + bin/MoneyServer.ini の [MySql] に MySQLサーバにアクセスするための情報を追加する + + # cd opensim-0.9.x-source + # vi bin/MoneyServer.ini + + 1. [MySql]セクションの hostname, database, username, password を設定する. + OpenSim のMySQLの設定と同じものを指定すれば良い. + 2. バンカーアバター(システムから対価無しにMoneyを購入することが可能なアバター)を有効にするには + [MoneyServer]セクションの "BankerAvatar" にバンカーアバターとなるアバターのUUIDを指定する. + UUIDとして 00000000-0000-0000-0000-000000000000を指定した場合は,全てのアバターがバンカーアバターとなる. + 3. 支払い者がログインしていない状態でも,llGiveMoney()を正常に動かす場合は + [MoneyServer]セクションの "enableForceTransfer" を trueにする. + 4. PHPスクリプトの send_money() を使用して,お金をアバターに振り込む場合は "enableScriptSendMoney" を trueにする. + また,"MoneyScriptAccessKey", "MoneyScriptIPaddress" も設定が必要である. + "MoneyScriptAccessKey" はスクリプトへのアクセスキーであり,include/config.php または WI(XoopenSim/Modlos) と同じものを設定する. + "MoneyScriptIPaddress" にはスクリプトが動作するサーバのIPアドレスを記述する.127.0.0.1は使用しないこと. + 5. 所持金のアップデートのメッセージ(青いダイアログ)を変更することが可能である.メッセージを変更したい場合は各 "BalanceMessage..." + を有効にし内容を書き換える. + + 参考: http://www.nsl.tuis.ac.jp/xoops/modules/xpwiki/?OpenSim%2FMoneyServer%2FMoneyServer.ini + + +2-2. Region Server + + bin/OpenSim.ini に MoneyServerに接続するための設定を行う.Money Serverには HTTPS +を使用して接続を行う.この際に使用するポート番号は 8008である. + + # cd opensim-0.9.x-source + # vi bin/OpenSim.ini + + [Economy] + SellEnabled = "true" + CurrencyServer = "https://(MoneyServer's Name or IP):8008/" + UserServer = "http://(UserServer's Name or IP):8002" + EconomyModule = DTLNSLMoneyModule + + ;; Money Unit fee to upload textures, animations etc + PriceUpload = 10 ;; テクスチャなどのアップロードのコスト + + ;; Money Unit fee to create groups + PriceGroupCreate = 100 ;; グループ作成のコスト + + + 注)モジュールの名前が DTLNSLMoneyModule に変更になりました. + なお,UserServer と CurrencyServer のアドレスに localhost (127.0.0.1)を使用しないこと. + これは,アドレスが,Monet Server内でのユーザのアカウント識別に利用されるためである. + + 参考: http://www.nsl.tuis.ac.jp/xoops/modules/xpwiki/?OpenSim%2FMoneyServer%2FOpenSim.ini + + +2-3. Helper スクリプト + + Webインターフェイスの XoopenSimまたは Modlosを使用しない場合,ヘルパースクリプトを手動で設置 +なければならない.ヘルパースクリプトは Scriptsディレクトリにあるので,これを適当なディレクトリ +にコピーし,inculde/helper.php の内容(主に ENV_HELPER_URL, ENV_HELPER_PATH, DB関連)をシステム +に合わせて変更する. + + コマンド例) + # mkdir /var/www/currency + # cp -Rpd Scripts/* /var/www/currency + # cd /var/www/currency + # ./setup_scripts.sh + # chown -R www-data.www-data . + # vi include/config.php + + Viewerは "-helperuri [ENV_HELPER_URL/]" オプション付きで起動しなければならない.ここで ENV_HELPER_URL +ヘルパースクリプトの helperディレクトリのURLである. + 例) -helperuri http://localhost/currency/helper/ 最後の / は必ず必要!! + + 参考: http://www.nsl.tuis.ac.jp/xoops/modules/xpwiki/?OpenSim%2FMoneyServer%2FHelper%20Script + + +3. ライセンス + + MIT License + + +4. 注意事項 + + このソフトウェアはNSLで改造された非公式のソフトウェアです.このソフトウェアに関して OpenSimの開発チームや + DTL Currency Processingの開発チームへのお問い合わせは行わないでください. + + +5. 免責 + + このソフトウェアは全くの無保証です.このソフトウェアの使用・改造・再配布に伴い発生する問題に関して,作者は一切の + 責任を負いません.全て自己責任でご使用ください. + + +6. 謝辞 + + このプログラムは DTL Curremcy Processing を改造したものです. + + このプロジェクトに関して,Miloから沢山のアドバイスと寄付をいただきました. + + ありがとうございました. + diff --git a/Release.txt b/Release.txt new file mode 100644 index 0000000..cbeb0ad --- /dev/null +++ b/Release.txt @@ -0,0 +1,16 @@ + +DTL/NSL Money Server v0.9.3.0 (2023 8/11) + for .Net6 + +DTL/NSL Money Server v0.9.2.2 (2023 6/2) + for Client Certificate + +DTL/NSL Money Server v0.9.1r01 (2020 4/11) + Fix NPC Bug, Variable Port No. of Server + +DTL/NSL Money Server v0.9.1 (2019 11/5) + Suport of OpenSim v0.9.1 + +DTL/NSL Money Server v0.9.0 (2018 4/22) + Suport of OpenSim v0.9.0 + diff --git a/bin/net6.0/MoneyServer b/bin/net6.0/MoneyServer new file mode 100755 index 0000000000000000000000000000000000000000..690bd3a27c9f88b86a443fe88a02e833adee281f GIT binary patch literal 142840 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

sF%?JXf)DJEMpZ)Q!5T@F7O&%jyOC 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 literal 0 HcmV?d00001 diff --git a/bin/net6.0/MoneyServer.runtimeconfig.json b/bin/net6.0/MoneyServer.runtimeconfig.json new file mode 100644 index 0000000..9adb78a --- /dev/null +++ b/bin/net6.0/MoneyServer.runtimeconfig.json @@ -0,0 +1,13 @@ +{ + "runtimeOptions": { + "tfm": "net6.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "6.0.0" + }, + "configProperties": { + "System.Reflection.Metadata.MetadataUpdater.IsSupported": false, + "System.Runtime.TieredCompilation.QuickJit": false + } + } +} \ No newline at end of file diff --git a/bin/net6.0/OpenSim.Data.MySQL.MySQLMoneyDataWrapper.dll b/bin/net6.0/OpenSim.Data.MySQL.MySQLMoneyDataWrapper.dll new file mode 100644 index 0000000000000000000000000000000000000000..3453be03fbd88f6b8bcf69ab5914703eaf480968 GIT binary patch literal 49152 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 tf5&hqM>#2hNeN6!U{V5;5}1_0qy#1 + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/MoneyServer.ini b/config/MoneyServer.ini new file mode 100644 index 0000000..99cce6d --- /dev/null +++ b/config/MoneyServer.ini @@ -0,0 +1,105 @@ +[Startup] +; +; Place to create a PID file +PIDFile = "/var/run/MoneyServer.pid" + +NoVerifyCertChain = true +NoVerifyCertHostname = true + + +[MySql] +; +;Connection parameters of MySQL +hostname = localhost ; Name of MySQL Server +database = opensim_db +username = opensim_user +password = opensim_pass + +pooling = false +port = 3306 + +; +; Max DB connections kept by money server. +MaxConnection = 10 + + +[MoneyServer] +; Port No. of this Server +;ServerPort = 8008 + +; +; If the user is not found in database,he/she will be created with the default balance. +DefaultBalance = 1000 + +; +; Is amount==0 transaction enable? Default is false. +;EnableAmountZero = false + +; +; If "00000000-0000-0000-0000-000000000000" is specified, all avatars can get money from system. +; If "" is specified, nobody can get money. +BankerAvatar = "" + +; +; If you want to use llGiveMoney() function normally even when payer doesn't login to OpenSim, +; please set true to this valiable +EnableForceTransfer = true + +; +; Send/Move money to/from avatar by Money Script +;EnableScriptSendMoney = false +;MoneyScriptAccessKey = "123456789" ;; Specify same secret key in include/config.php or WI(XoopenSim/Modlos) +;MoneyScriptIPaddress = "202.26.159.139" ;; Not use 127.0.0.1. This is used to generate Script key + +; +; for HG/Guest Avatar. Foreign Avatar is always false +;EnableHGAvatar = false +;EnableGuestAvatar = false +;HGAvatarDefaultBalance = 0 +;GuestAvatarDefaultBalance = 0 + +; +; Message that displayed in blue dialog, when balance is updated. +; If "" is specified, blue dialog is not displayed. +; You can use {0} and {1} in message string. +; {0} means amount and {1} means avatar name or object owner name. +;BalanceMessageSendGift = "Sent Gift L${0} to {1}." ;; for send gift to other avatar +;BalanceMessageReceiveGift = "Received Gift L${0} from {1}." ;; for receieve gift from other avatar +;BalanceMessagePayCharge = "" ;; for upload and group creation charge +;BalanceMessageBuyObject = "Bought the Object {2} from {1} by L${0}." ;; for buy the object +;BalanceMessageSellObject = "{1} bought the Object {2} by L${0}." ;; for sell the object +;BalanceMessageLandSale = "Paid the Money L${0} for Land." ;; for buy the land +;BalanceMessageScvLandSale = "" ;; for get the money of the sold land +;BalanceMessageGetMoney = "Got the Money L${0} from {1}." ;; for get the money from object by llGiveMoney() +;BalanceMessageBuyMoney = "Bought the Money L${0}." ;; for buy the money from system +;BalanceMessageRollBack = "RollBack the Transaction: L${0} from/to {1}.";; when roll back ocuurred +;BalanceMessageSendMoney = "Paid the Money L${0} to {1}." ;; for sender of sending the money +;BalanceMessageReceiveMoney = "Received L${0} from {1}." ;; for receive the money + + +[Certificate] +; +; Certification Configuration +; + +; CA Cert to check Client/Server Cert +;CACertFilename = "cacert.crt" + +; +; HTTPS Server Cert (Server Mode) +;ServerCertFilename = "SineWaveCert.pfx" +;ServerCertPassword = "123" +ServerCertFilename = "server_cert.p12" +ServerCertPassword = "" + +; Client Authentication from Region Server +;CheckClientCert = false ;; check Region Server +;ClientCrlFilename = "clcrl.crt" + + +; +; XML RPC to Region Server (Client Mode) +;;CheckServerCert = false ;; check Region Server +;;ClientCertFilename = "client_cert.p12" +;;ClientCertPassword = "" + diff --git a/config/OpenSim.ini.sample b/config/OpenSim.ini.sample new file mode 100644 index 0000000..1f7e7f5 --- /dev/null +++ b/config/OpenSim.ini.sample @@ -0,0 +1,43 @@ +[Economy] + + ;; Enables selling things for $0. Default is true. + SellEnabled = true + + ;CurrencyServer = "" ;; ex.) "https://jogrid.net:8008/" Default is "" + EconomyModule = DTLNSLMoneyModule + + ; + ;; CA + ;CheckServerCert = false + ;CACertFilename = "cacert.crt" + + ; + ;; Client Cert + ;ClientCertFilename = "region_cert.p12" + ;ClientCertPassword = "" + + ; + ;; Money Unit fee to upload textures, animations etc. Default is 0. + PriceUpload = 10 + + ;; Mesh upload factors + ;MeshModelUploadCostFactor = 0.0 + ;MeshModelUploadTextureCostFactor = 1.0 + ;MeshModelMinCostFactor = 0.0 + + ;; Money Unit fee to create groups. Default is 0. + PriceGroupCreate = 10 + + ; + ;; Avatar Class for HG Avatar + ;; {ForeignAvatar, HGAvatar, GuestAvatar, LocalAvatar} HGAvatar + ;; HG Avatar is assumed as a specified avatar class. Default is HGAvatar + ;; Processing for each avatar class is dependent on Money Server settings. + ;HGAvatarAs = "HGAvatar" + + ; + ;; in development + ;SettlementByWeb = false + ;SettlementURL = "http://www.jogrid.net" + ;SettlementMessage = "Goto the settlement of accounts Web page. (Testing now)" + diff --git a/config/ResetDB_to_0.8.2.sql b/config/ResetDB_to_0.8.2.sql new file mode 100644 index 0000000..ec2bdbf --- /dev/null +++ b/config/ResetDB_to_0.8.2.sql @@ -0,0 +1,7 @@ +alter table userinfo drop type; +alter table userinfo drop class; +alter table userinfo drop serverurl; +alter table userinfo COMMENT='Rev.2'; +alter table balances COMMENT='Rev.2'; +alter table totalsales COMMENT='Rev.2'; +alter table transactions COMMENT='Rev.9'; diff --git a/config/SineWaveCert.pfx b/config/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/config/opensim_money.net.service b/config/opensim_money.net.service new file mode 100644 index 0000000..6427722 --- /dev/null +++ b/config/opensim_money.net.service @@ -0,0 +1,30 @@ +# /usr/lib/systemd/system/opensim_money.net.service +# +# v.0.2 by Fumi.Iseki +# + +[Unit] +Description = MoneyServer for OpenSimulator +After = opensim_robust.net.service + +[Service] +Environment = OPENSIM_DIR=/usr/local/opensim_server +Environment = OPENSIM_COM=MoneyServer +Environment = OPENSIM_DLL=MoneyServer.dll +Environment = EXEC_COM=/usr/bin/dotnet +Environment = TMUX_OS_ID=opensim_money +#Environment = WAIT_STOP_TM=5 + +Type = forking +User = root +LimitSTACK = infinity + +WorkingDirectory = ${OPENSIM_DIR}/bin + +ExecStart = /usr/bin/tmux new -d -s ${TMUX_OS_ID} -c ${OPENSIM_DIR}/bin ${OPENSIM_DIR}/bin/${OPENSIM_COM} +#ExecStop = /usr/bin/tmux send-keys -t ${TMUX_OS_ID} C-m "quit" C-m ; /usr/bin/sleep ${WAIT_STOP_TM} + +Restart = always + +[Install] +WantedBy = multi-user.target diff --git a/config/server_cert.p12 b/config/server_cert.p12 new file mode 100644 index 0000000000000000000000000000000000000000..16adb0142af1ca8eef9c0fe727f52b561dff1867 GIT binary patch literal 2685 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 literal 0 HcmV?d00001 diff --git a/old/HttpServer_OpenSim/original/source/HttpClientContext.cs b/old/HttpServer_OpenSim/original/source/HttpClientContext.cs new file mode 100644 index 0000000..b85f436 --- /dev/null +++ b/old/HttpServer_OpenSim/original/source/HttpClientContext.cs @@ -0,0 +1,578 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using HttpServer.Exceptions; +using HttpServer.Parser; + +namespace HttpServer +{ + /// + /// Contains a connection to a browser/client. + /// + /// + /// Remember to after you have hooked the event. + /// + /// TODO: Maybe this class should be broken up into HttpClientChannel and HttpClientContext? + public class HttpClientContext : IHttpClientContext ,IDisposable + { + private readonly byte[] _buffer; + private int _bytesLeft; + private ILogWriter _log; + private readonly IHttpRequestParser _parser; + private readonly int _bufferSize; + private IHttpRequest _currentRequest; + private Socket _sock; + + public bool Available = true; + private bool _endWhenDone = false; + public bool StreamPassedOff = false; + public int MonitorStartMS = 0; + public int MonitorKeepaliveMS = 0; + public bool TriggerKeepalive = false; + public int TimeoutFirstLine = 70000; // 70 seconds + public int TimeoutRequestReceived = 180000; // 180 seconds + + // The difference between this and request received is on POST more time is needed before we get the full request. + public int TimeoutFullRequestProcessed = 1200000; // 20 minutes + public int TimeoutKeepAlive = 400000; // 400 seconds before keepalive timeout + + public bool FirstRequestLineReceived; + public bool FullRequestReceived; + public bool FullRequestProcessed; + + public bool StopMonitoring; + /// + /// This context have been cleaned, which means that it can be reused. + /// + public event EventHandler Cleaned = delegate { }; + + /// + /// Context have been started (a new client have connected) + /// + public event EventHandler Started = delegate { }; + + /// + /// Initializes a new instance of the class. + /// + /// true if the connection is secured (SSL/TLS) + /// client that connected. + /// Stream used for communication + /// Used to create a . + /// Size of buffer to use when reading data. Must be at least 4096 bytes. + /// If fails + /// Stream must be writable and readable. + public HttpClientContext(bool secured, IPEndPoint remoteEndPoint, + Stream stream, IRequestParserFactory parserFactory, int bufferSize, Socket sock) + { + Check.Require(remoteEndPoint, "remoteEndPoint"); + Check.NotEmpty(remoteEndPoint.Address.ToString(), "remoteEndPoint.Address"); + Check.Require(stream, "stream"); + Check.Require(parserFactory, "parser"); + Check.Min(4096, bufferSize, "bufferSize"); + Check.Require(sock, "socket"); + + if (!stream.CanWrite || !stream.CanRead) + throw new ArgumentException("Stream must be writable and readable."); + + _bufferSize = bufferSize; + RemoteAddress = remoteEndPoint.Address.ToString(); + RemotePort = remoteEndPoint.Port.ToString(); + _log = NullLogWriter.Instance; + _parser = parserFactory.CreateParser(_log); + _parser.RequestCompleted += OnRequestCompleted; + _parser.RequestLineReceived += OnRequestLine; + _parser.HeaderReceived += OnHeaderReceived; + _parser.BodyBytesReceived += OnBodyBytesReceived; + _currentRequest = new HttpRequest(); + Available = false; + IsSecured = secured; + _stream = stream; + _sock = sock; + _buffer = new byte[bufferSize]; + + } + + public bool EndWhenDone + { + get { return _endWhenDone; } + set { _endWhenDone = value;} + } + + /// + /// Process incoming body bytes. + /// + /// + /// Bytes + protected virtual void OnBodyBytesReceived(object sender, BodyEventArgs e) + { + _currentRequest.AddToBody(e.Buffer, e.Offset, e.Count); + } + + /// + /// + /// + /// + /// + protected virtual void OnHeaderReceived(object sender, HeaderEventArgs e) + { + if (string.Compare(e.Name, "expect", true) == 0 && e.Value.Contains("100-continue")) + { + Respond("HTTP/1.0", HttpStatusCode.Continue, "Please continue mate."); + } + + _currentRequest.AddHeader(e.Name, e.Value); + } + + private void OnRequestLine(object sender, RequestLineEventArgs e) + { + _currentRequest.Method = e.HttpMethod; + _currentRequest.HttpVersion = e.HttpVersion; + _currentRequest.UriPath = e.UriPath; + FirstRequestLineReceived = true; + } + + /// + /// Overload to specify own type. + /// + /// + /// Must be specified before the context is being used. + /// + protected IHttpRequest CurrentRequest + { get { return _currentRequest; } set { _currentRequest = value; } } + + /// + /// Start reading content. + /// + /// + /// Make sure to call base.Start() if you override this method. + /// + public virtual void Start() + { + try + { + _stream.BeginRead(_buffer, 0, _bufferSize, OnReceive, null); + } + catch (IOException err) + { + LogWriter.Write(this, LogPrio.Debug, err.ToString()); + } + + Started(this, EventArgs.Empty); + } + + /// + /// Clean up context. + /// + /// + /// Make sure to call base.Cleanup() if you override the method. + /// + public virtual void Cleanup() + { + if (Stream == null) + return; + if (StreamPassedOff) + return; + _sock = null; + + Stream.Dispose(); + Stream = null; + _currentRequest.Clear(); + _bytesLeft = 0; + + FirstRequestLineReceived = false; + FullRequestReceived = false; + FullRequestProcessed = false; + MonitorStartMS = 0; + StopMonitoring = false; + MonitorKeepaliveMS = 0; + TriggerKeepalive = false; + + Cleaned(this, EventArgs.Empty); + _parser.Clear(); + } + + public void Close() + { + if (StreamPassedOff) + return; + Cleanup(); + Available = true; + } + + /// + /// Using SSL or other encryption method. + /// + [Obsolete("Use IsSecured instead.")] + public bool Secured + { + get { return IsSecured; } + } + + /// + /// Using SSL or other encryption method. + /// + public bool IsSecured { get; internal set; } + + /// + /// Specify which logger to use. + /// + public ILogWriter LogWriter + { + get { return _log; } + set + { + _log = value ?? NullLogWriter.Instance; + _parser.LogWriter = _log; + } + } + + private Stream _stream; + + /// + /// Gets or sets the network stream. + /// + internal Stream Stream + { + get { return _stream; } + set { _stream = value; } + } + + /// + /// Gets or sets IP address that the client connected from. + /// + internal string RemoteAddress { get; set; } + + /// + /// Gets or sets port that the client connected from. + /// + internal string RemotePort { get; set; } + + /// + /// Disconnect from client + /// + /// error to report in the event. + public void Disconnect(SocketError error) + { + // disconnect may not throw any exceptions + try + { + //_sock.Disconnect(true); + if (error == SocketError.Success) + { + if (Stream is ReusableSocketNetworkStream) + ((NetworkStream)Stream).Flush(); + if (_currentRequest.Connection == ConnectionType.Close) + _sock = null; + + } + if (error == SocketError.TimedOut) + { + try + { + if (Stream != null) + Stream.Close(); + _sock = null; + } + catch {} // Best Try + } + //Stream.Close(); + Disconnected(this, new DisconnectedEventArgs(error)); + } + catch (Exception err) + { + LogWriter.Write(this, LogPrio.Error, "Disconnect threw an exception: " + err); + } + } + + private void OnReceive(IAsyncResult ar) + { + try + { + int bytesRead = 0; + + try + { + bytesRead = Stream.EndRead(ar); + } + catch (NullReferenceException) + { + Disconnect(SocketError.ConnectionReset); + return; + } + + if (bytesRead == 0) + { + Disconnect(SocketError.ConnectionReset); + return; + } + _bytesLeft += bytesRead; + if (_bytesLeft > _buffer.Length) + { +#if DEBUG + throw new BadRequestException("Too large HTTP header: " + Encoding.UTF8.GetString(_buffer, 0, bytesRead)); +#else + throw new BadRequestException("Too large HTTP header: " + _bytesLeft); +#endif + } + +#if DEBUG +#pragma warning disable 219 + string temp = Encoding.ASCII.GetString(_buffer, 0, _bytesLeft); + LogWriter.Write(this, LogPrio.Trace, "Received: " + temp); +#pragma warning restore 219 +#endif + int offset = _parser.Parse(_buffer, 0, _bytesLeft); + if (Stream == null) + return; // "Connection: Close" in effect. + + // try again to see if we can parse another message (check parser to see if it is looking for a new message) + int oldOffset = offset; + while (_parser.CurrentState == RequestParserState.FirstLine && offset != 0 && _bytesLeft - offset > 0) + { +#if DEBUG + temp = Encoding.ASCII.GetString(_buffer, offset, _bytesLeft - offset); + LogWriter.Write(this, LogPrio.Trace, "Processing: " + temp); +#endif + offset = _parser.Parse(_buffer, offset, _bytesLeft - offset); + if (Stream == null) + return; // "Connection: Close" in effect. + } + + // need to be able to move prev bytes, so restore offset. + if (offset == 0) + offset = oldOffset; + + // copy unused bytes to the beginning of the array + if (offset > 0 && _bytesLeft > offset) + Buffer.BlockCopy(_buffer, offset, _buffer, 0, _bytesLeft - offset); + + _bytesLeft -= offset; + if (Stream != null && Stream.CanRead && !StreamPassedOff) + Stream.BeginRead(_buffer, _bytesLeft, _buffer.Length - _bytesLeft, OnReceive, null); + else + { + _log.Write(this, LogPrio.Warning, "Could not read any more from the socket."); + Disconnect(SocketError.Success); + } + } + catch (BadRequestException err) + { + LogWriter.Write(this, LogPrio.Warning, "Bad request, responding with it. Error: " + err); + try + { + Respond("HTTP/1.0", HttpStatusCode.BadRequest, err.Message); + } + catch(Exception err2) + { + LogWriter.Write(this, LogPrio.Fatal, "Failed to reply to a bad request. " + err2); + } + Disconnect(SocketError.NoRecovery); + } + catch (IOException err) + { + LogWriter.Write(this, LogPrio.Debug, "Failed to end receive: " + err.Message); + if (err.InnerException is SocketException) + Disconnect((SocketError) ((SocketException) err.InnerException).ErrorCode); + else + Disconnect(SocketError.ConnectionReset); + } + catch (ObjectDisposedException err) + { + LogWriter.Write(this, LogPrio.Debug, "Failed to end receive : " + err.Message); + Disconnect(SocketError.NotSocket); + } + catch (NullReferenceException err) + { + LogWriter.Write(this, LogPrio.Debug, "Failed to end receive : NullRef: " + err.Message); + Disconnect(SocketError.NoRecovery); + } + catch (Exception err) + { + LogWriter.Write(this, LogPrio.Debug, "Failed to end receive: " + err.Message); + Disconnect(SocketError.NoRecovery); + } + } + + private void OnRequestCompleted(object source, EventArgs args) + { + _currentRequest.AddHeader("remote_addr", RemoteAddress); + _currentRequest.AddHeader("remote_port", RemotePort); + + // load cookies if they exist + RequestCookies cookies = _currentRequest.Headers["cookie"] != null + ? new RequestCookies(_currentRequest.Headers["cookie"]) + : new RequestCookies(String.Empty); + _currentRequest.SetCookies(cookies); + + _currentRequest.Body.Seek(0, SeekOrigin.Begin); + RequestReceived(this, new RequestEventArgs(_currentRequest)); + + FullRequestReceived = true; + + if (_currentRequest.Connection == ConnectionType.Close) + _sock = null; + else + TriggerKeepalive = true; + + if (!StreamPassedOff) + _currentRequest.Clear(); + } + + /// + /// Send a response. + /// + /// Either or + /// HTTP status code + /// reason for the status code. + /// HTML body contents, can be null or empty. + /// A content type to return the body as, i.e. 'text/html' or 'text/plain', defaults to 'text/html' if null or empty + /// If is invalid. + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body, string contentType) + { + if (string.IsNullOrEmpty(contentType)) + contentType = "text/html"; + + if (string.IsNullOrEmpty(httpVersion) || !httpVersion.StartsWith("HTTP/1")) + throw new ArgumentException("Invalid HTTP version"); + + if (string.IsNullOrEmpty(reason)) + reason = statusCode.ToString(); + + string response = string.IsNullOrEmpty(body) + ? httpVersion + " " + (int) statusCode + " " + reason + "\r\n\r\n" + : string.Format("{0} {1} {2}\r\nContent-Type: {5}\r\nContent-Length: {3}\r\n\r\n{4}", + httpVersion, (int) statusCode, reason ?? statusCode.ToString(), + body.Length, body, contentType); + byte[] buffer = Encoding.ASCII.GetBytes(response); + + Send(buffer); + if (_currentRequest.Connection == ConnectionType.Close) + FullRequestProcessed = true; + + } + + /// + /// Send a response. + /// + /// Either or + /// HTTP status code + /// reason for the status code. + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason) + { + Respond(httpVersion, statusCode, reason, null, null); + } + + /// + /// Send a response. + /// + /// + public void Respond(string body) + { + if (body == null) + throw new ArgumentNullException("body"); + Respond("HTTP/1.1", HttpStatusCode.OK, HttpStatusCode.OK.ToString(), body, null); + } + + /// + /// send a whole buffer + /// + /// buffer to send + /// + public void Send(byte[] buffer) + { + if (buffer == null) + throw new ArgumentNullException("buffer"); + Send(buffer, 0, buffer.Length); + } + + /// + /// Send data using the stream + /// + /// Contains data to send + /// Start position in buffer + /// number of bytes to send + /// + /// + public void Send(byte[] buffer, int offset, int size) + { + + if (offset + size > buffer.Length) + throw new ArgumentOutOfRangeException("offset", offset, "offset + size is beyond end of buffer."); + + if (Stream != null && Stream.CanWrite) + { + try + { + Stream.Write(buffer, offset, size); + } + catch (IOException) + { + + } + } + + } + + /// + /// The context have been disconnected. + /// + /// + /// Event can be used to clean up a context, or to reuse it. + /// + public event EventHandler Disconnected = delegate{}; + /// + /// A request have been received in the context. + /// + public event EventHandler RequestReceived = delegate{}; + + public HTTPNetworkContext GiveMeTheNetworkStreamIKnowWhatImDoing() + { + _endWhenDone = true; + StreamPassedOff = true; + _parser.RequestCompleted -= OnRequestCompleted; + _parser.RequestLineReceived -= OnRequestLine; + _parser.HeaderReceived -= OnHeaderReceived; + _parser.BodyBytesReceived -= OnBodyBytesReceived; + _parser.Clear(); + + return new HTTPNetworkContext() { Socket = _sock ,Stream = _stream as NetworkStream}; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool unmanaged) + { + if (unmanaged) + { + if (Stream != null) + { + try + { + if (Stream.CanWrite) + Stream.Flush(); + + Cleanup(); + + } + catch (IOException) + { + + } + } + + } + + } + + ~HttpClientContext() + { + Dispose(); + } + } +} \ No newline at end of file diff --git a/old/HttpServer_OpenSim/original/source/HttpContextFactory.cs b/old/HttpServer_OpenSim/original/source/HttpContextFactory.cs new file mode 100644 index 0000000..a50b9a3 --- /dev/null +++ b/old/HttpServer_OpenSim/original/source/HttpContextFactory.cs @@ -0,0 +1,354 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; + +namespace HttpServer +{ + /// + /// Used to create and reuse contexts. + /// + public class HttpContextFactory : IHttpContextFactory + { + private readonly int _bufferSize; + private readonly Queue _contextQueue = new Queue(); + private readonly IRequestParserFactory _factory; + private readonly ILogWriter _logWriter; + private readonly ContextTimeoutManager _contextTimeoutManager; + + /// + /// Initializes a new instance of the class. + /// + /// The writer. + /// Amount of bytes to read from the incoming socket stream. + /// Used to create a request parser. + public HttpContextFactory(ILogWriter writer, int bufferSize, IRequestParserFactory factory) + { + _logWriter = writer; + _bufferSize = bufferSize; + _factory = factory; + _contextTimeoutManager = new ContextTimeoutManager(ContextTimeoutManager.MonitorType.Thread); + } + + /// + /// True if detailed trace logs should be written. + /// + public bool UseTraceLogs { get; set; } + + /// + /// Create a new context. + /// + /// true if socket is running HTTPS. + /// Client that connected + /// Network/SSL stream. + /// A context. + protected HttpClientContext CreateContext(bool isSecured, IPEndPoint endPoint, Stream stream, Socket sock) + { + HttpClientContext context; + lock (_contextQueue) + { + if (_contextQueue.Count > 0) + { + context = _contextQueue.Dequeue(); + if (!context.Available) + { + context = CreateNewContext(isSecured, endPoint, stream, sock); + context.Disconnected += OnFreeContext; + context.RequestReceived += OnRequestReceived; + context.EndWhenDone = true; + + } + } + else + { + context = CreateNewContext(isSecured, endPoint, stream, sock); + context.Disconnected += OnFreeContext; + context.RequestReceived += OnRequestReceived; + } + } + + context.Stream = stream; + context.IsSecured = isSecured; + context.RemotePort = endPoint.Port.ToString(); + context.RemoteAddress = endPoint.Address.ToString(); + _contextTimeoutManager.StartMonitoringContext(context); + context.Start(); + + return context; + } + + /// + /// Create a new context. + /// + /// true if HTTPS is used. + /// Remote client + /// Network stream, uses . + /// A new context (always). + protected virtual HttpClientContext CreateNewContext(bool isSecured, IPEndPoint endPoint, Stream stream, Socket sock) + { + return new HttpClientContext(isSecured, endPoint, stream, _factory, _bufferSize, sock); + } + + private void OnRequestReceived(object sender, RequestEventArgs e) + { + RequestReceived(sender, e); + } + + private void OnFreeContext(object sender, DisconnectedEventArgs e) + { + var imp = (HttpClientContext) sender; + imp.Cleanup(); + + if (!imp.EndWhenDone) + { + lock (_contextQueue) + _contextQueue.Enqueue(imp); + } + else + { + + imp.Close(); + } + } + + + #region IHttpContextFactory Members + + /// + /// Create a secure . + /// + /// Client socket (accepted by the ). + /// HTTPS certificate to use. + /// Kind of HTTPS protocol. Usually TLS or SSL. + /// + /// A created . + /// + public IHttpClientContext CreateSecureContext(Socket socket, X509Certificate certificate, SslProtocols protocol) + { + var networkStream = new ReusableSocketNetworkStream(socket, true); + var remoteEndPoint = (IPEndPoint) socket.RemoteEndPoint; + + var sslStream = new SslStream(networkStream, false); + try + { + //TODO: this may fail + sslStream.AuthenticateAsServer(certificate, false, protocol, false); + return CreateContext(true, remoteEndPoint, sslStream, socket); + } + catch (IOException err) + { + if (UseTraceLogs) + _logWriter.Write(this, LogPrio.Trace, err.Message); + } + catch (ObjectDisposedException err) + { + if (UseTraceLogs) + _logWriter.Write(this, LogPrio.Trace, err.Message); + } + + return null; + } + + + /// + /// A request have been received from one of the contexts. + /// + public event EventHandler RequestReceived = delegate{}; + + /// + /// Creates a that handles a connected client. + /// + /// Client socket (accepted by the ). + /// + /// A creates . + /// + public IHttpClientContext CreateContext(Socket socket) + { + var networkStream = new ReusableSocketNetworkStream(socket, true); + var remoteEndPoint = (IPEndPoint) socket.RemoteEndPoint; + return CreateContext(false, remoteEndPoint, networkStream, socket); + } + + #endregion + + /// + /// Server is shutting down so shut down the factory + /// + public void Shutdown() + { + _contextTimeoutManager.StopMonitoring(); + } + } + + /// + /// Custom network stream to mark sockets as reusable when disposing the stream. + /// + internal class ReusableSocketNetworkStream : NetworkStream + { + private bool disposed = false; + /// + /// Creates a new instance of the class for the specified . + /// + /// + /// The that the will use to send and receive data. + /// + /// + /// The parameter is null. + /// + /// + /// The parameter is not connected. + /// -or- + /// The property of the parameter is not . + /// -or- + /// The parameter is in a nonblocking state. + /// + public ReusableSocketNetworkStream(Socket socket) + : base(socket) + { + } + + /// + /// Initializes a new instance of the class for the specified with the specified ownership. + /// + /// + /// The that the will use to send and receive data. + /// + /// + /// Set to true to indicate that the will take ownership of the ; otherwise, false. + /// + /// + /// The parameter is null. + /// + /// + /// The parameter is not connected. + /// -or- + /// the value of the property of the parameter is not . + /// -or- + /// the parameter is in a nonblocking state. + /// + public ReusableSocketNetworkStream(Socket socket, bool ownsSocket) + : base(socket, ownsSocket) + { + } + + /// + /// Creates a new instance of the class for the specified with the specified access rights. + /// + /// + /// The that the will use to send and receive data. + /// + /// + /// A bitwise combination of the values that specify the type of access given to the over the provided . + /// + /// + /// The parameter is null. + /// + /// + /// The parameter is not connected. + /// -or- + /// the property of the parameter is not . + /// -or- + /// the parameter is in a nonblocking state. + /// + public ReusableSocketNetworkStream(Socket socket, FileAccess access) + : base(socket, access) + { + } + + /// + /// Creates a new instance of the class for the specified with the specified access rights and the specified ownership. + /// + /// + /// The that the will use to send and receive data. + /// + /// + /// A bitwise combination of the values that specifies the type of access given to the over the provided . + /// + /// + /// Set to true to indicate that the will take ownership of the ; otherwise, false. + /// + /// + /// The parameter is null. + /// + /// + /// The parameter is not connected. + /// -or- + /// The property of the parameter is not . + /// -or- + /// The parameter is in a nonblocking state. + /// + public ReusableSocketNetworkStream(Socket socket, FileAccess access, bool ownsSocket) + : base(socket, access, ownsSocket) + { + } + + /// + /// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. + /// + public override void Close() + { + + if (Socket != null && Socket.Connected) + Socket.Close(); //TODO: Maybe use Disconnect with reuseSocket=true? I tried but it took forever. + base.Close(); + + } + + /// + /// Releases the unmanaged resources used by the and optionally releases the managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected override void Dispose(bool disposing) + { + + try + { + if (!disposed) + { + disposed = true; + if (Socket != null && Socket.Connected) + Socket.Disconnect(true); + + } + base.Dispose(disposing); + } + catch { } // Best effort, ignore fails + } + } + + /// + /// Used to create es. + /// + public interface IHttpContextFactory + { + /// + /// Creates a that handles a connected client. + /// + /// Client socket (accepted by the ). + /// A creates . + IHttpClientContext CreateContext(Socket socket); + + /// + /// Create a secure . + /// + /// Client socket (accepted by the ). + /// HTTPS certificate to use. + /// Kind of HTTPS protocol. Usually TLS or SSL. + /// A created . + IHttpClientContext CreateSecureContext(Socket socket, X509Certificate certificate, SslProtocols protocol); + + /// + /// A request have been received from one of the contexts. + /// + event EventHandler RequestReceived; + + /// + /// Server is shutting down so shut down the factory + /// + void Shutdown(); + } +} \ No newline at end of file diff --git a/old/HttpServer_OpenSim/original/source/IHttpClientContext.cs b/old/HttpServer_OpenSim/original/source/IHttpClientContext.cs new file mode 100644 index 0000000..174e7e2 --- /dev/null +++ b/old/HttpServer_OpenSim/original/source/IHttpClientContext.cs @@ -0,0 +1,143 @@ +using System; +using System.Net; +using System.Net.Sockets; + +namespace HttpServer +{ + /// + /// Contains a connection to a browser/client. + /// + public interface IHttpClientContext + { + /// + /// Using SSL or other encryption method. + /// + [Obsolete("Use IsSecured instead.")] + bool Secured { get; } + + /// + /// Using SSL or other encryption method. + /// + bool IsSecured { get; } + + /// + /// Disconnect from client + /// + /// error to report in the event. + void Disconnect(SocketError error); + + /// + /// Send a response. + /// + /// Either or + /// HTTP status code + /// reason for the status code. + /// HTML body contents, can be null or empty. + /// A content type to return the body as, i.e. 'text/html' or 'text/plain', defaults to 'text/html' if null or empty + /// If is invalid. + void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body, string contentType); + + /// + /// Send a response. + /// + /// Either or + /// HTTP status code + /// reason for the status code. + void Respond(string httpVersion, HttpStatusCode statusCode, string reason); + + /// + /// Send a response. + /// + /// + void Respond(string body); + + /// + /// send a whole buffer + /// + /// buffer to send + /// + void Send(byte[] buffer); + + /// + /// Send data using the stream + /// + /// Contains data to send + /// Start position in buffer + /// number of bytes to send + /// + /// + void Send(byte[] buffer, int offset, int size); + + /// + /// Closes the streams and disposes of the unmanaged resources + /// + void Close(); + + bool EndWhenDone { get; set; } + + /// + /// The context have been disconnected. + /// + /// + /// Event can be used to clean up a context, or to reuse it. + /// + event EventHandler Disconnected; + + /// + /// A request have been received in the context. + /// + event EventHandler RequestReceived; + + HTTPNetworkContext GiveMeTheNetworkStreamIKnowWhatImDoing(); + } + public class HTTPNetworkContext + { + public NetworkStream Stream; + public Socket Socket; + } + + /// + /// A have been disconnected. + /// + public class DisconnectedEventArgs : EventArgs + { + /// + /// Gets reason to why client disconnected. + /// + public SocketError Error { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// Reason to disconnection. + public DisconnectedEventArgs(SocketError error) + { + Check.Require(error, "error"); + + Error = error; + } + } + + /// + /// + /// + public class RequestEventArgs : EventArgs + { + /// + /// Gets received request. + /// + public IHttpRequest Request { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The request. + public RequestEventArgs(IHttpRequest request) + { + Check.Require(request, "request"); + + Request = request; + } + } + +} diff --git a/old/HttpServer_OpenSim/patch/HttpServer.patch b/old/HttpServer_OpenSim/patch/HttpServer.patch new file mode 100644 index 0000000..1ae4237 --- /dev/null +++ b/old/HttpServer_OpenSim/patch/HttpServer.patch @@ -0,0 +1,116 @@ +diff -Nur HttpServer.orig/HttpClientContext.cs HttpServer/HttpClientContext.cs +--- HttpServer.orig/HttpClientContext.cs 2016-07-23 11:49:36.242240234 +0900 ++++ HttpServer/HttpClientContext.cs 2016-07-23 11:52:31.801952465 +0900 +@@ -6,6 +6,10 @@ + using HttpServer.Exceptions; + using HttpServer.Parser; + ++// by Fumi.Iseki ++using System.Net.Security; ++using System.Security.Cryptography.X509Certificates; ++ + namespace HttpServer + { + /// +@@ -92,6 +96,18 @@ + _sock = sock; + _buffer = new byte[bufferSize]; + ++ // by Fumi.Iseki ++ SSLCommonName = ""; ++ if (secured) ++ { ++ SslStream _ssl = (SslStream)_stream; ++ X509Certificate _cert1 = _ssl.RemoteCertificate; ++ if (_cert1 != null) ++ { ++ X509Certificate2 _cert2 = new X509Certificate2(_cert1); ++ if (_cert2 != null) SSLCommonName = _cert2.GetNameInfo(X509NameType.SimpleName, false); ++ } ++ } + } + + public bool EndWhenDone +@@ -215,6 +231,11 @@ + /// + public bool IsSecured { get; internal set; } + ++ // ++ // ++ // by Fumi.Iseki ++ public string SSLCommonName { get; internal set; } ++ + /// + /// Specify which logger to use. + /// +diff -Nur HttpServer.orig/HttpContextFactory.cs HttpServer/HttpContextFactory.cs +--- HttpServer.orig/HttpContextFactory.cs 2016-07-23 11:49:36.242240234 +0900 ++++ HttpServer/HttpContextFactory.cs 2016-07-23 11:52:32.648951102 +0900 +@@ -20,6 +20,10 @@ + private readonly ILogWriter _logWriter; + private readonly ContextTimeoutManager _contextTimeoutManager; + ++ // by Fumi.Iseki ++ public static RemoteCertificateValidationCallback ClientCertificateValidationCallback = null; ++ private RemoteCertificateValidationCallback _clientCallback = null; ++ + /// + /// Initializes a new instance of the class. + /// +@@ -32,6 +36,13 @@ + _bufferSize = bufferSize; + _factory = factory; + _contextTimeoutManager = new ContextTimeoutManager(ContextTimeoutManager.MonitorType.Thread); ++ ++ // by Fumi.Iseki ++ if (ClientCertificateValidationCallback != null) ++ { ++ _clientCallback = ClientCertificateValidationCallback; ++ ClientCertificateValidationCallback = null; ++ } + } + + /// +@@ -132,11 +143,25 @@ + var networkStream = new ReusableSocketNetworkStream(socket, true); + var remoteEndPoint = (IPEndPoint) socket.RemoteEndPoint; + +- var sslStream = new SslStream(networkStream, false); ++ // by Fumi.Iseki ++ //var sslStream = new SslStream(networkStream, false); ++ SslStream sslStream = null; + try + { + //TODO: this may fail +- sslStream.AuthenticateAsServer(certificate, false, protocol, false); ++ // by Fumi.Iseki ++ //sslStream.AuthenticateAsServer(certificate, false, protocol, false); ++ if (_clientCallback == null) ++ { ++ sslStream = new SslStream(networkStream, false); ++ sslStream.AuthenticateAsServer(certificate, false, protocol, false); ++ } ++ else ++ { ++ sslStream = new SslStream(networkStream, false, new RemoteCertificateValidationCallback(_clientCallback)); ++ sslStream.AuthenticateAsServer(certificate, true, protocol, false); ++ } ++ + return CreateContext(true, remoteEndPoint, sslStream, socket); + } + catch (IOException err) +diff -Nur HttpServer.orig/IHttpClientContext.cs HttpServer/IHttpClientContext.cs +--- HttpServer.orig/IHttpClientContext.cs 2016-07-23 11:49:36.245240228 +0900 ++++ HttpServer/IHttpClientContext.cs 2016-07-23 11:52:33.369949910 +0900 +@@ -9,6 +9,11 @@ + /// + public interface IHttpClientContext + { ++ // ++ // ++ // by Fumi.Iseki ++ string SSLCommonName { get; } ++ + /// + /// Using SSL or other encryption method. + /// diff --git a/old/HttpServer_OpenSim/source/HttpClientContext.cs b/old/HttpServer_OpenSim/source/HttpClientContext.cs new file mode 100644 index 0000000..b0cf212 --- /dev/null +++ b/old/HttpServer_OpenSim/source/HttpClientContext.cs @@ -0,0 +1,599 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using HttpServer.Exceptions; +using HttpServer.Parser; + +// by Fumi.Iseki +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; + +namespace HttpServer +{ + /// + /// Contains a connection to a browser/client. + /// + /// + /// Remember to after you have hooked the event. + /// + /// TODO: Maybe this class should be broken up into HttpClientChannel and HttpClientContext? + public class HttpClientContext : IHttpClientContext ,IDisposable + { + private readonly byte[] _buffer; + private int _bytesLeft; + private ILogWriter _log; + private readonly IHttpRequestParser _parser; + private readonly int _bufferSize; + private IHttpRequest _currentRequest; + private Socket _sock; + + public bool Available = true; + private bool _endWhenDone = false; + public bool StreamPassedOff = false; + public int MonitorStartMS = 0; + public int MonitorKeepaliveMS = 0; + public bool TriggerKeepalive = false; + public int TimeoutFirstLine = 70000; // 70 seconds + public int TimeoutRequestReceived = 180000; // 180 seconds + + // The difference between this and request received is on POST more time is needed before we get the full request. + public int TimeoutFullRequestProcessed = 1200000; // 20 minutes + public int TimeoutKeepAlive = 400000; // 400 seconds before keepalive timeout + + public bool FirstRequestLineReceived; + public bool FullRequestReceived; + public bool FullRequestProcessed; + + public bool StopMonitoring; + /// + /// This context have been cleaned, which means that it can be reused. + /// + public event EventHandler Cleaned = delegate { }; + + /// + /// Context have been started (a new client have connected) + /// + public event EventHandler Started = delegate { }; + + /// + /// Initializes a new instance of the class. + /// + /// true if the connection is secured (SSL/TLS) + /// client that connected. + /// Stream used for communication + /// Used to create a . + /// Size of buffer to use when reading data. Must be at least 4096 bytes. + /// If fails + /// Stream must be writable and readable. + public HttpClientContext(bool secured, IPEndPoint remoteEndPoint, + Stream stream, IRequestParserFactory parserFactory, int bufferSize, Socket sock) + { + Check.Require(remoteEndPoint, "remoteEndPoint"); + Check.NotEmpty(remoteEndPoint.Address.ToString(), "remoteEndPoint.Address"); + Check.Require(stream, "stream"); + Check.Require(parserFactory, "parser"); + Check.Min(4096, bufferSize, "bufferSize"); + Check.Require(sock, "socket"); + + if (!stream.CanWrite || !stream.CanRead) + throw new ArgumentException("Stream must be writable and readable."); + + _bufferSize = bufferSize; + RemoteAddress = remoteEndPoint.Address.ToString(); + RemotePort = remoteEndPoint.Port.ToString(); + _log = NullLogWriter.Instance; + _parser = parserFactory.CreateParser(_log); + _parser.RequestCompleted += OnRequestCompleted; + _parser.RequestLineReceived += OnRequestLine; + _parser.HeaderReceived += OnHeaderReceived; + _parser.BodyBytesReceived += OnBodyBytesReceived; + _currentRequest = new HttpRequest(); + Available = false; + IsSecured = secured; + _stream = stream; + _sock = sock; + _buffer = new byte[bufferSize]; + + // by Fumi.Iseki + SSLCommonName = ""; + if (secured) + { + SslStream _ssl = (SslStream)_stream; + X509Certificate _cert1 = _ssl.RemoteCertificate; + if (_cert1 != null) + { + X509Certificate2 _cert2 = new X509Certificate2(_cert1); + if (_cert2 != null) SSLCommonName = _cert2.GetNameInfo(X509NameType.SimpleName, false); + } + } + } + + public bool EndWhenDone + { + get { return _endWhenDone; } + set { _endWhenDone = value;} + } + + /// + /// Process incoming body bytes. + /// + /// + /// Bytes + protected virtual void OnBodyBytesReceived(object sender, BodyEventArgs e) + { + _currentRequest.AddToBody(e.Buffer, e.Offset, e.Count); + } + + /// + /// + /// + /// + /// + protected virtual void OnHeaderReceived(object sender, HeaderEventArgs e) + { + if (string.Compare(e.Name, "expect", true) == 0 && e.Value.Contains("100-continue")) + { + Respond("HTTP/1.0", HttpStatusCode.Continue, "Please continue mate."); + } + + _currentRequest.AddHeader(e.Name, e.Value); + } + + private void OnRequestLine(object sender, RequestLineEventArgs e) + { + _currentRequest.Method = e.HttpMethod; + _currentRequest.HttpVersion = e.HttpVersion; + _currentRequest.UriPath = e.UriPath; + FirstRequestLineReceived = true; + } + + /// + /// Overload to specify own type. + /// + /// + /// Must be specified before the context is being used. + /// + protected IHttpRequest CurrentRequest + { get { return _currentRequest; } set { _currentRequest = value; } } + + /// + /// Start reading content. + /// + /// + /// Make sure to call base.Start() if you override this method. + /// + public virtual void Start() + { + try + { + _stream.BeginRead(_buffer, 0, _bufferSize, OnReceive, null); + } + catch (IOException err) + { + LogWriter.Write(this, LogPrio.Debug, err.ToString()); + } + + Started(this, EventArgs.Empty); + } + + /// + /// Clean up context. + /// + /// + /// Make sure to call base.Cleanup() if you override the method. + /// + public virtual void Cleanup() + { + if (Stream == null) + return; + if (StreamPassedOff) + return; + _sock = null; + + Stream.Dispose(); + Stream = null; + _currentRequest.Clear(); + _bytesLeft = 0; + + FirstRequestLineReceived = false; + FullRequestReceived = false; + FullRequestProcessed = false; + MonitorStartMS = 0; + StopMonitoring = false; + MonitorKeepaliveMS = 0; + TriggerKeepalive = false; + + Cleaned(this, EventArgs.Empty); + _parser.Clear(); + } + + public void Close() + { + if (StreamPassedOff) + return; + Cleanup(); + Available = true; + } + + /// + /// Using SSL or other encryption method. + /// + [Obsolete("Use IsSecured instead.")] + public bool Secured + { + get { return IsSecured; } + } + + /// + /// Using SSL or other encryption method. + /// + public bool IsSecured { get; internal set; } + + // + // + // by Fumi.Iseki + public string SSLCommonName { get; internal set; } + + /// + /// Specify which logger to use. + /// + public ILogWriter LogWriter + { + get { return _log; } + set + { + _log = value ?? NullLogWriter.Instance; + _parser.LogWriter = _log; + } + } + + private Stream _stream; + + /// + /// Gets or sets the network stream. + /// + internal Stream Stream + { + get { return _stream; } + set { _stream = value; } + } + + /// + /// Gets or sets IP address that the client connected from. + /// + internal string RemoteAddress { get; set; } + + /// + /// Gets or sets port that the client connected from. + /// + internal string RemotePort { get; set; } + + /// + /// Disconnect from client + /// + /// error to report in the event. + public void Disconnect(SocketError error) + { + // disconnect may not throw any exceptions + try + { + //_sock.Disconnect(true); + if (error == SocketError.Success) + { + if (Stream is ReusableSocketNetworkStream) + ((NetworkStream)Stream).Flush(); + if (_currentRequest.Connection == ConnectionType.Close) + _sock = null; + + } + if (error == SocketError.TimedOut) + { + try + { + if (Stream != null) + Stream.Close(); + _sock = null; + } + catch {} // Best Try + } + //Stream.Close(); + Disconnected(this, new DisconnectedEventArgs(error)); + } + catch (Exception err) + { + LogWriter.Write(this, LogPrio.Error, "Disconnect threw an exception: " + err); + } + } + + private void OnReceive(IAsyncResult ar) + { + try + { + int bytesRead = 0; + + try + { + bytesRead = Stream.EndRead(ar); + } + catch (NullReferenceException) + { + Disconnect(SocketError.ConnectionReset); + return; + } + + if (bytesRead == 0) + { + Disconnect(SocketError.ConnectionReset); + return; + } + _bytesLeft += bytesRead; + if (_bytesLeft > _buffer.Length) + { +#if DEBUG + throw new BadRequestException("Too large HTTP header: " + Encoding.UTF8.GetString(_buffer, 0, bytesRead)); +#else + throw new BadRequestException("Too large HTTP header: " + _bytesLeft); +#endif + } + +#if DEBUG +#pragma warning disable 219 + string temp = Encoding.ASCII.GetString(_buffer, 0, _bytesLeft); + LogWriter.Write(this, LogPrio.Trace, "Received: " + temp); +#pragma warning restore 219 +#endif + int offset = _parser.Parse(_buffer, 0, _bytesLeft); + if (Stream == null) + return; // "Connection: Close" in effect. + + // try again to see if we can parse another message (check parser to see if it is looking for a new message) + int oldOffset = offset; + while (_parser.CurrentState == RequestParserState.FirstLine && offset != 0 && _bytesLeft - offset > 0) + { +#if DEBUG + temp = Encoding.ASCII.GetString(_buffer, offset, _bytesLeft - offset); + LogWriter.Write(this, LogPrio.Trace, "Processing: " + temp); +#endif + offset = _parser.Parse(_buffer, offset, _bytesLeft - offset); + if (Stream == null) + return; // "Connection: Close" in effect. + } + + // need to be able to move prev bytes, so restore offset. + if (offset == 0) + offset = oldOffset; + + // copy unused bytes to the beginning of the array + if (offset > 0 && _bytesLeft > offset) + Buffer.BlockCopy(_buffer, offset, _buffer, 0, _bytesLeft - offset); + + _bytesLeft -= offset; + if (Stream != null && Stream.CanRead && !StreamPassedOff) + Stream.BeginRead(_buffer, _bytesLeft, _buffer.Length - _bytesLeft, OnReceive, null); + else + { + _log.Write(this, LogPrio.Warning, "Could not read any more from the socket."); + Disconnect(SocketError.Success); + } + } + catch (BadRequestException err) + { + LogWriter.Write(this, LogPrio.Warning, "Bad request, responding with it. Error: " + err); + try + { + Respond("HTTP/1.0", HttpStatusCode.BadRequest, err.Message); + } + catch(Exception err2) + { + LogWriter.Write(this, LogPrio.Fatal, "Failed to reply to a bad request. " + err2); + } + Disconnect(SocketError.NoRecovery); + } + catch (IOException err) + { + LogWriter.Write(this, LogPrio.Debug, "Failed to end receive: " + err.Message); + if (err.InnerException is SocketException) + Disconnect((SocketError) ((SocketException) err.InnerException).ErrorCode); + else + Disconnect(SocketError.ConnectionReset); + } + catch (ObjectDisposedException err) + { + LogWriter.Write(this, LogPrio.Debug, "Failed to end receive : " + err.Message); + Disconnect(SocketError.NotSocket); + } + catch (NullReferenceException err) + { + LogWriter.Write(this, LogPrio.Debug, "Failed to end receive : NullRef: " + err.Message); + Disconnect(SocketError.NoRecovery); + } + catch (Exception err) + { + LogWriter.Write(this, LogPrio.Debug, "Failed to end receive: " + err.Message); + Disconnect(SocketError.NoRecovery); + } + } + + private void OnRequestCompleted(object source, EventArgs args) + { + _currentRequest.AddHeader("remote_addr", RemoteAddress); + _currentRequest.AddHeader("remote_port", RemotePort); + + // load cookies if they exist + RequestCookies cookies = _currentRequest.Headers["cookie"] != null + ? new RequestCookies(_currentRequest.Headers["cookie"]) + : new RequestCookies(String.Empty); + _currentRequest.SetCookies(cookies); + + _currentRequest.Body.Seek(0, SeekOrigin.Begin); + RequestReceived(this, new RequestEventArgs(_currentRequest)); + + FullRequestReceived = true; + + if (_currentRequest.Connection == ConnectionType.Close) + _sock = null; + else + TriggerKeepalive = true; + + if (!StreamPassedOff) + _currentRequest.Clear(); + } + + /// + /// Send a response. + /// + /// Either or + /// HTTP status code + /// reason for the status code. + /// HTML body contents, can be null or empty. + /// A content type to return the body as, i.e. 'text/html' or 'text/plain', defaults to 'text/html' if null or empty + /// If is invalid. + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body, string contentType) + { + if (string.IsNullOrEmpty(contentType)) + contentType = "text/html"; + + if (string.IsNullOrEmpty(httpVersion) || !httpVersion.StartsWith("HTTP/1")) + throw new ArgumentException("Invalid HTTP version"); + + if (string.IsNullOrEmpty(reason)) + reason = statusCode.ToString(); + + string response = string.IsNullOrEmpty(body) + ? httpVersion + " " + (int) statusCode + " " + reason + "\r\n\r\n" + : string.Format("{0} {1} {2}\r\nContent-Type: {5}\r\nContent-Length: {3}\r\n\r\n{4}", + httpVersion, (int) statusCode, reason ?? statusCode.ToString(), + body.Length, body, contentType); + byte[] buffer = Encoding.ASCII.GetBytes(response); + + Send(buffer); + if (_currentRequest.Connection == ConnectionType.Close) + FullRequestProcessed = true; + + } + + /// + /// Send a response. + /// + /// Either or + /// HTTP status code + /// reason for the status code. + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason) + { + Respond(httpVersion, statusCode, reason, null, null); + } + + /// + /// Send a response. + /// + /// + public void Respond(string body) + { + if (body == null) + throw new ArgumentNullException("body"); + Respond("HTTP/1.1", HttpStatusCode.OK, HttpStatusCode.OK.ToString(), body, null); + } + + /// + /// send a whole buffer + /// + /// buffer to send + /// + public void Send(byte[] buffer) + { + if (buffer == null) + throw new ArgumentNullException("buffer"); + Send(buffer, 0, buffer.Length); + } + + /// + /// Send data using the stream + /// + /// Contains data to send + /// Start position in buffer + /// number of bytes to send + /// + /// + public void Send(byte[] buffer, int offset, int size) + { + + if (offset + size > buffer.Length) + throw new ArgumentOutOfRangeException("offset", offset, "offset + size is beyond end of buffer."); + + if (Stream != null && Stream.CanWrite) + { + try + { + Stream.Write(buffer, offset, size); + } + catch (IOException) + { + + } + } + + } + + /// + /// The context have been disconnected. + /// + /// + /// Event can be used to clean up a context, or to reuse it. + /// + public event EventHandler Disconnected = delegate{}; + /// + /// A request have been received in the context. + /// + public event EventHandler RequestReceived = delegate{}; + + public HTTPNetworkContext GiveMeTheNetworkStreamIKnowWhatImDoing() + { + _endWhenDone = true; + StreamPassedOff = true; + _parser.RequestCompleted -= OnRequestCompleted; + _parser.RequestLineReceived -= OnRequestLine; + _parser.HeaderReceived -= OnHeaderReceived; + _parser.BodyBytesReceived -= OnBodyBytesReceived; + _parser.Clear(); + + return new HTTPNetworkContext() { Socket = _sock ,Stream = _stream as NetworkStream}; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool unmanaged) + { + if (unmanaged) + { + if (Stream != null) + { + try + { + if (Stream.CanWrite) + Stream.Flush(); + + Cleanup(); + + } + catch (IOException) + { + + } + } + + } + + } + + ~HttpClientContext() + { + Dispose(); + } + } +} \ No newline at end of file diff --git a/old/HttpServer_OpenSim/source/HttpContextFactory.cs b/old/HttpServer_OpenSim/source/HttpContextFactory.cs new file mode 100644 index 0000000..e2b0765 --- /dev/null +++ b/old/HttpServer_OpenSim/source/HttpContextFactory.cs @@ -0,0 +1,379 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; + +namespace HttpServer +{ + /// + /// Used to create and reuse contexts. + /// + public class HttpContextFactory : IHttpContextFactory + { + private readonly int _bufferSize; + private readonly Queue _contextQueue = new Queue(); + private readonly IRequestParserFactory _factory; + private readonly ILogWriter _logWriter; + private readonly ContextTimeoutManager _contextTimeoutManager; + + // by Fumi.Iseki + public static RemoteCertificateValidationCallback ClientCertificateValidationCallback = null; + private RemoteCertificateValidationCallback _clientCallback = null; + + /// + /// Initializes a new instance of the class. + /// + /// The writer. + /// Amount of bytes to read from the incoming socket stream. + /// Used to create a request parser. + public HttpContextFactory(ILogWriter writer, int bufferSize, IRequestParserFactory factory) + { + _logWriter = writer; + _bufferSize = bufferSize; + _factory = factory; + _contextTimeoutManager = new ContextTimeoutManager(ContextTimeoutManager.MonitorType.Thread); + + // by Fumi.Iseki + if (ClientCertificateValidationCallback != null) + { + _clientCallback = ClientCertificateValidationCallback; + ClientCertificateValidationCallback = null; + } + } + + /// + /// True if detailed trace logs should be written. + /// + public bool UseTraceLogs { get; set; } + + /// + /// Create a new context. + /// + /// true if socket is running HTTPS. + /// Client that connected + /// Network/SSL stream. + /// A context. + protected HttpClientContext CreateContext(bool isSecured, IPEndPoint endPoint, Stream stream, Socket sock) + { + HttpClientContext context; + lock (_contextQueue) + { + if (_contextQueue.Count > 0) + { + context = _contextQueue.Dequeue(); + if (!context.Available) + { + context = CreateNewContext(isSecured, endPoint, stream, sock); + context.Disconnected += OnFreeContext; + context.RequestReceived += OnRequestReceived; + context.EndWhenDone = true; + + } + } + else + { + context = CreateNewContext(isSecured, endPoint, stream, sock); + context.Disconnected += OnFreeContext; + context.RequestReceived += OnRequestReceived; + } + } + + context.Stream = stream; + context.IsSecured = isSecured; + context.RemotePort = endPoint.Port.ToString(); + context.RemoteAddress = endPoint.Address.ToString(); + _contextTimeoutManager.StartMonitoringContext(context); + context.Start(); + + return context; + } + + /// + /// Create a new context. + /// + /// true if HTTPS is used. + /// Remote client + /// Network stream, uses . + /// A new context (always). + protected virtual HttpClientContext CreateNewContext(bool isSecured, IPEndPoint endPoint, Stream stream, Socket sock) + { + return new HttpClientContext(isSecured, endPoint, stream, _factory, _bufferSize, sock); + } + + private void OnRequestReceived(object sender, RequestEventArgs e) + { + RequestReceived(sender, e); + } + + private void OnFreeContext(object sender, DisconnectedEventArgs e) + { + var imp = (HttpClientContext) sender; + imp.Cleanup(); + + if (!imp.EndWhenDone) + { + lock (_contextQueue) + _contextQueue.Enqueue(imp); + } + else + { + + imp.Close(); + } + } + + + #region IHttpContextFactory Members + + /// + /// Create a secure . + /// + /// Client socket (accepted by the ). + /// HTTPS certificate to use. + /// Kind of HTTPS protocol. Usually TLS or SSL. + /// + /// A created . + /// + public IHttpClientContext CreateSecureContext(Socket socket, X509Certificate certificate, SslProtocols protocol) + { + var networkStream = new ReusableSocketNetworkStream(socket, true); + var remoteEndPoint = (IPEndPoint) socket.RemoteEndPoint; + + // by Fumi.Iseki + //var sslStream = new SslStream(networkStream, false); + SslStream sslStream = null; + try + { + //TODO: this may fail + // by Fumi.Iseki + //sslStream.AuthenticateAsServer(certificate, false, protocol, false); + if (_clientCallback == null) + { + sslStream = new SslStream(networkStream, false); + sslStream.AuthenticateAsServer(certificate, false, protocol, false); + } + else + { + sslStream = new SslStream(networkStream, false, new RemoteCertificateValidationCallback(_clientCallback)); + sslStream.AuthenticateAsServer(certificate, true, protocol, false); + } + + return CreateContext(true, remoteEndPoint, sslStream, socket); + } + catch (IOException err) + { + if (UseTraceLogs) + _logWriter.Write(this, LogPrio.Trace, err.Message); + } + catch (ObjectDisposedException err) + { + if (UseTraceLogs) + _logWriter.Write(this, LogPrio.Trace, err.Message); + } + + return null; + } + + + /// + /// A request have been received from one of the contexts. + /// + public event EventHandler RequestReceived = delegate{}; + + /// + /// Creates a that handles a connected client. + /// + /// Client socket (accepted by the ). + /// + /// A creates . + /// + public IHttpClientContext CreateContext(Socket socket) + { + var networkStream = new ReusableSocketNetworkStream(socket, true); + var remoteEndPoint = (IPEndPoint) socket.RemoteEndPoint; + return CreateContext(false, remoteEndPoint, networkStream, socket); + } + + #endregion + + /// + /// Server is shutting down so shut down the factory + /// + public void Shutdown() + { + _contextTimeoutManager.StopMonitoring(); + } + } + + /// + /// Custom network stream to mark sockets as reusable when disposing the stream. + /// + internal class ReusableSocketNetworkStream : NetworkStream + { + private bool disposed = false; + /// + /// Creates a new instance of the class for the specified . + /// + /// + /// The that the will use to send and receive data. + /// + /// + /// The parameter is null. + /// + /// + /// The parameter is not connected. + /// -or- + /// The property of the parameter is not . + /// -or- + /// The parameter is in a nonblocking state. + /// + public ReusableSocketNetworkStream(Socket socket) + : base(socket) + { + } + + /// + /// Initializes a new instance of the class for the specified with the specified ownership. + /// + /// + /// The that the will use to send and receive data. + /// + /// + /// Set to true to indicate that the will take ownership of the ; otherwise, false. + /// + /// + /// The parameter is null. + /// + /// + /// The parameter is not connected. + /// -or- + /// the value of the property of the parameter is not . + /// -or- + /// the parameter is in a nonblocking state. + /// + public ReusableSocketNetworkStream(Socket socket, bool ownsSocket) + : base(socket, ownsSocket) + { + } + + /// + /// Creates a new instance of the class for the specified with the specified access rights. + /// + /// + /// The that the will use to send and receive data. + /// + /// + /// A bitwise combination of the values that specify the type of access given to the over the provided . + /// + /// + /// The parameter is null. + /// + /// + /// The parameter is not connected. + /// -or- + /// the property of the parameter is not . + /// -or- + /// the parameter is in a nonblocking state. + /// + public ReusableSocketNetworkStream(Socket socket, FileAccess access) + : base(socket, access) + { + } + + /// + /// Creates a new instance of the class for the specified with the specified access rights and the specified ownership. + /// + /// + /// The that the will use to send and receive data. + /// + /// + /// A bitwise combination of the values that specifies the type of access given to the over the provided . + /// + /// + /// Set to true to indicate that the will take ownership of the ; otherwise, false. + /// + /// + /// The parameter is null. + /// + /// + /// The parameter is not connected. + /// -or- + /// The property of the parameter is not . + /// -or- + /// The parameter is in a nonblocking state. + /// + public ReusableSocketNetworkStream(Socket socket, FileAccess access, bool ownsSocket) + : base(socket, access, ownsSocket) + { + } + + /// + /// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. + /// + public override void Close() + { + + if (Socket != null && Socket.Connected) + Socket.Close(); //TODO: Maybe use Disconnect with reuseSocket=true? I tried but it took forever. + base.Close(); + + } + + /// + /// Releases the unmanaged resources used by the and optionally releases the managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected override void Dispose(bool disposing) + { + + try + { + if (!disposed) + { + disposed = true; + if (Socket != null && Socket.Connected) + Socket.Disconnect(true); + + } + base.Dispose(disposing); + } + catch { } // Best effort, ignore fails + } + } + + /// + /// Used to create es. + /// + public interface IHttpContextFactory + { + /// + /// Creates a that handles a connected client. + /// + /// Client socket (accepted by the ). + /// A creates . + IHttpClientContext CreateContext(Socket socket); + + /// + /// Create a secure . + /// + /// Client socket (accepted by the ). + /// HTTPS certificate to use. + /// Kind of HTTPS protocol. Usually TLS or SSL. + /// A created . + IHttpClientContext CreateSecureContext(Socket socket, X509Certificate certificate, SslProtocols protocol); + + /// + /// A request have been received from one of the contexts. + /// + event EventHandler RequestReceived; + + /// + /// Server is shutting down so shut down the factory + /// + void Shutdown(); + } +} diff --git a/old/HttpServer_OpenSim/source/IHttpClientContext.cs b/old/HttpServer_OpenSim/source/IHttpClientContext.cs new file mode 100644 index 0000000..ff1d0f1 --- /dev/null +++ b/old/HttpServer_OpenSim/source/IHttpClientContext.cs @@ -0,0 +1,148 @@ +using System; +using System.Net; +using System.Net.Sockets; + +namespace HttpServer +{ + /// + /// Contains a connection to a browser/client. + /// + public interface IHttpClientContext + { + // + // + // by Fumi.Iseki + string SSLCommonName { get; } + + /// + /// Using SSL or other encryption method. + /// + [Obsolete("Use IsSecured instead.")] + bool Secured { get; } + + /// + /// Using SSL or other encryption method. + /// + bool IsSecured { get; } + + /// + /// Disconnect from client + /// + /// error to report in the event. + void Disconnect(SocketError error); + + /// + /// Send a response. + /// + /// Either or + /// HTTP status code + /// reason for the status code. + /// HTML body contents, can be null or empty. + /// A content type to return the body as, i.e. 'text/html' or 'text/plain', defaults to 'text/html' if null or empty + /// If is invalid. + void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body, string contentType); + + /// + /// Send a response. + /// + /// Either or + /// HTTP status code + /// reason for the status code. + void Respond(string httpVersion, HttpStatusCode statusCode, string reason); + + /// + /// Send a response. + /// + /// + void Respond(string body); + + /// + /// send a whole buffer + /// + /// buffer to send + /// + void Send(byte[] buffer); + + /// + /// Send data using the stream + /// + /// Contains data to send + /// Start position in buffer + /// number of bytes to send + /// + /// + void Send(byte[] buffer, int offset, int size); + + /// + /// Closes the streams and disposes of the unmanaged resources + /// + void Close(); + + bool EndWhenDone { get; set; } + + /// + /// The context have been disconnected. + /// + /// + /// Event can be used to clean up a context, or to reuse it. + /// + event EventHandler Disconnected; + + /// + /// A request have been received in the context. + /// + event EventHandler RequestReceived; + + HTTPNetworkContext GiveMeTheNetworkStreamIKnowWhatImDoing(); + } + public class HTTPNetworkContext + { + public NetworkStream Stream; + public Socket Socket; + } + + /// + /// A have been disconnected. + /// + public class DisconnectedEventArgs : EventArgs + { + /// + /// Gets reason to why client disconnected. + /// + public SocketError Error { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// Reason to disconnection. + public DisconnectedEventArgs(SocketError error) + { + Check.Require(error, "error"); + + Error = error; + } + } + + /// + /// + /// + public class RequestEventArgs : EventArgs + { + /// + /// Gets received request. + /// + public IHttpRequest Request { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The request. + public RequestEventArgs(IHttpRequest request) + { + Check.Require(request, "request"); + + Request = request; + } + } + +} diff --git a/patch/HttpServer-20130201.patch b/patch/HttpServer-20130201.patch new file mode 100644 index 0000000..71e930b --- /dev/null +++ b/patch/HttpServer-20130201.patch @@ -0,0 +1,109 @@ +diff -Nur HttpServer-/HttpClientContext.cs HttpServer/HttpClientContext.cs +--- HttpServer-/HttpClientContext.cs 2013-02-01 18:39:44.868009882 +0900 ++++ HttpServer/HttpClientContext.cs 2013-02-01 18:45:07.740259535 +0900 +@@ -6,6 +6,9 @@ + using HttpServer.Exceptions; + using HttpServer.Parser; + ++using System.Net.Security; ++using System.Security.Cryptography.X509Certificates; ++ + namespace HttpServer + { + /// +@@ -77,6 +80,18 @@ + + _buffer = new byte[bufferSize]; + ++ // by Fumi.Iseki ++ SSLCommonName = ""; ++ if (secured) ++ { ++ SslStream _ssl = (SslStream)_stream; ++ X509Certificate _cert1 = _ssl.RemoteCertificate; ++ if (_cert1 != null) ++ { ++ X509Certificate2 _cert2 = new X509Certificate2(_cert1); ++ if (_cert2 != null) SSLCommonName = _cert2.GetNameInfo(X509NameType.SimpleName, false); ++ } ++ } + } + + public bool EndWhenDone +@@ -185,6 +200,11 @@ + /// + public bool IsSecured { get; internal set; } + ++ // ++ // ++ // by Fumi.Iseki ++ public string SSLCommonName { get; internal set; } ++ + /// + /// Specify which logger to use. + /// +diff -Nur HttpServer-/HttpContextFactory.cs HttpServer/HttpContextFactory.cs +--- HttpServer-/HttpContextFactory.cs 2013-02-01 18:39:44.868009882 +0900 ++++ HttpServer/HttpContextFactory.cs 2013-02-01 18:44:42.408133328 +0900 +@@ -19,6 +19,10 @@ + private readonly IRequestParserFactory _factory; + private readonly ILogWriter _logWriter; + ++ // by Fumi.Iseki ++ public static RemoteCertificateValidationCallback ClientCertificateValidationCallback = null; ++ private RemoteCertificateValidationCallback _clientCallback = null; ++ + /// + /// Initializes a new instance of the class. + /// +@@ -30,6 +34,13 @@ + _logWriter = writer; + _bufferSize = bufferSize; + _factory = factory; ++ ++ // by Fumi.Iseki ++ if (ClientCertificateValidationCallback != null) ++ { ++ _clientCallback = ClientCertificateValidationCallback; ++ ClientCertificateValidationCallback = null; ++ } + } + + /// +@@ -127,11 +138,19 @@ + var networkStream = new ReusableSocketNetworkStream(socket, true); + var remoteEndPoint = (IPEndPoint) socket.RemoteEndPoint; + +- var sslStream = new SslStream(networkStream, false); ++ // by Fumi.Iseki ++ var sslStream = new SslStream(networkStream, false, new RemoteCertificateValidationCallback(_clientCallback)); + try + { + //TODO: this may fail +- sslStream.AuthenticateAsServer(certificate, false, protocol, false); ++ if (_clientCallback == null) // by Fumi.Iseki ++ { ++ sslStream.AuthenticateAsServer(certificate, false, protocol, false); ++ } ++ else ++ { ++ sslStream.AuthenticateAsServer(certificate, true, protocol, false); ++ } + return CreateContext(true, remoteEndPoint, sslStream, socket); + } + catch (IOException err) +diff -Nur HttpServer-/IHttpClientContext.cs HttpServer/IHttpClientContext.cs +--- HttpServer-/IHttpClientContext.cs 2013-02-01 18:39:44.872009815 +0900 ++++ HttpServer/IHttpClientContext.cs 2013-02-01 18:44:05.196259567 +0900 +@@ -9,6 +9,11 @@ + /// + public interface IHttpClientContext + { ++ // ++ // ++ // by Fumi.Iseki ++ string SSLCommonName { get; } ++ + /// + /// Using SSL or other encryption method. + /// diff --git a/patch/HttpServer-20131020.patch b/patch/HttpServer-20131020.patch new file mode 100644 index 0000000..ec140a9 --- /dev/null +++ b/patch/HttpServer-20131020.patch @@ -0,0 +1,112 @@ +diff -Nur HttpServer-/HttpClientContext.cs HttpServer/HttpClientContext.cs +--- HttpServer-/HttpClientContext.cs 2013-10-21 13:08:30.861101971 +0900 ++++ HttpServer/HttpClientContext.cs 2013-10-21 15:17:40.493510079 +0900 +@@ -6,6 +6,10 @@ + using HttpServer.Exceptions; + using HttpServer.Parser; + ++// by Fumi.Iseki ++using System.Net.Security; ++using System.Security.Cryptography.X509Certificates; ++ + namespace HttpServer + { + /// +@@ -92,6 +96,18 @@ + _sock = sock; + _buffer = new byte[bufferSize]; + ++ // by Fumi.Iseki ++ SSLCommonName = ""; ++ if (secured) ++ { ++ SslStream _ssl = (SslStream)_stream; ++ X509Certificate _cert1 = _ssl.RemoteCertificate; ++ if (_cert1 != null) ++ { ++ X509Certificate2 _cert2 = new X509Certificate2(_cert1); ++ if (_cert2 != null) SSLCommonName = _cert2.GetNameInfo(X509NameType.SimpleName, false); ++ } ++ } + } + + public bool EndWhenDone +@@ -215,6 +231,11 @@ + /// + public bool IsSecured { get; internal set; } + ++ // ++ // ++ // by Fumi.Iseki ++ public string SSLCommonName { get; internal set; } ++ + /// + /// Specify which logger to use. + /// +diff -Nur HttpServer-/HttpContextFactory.cs HttpServer/HttpContextFactory.cs +--- HttpServer-/HttpContextFactory.cs 2013-10-21 13:08:30.861101971 +0900 ++++ HttpServer/HttpContextFactory.cs 2013-10-21 15:19:36.431821703 +0900 +@@ -20,6 +20,10 @@ + private readonly ILogWriter _logWriter; + private readonly ContextTimeoutManager _contextTimeoutManager; + ++ // by Fumi.Iseki ++ public static RemoteCertificateValidationCallback ClientCertificateValidationCallback = null; ++ private RemoteCertificateValidationCallback _clientCallback = null; ++ + /// + /// Initializes a new instance of the class. + /// +@@ -32,6 +36,13 @@ + _bufferSize = bufferSize; + _factory = factory; + _contextTimeoutManager = new ContextTimeoutManager(ContextTimeoutManager.MonitorType.Thread); ++ ++ // by Fumi.Iseki ++ if (ClientCertificateValidationCallback != null) ++ { ++ _clientCallback = ClientCertificateValidationCallback; ++ ClientCertificateValidationCallback = null; ++ } + } + + /// +@@ -132,11 +143,21 @@ + var networkStream = new ReusableSocketNetworkStream(socket, true); + var remoteEndPoint = (IPEndPoint) socket.RemoteEndPoint; + +- var sslStream = new SslStream(networkStream, false); ++ // by Fumi.Iseki ++ SslStream sslStream = null; + try + { + //TODO: this may fail +- sslStream.AuthenticateAsServer(certificate, false, protocol, false); ++ if (_clientCallback == null) // by Fumi.Iseki ++ { ++ sslStream = new SslStream(networkStream, false); ++ sslStream.AuthenticateAsServer(certificate, false, protocol, false); ++ } ++ else ++ { ++ sslStream = new SslStream(networkStream, false, new RemoteCertificateValidationCallback(_clientCallback)); ++ sslStream.AuthenticateAsServer(certificate, true, protocol, false); ++ } + return CreateContext(true, remoteEndPoint, sslStream, socket); + } + catch (IOException err) +diff -Nur HttpServer-/IHttpClientContext.cs HttpServer/IHttpClientContext.cs +--- HttpServer-/IHttpClientContext.cs 2013-10-21 13:08:30.865101913 +0900 ++++ HttpServer/IHttpClientContext.cs 2013-10-21 15:17:40.518509254 +0900 +@@ -9,6 +9,11 @@ + /// + public interface IHttpClientContext + { ++ // ++ // ++ // by Fumi.Iseki ++ string SSLCommonName { get; } ++ + /// + /// Using SSL or other encryption method. + /// diff --git a/patch/HttpServer-20140307.patch b/patch/HttpServer-20140307.patch new file mode 100644 index 0000000..1e34fba --- /dev/null +++ b/patch/HttpServer-20140307.patch @@ -0,0 +1,116 @@ +diff -Nur HttpServer-/HttpClientContext.cs HttpServer/HttpClientContext.cs +--- HttpServer-/HttpClientContext.cs 2014-03-06 16:53:10.802499259 +0900 ++++ HttpServer/HttpClientContext.cs 2014-03-07 13:33:26.265146166 +0900 +@@ -6,6 +6,10 @@ + using HttpServer.Exceptions; + using HttpServer.Parser; + ++// by Fumi.Iseki ++using System.Net.Security; ++using System.Security.Cryptography.X509Certificates; ++ + namespace HttpServer + { + /// +@@ -92,6 +96,18 @@ + _sock = sock; + _buffer = new byte[bufferSize]; + ++ // by Fumi.Iseki ++ SSLCommonName = ""; ++ if (secured) ++ { ++ SslStream _ssl = (SslStream)_stream; ++ X509Certificate _cert1 = _ssl.RemoteCertificate; ++ if (_cert1 != null) ++ { ++ X509Certificate2 _cert2 = new X509Certificate2(_cert1); ++ if (_cert2 != null) SSLCommonName = _cert2.GetNameInfo(X509NameType.SimpleName, false); ++ } ++ } + } + + public bool EndWhenDone +@@ -215,6 +231,11 @@ + /// + public bool IsSecured { get; internal set; } + ++ // ++ // ++ // by Fumi.Iseki ++ public string SSLCommonName { get; internal set; } ++ + /// + /// Specify which logger to use. + /// +diff -Nur HttpServer-/HttpContextFactory.cs HttpServer/HttpContextFactory.cs +--- HttpServer-/HttpContextFactory.cs 2014-03-06 16:53:10.802499259 +0900 ++++ HttpServer/HttpContextFactory.cs 2014-03-07 13:39:25.522879915 +0900 +@@ -20,6 +20,10 @@ + private readonly ILogWriter _logWriter; + private readonly ContextTimeoutManager _contextTimeoutManager; + ++ // by Fumi.Iseki ++ public static RemoteCertificateValidationCallback ClientCertificateValidationCallback = null; ++ private RemoteCertificateValidationCallback _clientCallback = null; ++ + /// + /// Initializes a new instance of the class. + /// +@@ -32,6 +36,13 @@ + _bufferSize = bufferSize; + _factory = factory; + _contextTimeoutManager = new ContextTimeoutManager(ContextTimeoutManager.MonitorType.Thread); ++ ++ // by Fumi.Iseki ++ if (ClientCertificateValidationCallback != null) ++ { ++ _clientCallback = ClientCertificateValidationCallback; ++ ClientCertificateValidationCallback = null; ++ } + } + + /// +@@ -132,11 +143,25 @@ + var networkStream = new ReusableSocketNetworkStream(socket, true); + var remoteEndPoint = (IPEndPoint) socket.RemoteEndPoint; + +- var sslStream = new SslStream(networkStream, false); ++ // by Fumi.Iseki ++ //var sslStream = new SslStream(networkStream, false); ++ SslStream sslStream = null; + try + { + //TODO: this may fail +- sslStream.AuthenticateAsServer(certificate, false, protocol, false); ++ // by Fumi.Iseki ++ //sslStream.AuthenticateAsServer(certificate, false, protocol, false); ++ if (_clientCallback == null) ++ { ++ sslStream = new SslStream(networkStream, false); ++ sslStream.AuthenticateAsServer(certificate, false, protocol, false); ++ } ++ else ++ { ++ sslStream = new SslStream(networkStream, false, new RemoteCertificateValidationCallback(_clientCallback)); ++ sslStream.AuthenticateAsServer(certificate, true, protocol, false); ++ } ++ + return CreateContext(true, remoteEndPoint, sslStream, socket); + } + catch (IOException err) +diff -Nur HttpServer-/IHttpClientContext.cs HttpServer/IHttpClientContext.cs +--- HttpServer-/IHttpClientContext.cs 2014-03-06 16:53:10.805499220 +0900 ++++ HttpServer/IHttpClientContext.cs 2014-03-07 13:33:38.190006080 +0900 +@@ -9,6 +9,11 @@ + /// + public interface IHttpClientContext + { ++ // ++ // ++ // by Fumi.Iseki ++ string SSLCommonName { get; } ++ + /// + /// Using SSL or other encryption method. + /// diff --git a/patch/opensim-0.7.5.patch b/patch/opensim-0.7.5.patch new file mode 100644 index 0000000..fefa807 --- /dev/null +++ b/patch/opensim-0.7.5.patch @@ -0,0 +1,26 @@ +diff -Nur opensim-0.7.5-rc2-source-/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs opensim-0.7.5-rc2-source/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs +--- opensim-0.7.5-rc2-source-/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs 2013-01-26 09:33:28.000000000 +0900 ++++ opensim-0.7.5-rc2-source/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs 2013-02-01 19:36:52.090259990 +0900 +@@ -923,6 +923,7 @@ + } + } + xmlRprcRequest.Params.Add(request.Headers.Get(xff)); // Param[3] ++ xmlRprcRequest.Params.Add(request.IHttpClientContext.SSLCommonName); // Param[4] + + try + { +diff -Nur opensim-0.7.5-rc2-source-/OpenSim/Framework/Servers/Tests/OSHttpTests.cs opensim-0.7.5-rc2-source/OpenSim/Framework/Servers/Tests/OSHttpTests.cs +--- opensim-0.7.5-rc2-source-/OpenSim/Framework/Servers/Tests/OSHttpTests.cs 2013-01-26 09:33:28.000000000 +0900 ++++ opensim-0.7.5-rc2-source/OpenSim/Framework/Servers/Tests/OSHttpTests.cs 2013-02-01 19:37:57.065259588 +0900 +@@ -60,6 +60,11 @@ + _secured = secured; + } + ++ public string SSLCommonName ++ { ++ get { return "";} ++ } ++ + public void Disconnect(SocketError error) {} + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body) {} + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason) {} diff --git a/patch/opensim-0.7.6.patch b/patch/opensim-0.7.6.patch new file mode 100644 index 0000000..49e6aeb --- /dev/null +++ b/patch/opensim-0.7.6.patch @@ -0,0 +1,26 @@ +diff -Nur opensim-0.7.6-source-/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs opensim-0.7.6-source/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs +--- opensim-0.7.6-source-/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs 2013-10-05 04:45:02.000000000 +0900 ++++ opensim-0.7.6-source/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs 2013-10-19 22:14:51.000000000 +0900 +@@ -1021,6 +1021,7 @@ + } + } + xmlRprcRequest.Params.Add(request.Headers.Get(xff)); // Param[3] ++ xmlRprcRequest.Params.Add(request.IHttpClientContext.SSLCommonName); // Param[4] + + try + { +diff -Nur opensim-0.7.6-source-/OpenSim/Framework/Servers/Tests/OSHttpTests.cs opensim-0.7.6-source/OpenSim/Framework/Servers/Tests/OSHttpTests.cs +--- opensim-0.7.6-source-/OpenSim/Framework/Servers/Tests/OSHttpTests.cs 2013-10-05 04:45:02.000000000 +0900 ++++ opensim-0.7.6-source/OpenSim/Framework/Servers/Tests/OSHttpTests.cs 2013-10-19 22:14:51.000000000 +0900 +@@ -60,6 +60,11 @@ + _secured = secured; + } + ++ public string SSLCommonName ++ { ++ get { return "";} ++ } ++ + public void Disconnect(SocketError error) {} + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body) {} + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason) {} diff --git a/patch/opensim-0.8.0.patch b/patch/opensim-0.8.0.patch new file mode 100644 index 0000000..3638bd4 --- /dev/null +++ b/patch/opensim-0.8.0.patch @@ -0,0 +1,26 @@ +diff -Nur opensim/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs opensim+/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs +--- opensim/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs 2014-05-17 16:15:44.791286687 +0900 ++++ opensim+/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs 2014-05-17 16:24:12.446449197 +0900 +@@ -1053,6 +1053,7 @@ + } + } + xmlRprcRequest.Params.Add(request.Headers.Get(xff)); // Param[3] ++ xmlRprcRequest.Params.Add(request.IHttpClientContext.SSLCommonName); // Param[4] + + try + { +diff -Nur opensim/OpenSim/Tests/Common/Mock/TestHttpClientContext.cs opensim+/OpenSim/Tests/Common/Mock/TestHttpClientContext.cs +--- opensim/OpenSim/Tests/Common/Mock/TestHttpClientContext.cs 2014-05-17 15:48:53.785383361 +0900 ++++ opensim+/OpenSim/Tests/Common/Mock/TestHttpClientContext.cs 2014-05-17 16:23:40.934998601 +0900 +@@ -71,6 +71,11 @@ + // Console.WriteLine("TestHttpClientContext.Disconnect Received disconnect with status {0}", error); + } + ++ public string SSLCommonName ++ { ++ get { return "";} ++ } ++ + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body) {Console.WriteLine("x");} + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason) {Console.WriteLine("xx");} + public void Respond(string body) { Console.WriteLine("xxx");} diff --git a/patch/opensim-0.8.1.patch b/patch/opensim-0.8.1.patch new file mode 100644 index 0000000..5616c6a --- /dev/null +++ b/patch/opensim-0.8.1.patch @@ -0,0 +1,26 @@ +diff -Nur opensim/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs opensim_server/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs +--- opensim/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs 2015-01-24 17:01:54.940426676 +0900 ++++ opensim_server/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs 2015-01-24 17:02:49.084542865 +0900 +@@ -1079,6 +1079,7 @@ + } + } + xmlRprcRequest.Params.Add(request.Headers.Get(xff)); // Param[3] ++ xmlRprcRequest.Params.Add(request.IHttpClientContext.SSLCommonName); // Param[4] + + try + { +diff -Nur opensim/OpenSim/Tests/Common/Mock/TestHttpClientContext.cs opensim_server/OpenSim/Tests/Common/Mock/TestHttpClientContext.cs +--- opensim/OpenSim/Tests/Common/Mock/TestHttpClientContext.cs 2015-01-24 17:01:54.940426676 +0900 ++++ opensim_server/OpenSim/Tests/Common/Mock/TestHttpClientContext.cs 2015-01-24 17:02:49.085542868 +0900 +@@ -71,6 +71,11 @@ + // Console.WriteLine("TestHttpClientContext.Disconnect Received disconnect with status {0}", error); + } + ++ public string SSLCommonName ++ { ++ get { return "";} ++ } ++ + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body) {Console.WriteLine("x");} + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason) {Console.WriteLine("xx");} + public void Respond(string body) { Console.WriteLine("xxx");} diff --git a/patch/opensim-0.8.2.patch b/patch/opensim-0.8.2.patch new file mode 100644 index 0000000..561fd3b --- /dev/null +++ b/patch/opensim-0.8.2.patch @@ -0,0 +1,26 @@ +diff -Nur OpenSim-/Framework/Servers/HttpServer/BaseHttpServer.cs OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs +--- OpenSim-/Framework/Servers/HttpServer/BaseHttpServer.cs 2015-11-16 12:37:24.000000000 +0900 ++++ OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs 2015-11-20 16:16:46.111890755 +0900 +@@ -1080,6 +1080,7 @@ + } + } + xmlRprcRequest.Params.Add(request.Headers.Get(xff)); // Param[3] ++ xmlRprcRequest.Params.Add(request.IHttpClientContext.SSLCommonName); // Param[4] + + try + { +diff -Nur OpenSim-/Tests/Common/Mock/TestHttpClientContext.cs OpenSim/Tests/Common/Mock/TestHttpClientContext.cs +--- OpenSim-/Tests/Common/Mock/TestHttpClientContext.cs 2015-11-16 12:37:24.000000000 +0900 ++++ OpenSim/Tests/Common/Mock/TestHttpClientContext.cs 2015-11-20 16:16:46.111890755 +0900 +@@ -71,6 +71,11 @@ + // Console.WriteLine("TestHttpClientContext.Disconnect Received disconnect with status {0}", error); + } + ++ public string SSLCommonName ++ { ++ get { return "";} ++ } ++ + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body) {Console.WriteLine("x");} + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason) {Console.WriteLine("xx");} + public void Respond(string body) { Console.WriteLine("xxx");} diff --git a/patch/opensim-0.9.0.patch b/patch/opensim-0.9.0.patch new file mode 100644 index 0000000..8e9ebeb --- /dev/null +++ b/patch/opensim-0.9.0.patch @@ -0,0 +1,46 @@ +diff -Nur OpenSim-/Framework/Servers/HttpServer/BaseHttpServer.cs OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs +--- OpenSim-/Framework/Servers/HttpServer/BaseHttpServer.cs 2018-04-22 14:34:37.642698003 +0900 ++++ OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs 2018-04-22 14:35:35.331826307 +0900 +@@ -1094,6 +1094,10 @@ + + if (gridproxy) + xmlRprcRequest.Params.Add("gridproxy"); // Param[4] ++ ++ // by Fumi.Iseki for DTLNSLMoneyServer ++ xmlRprcRequest.Params.Add(request.IHttpClientContext.SSLCommonName); // Param[4] or Param[5] ++ + try + { + xmlRpcResponse = method(xmlRprcRequest, request.RemoteIPEndPoint); +diff -Nur OpenSim-/Framework/Servers/Tests/OSHttpTests.cs OpenSim/Framework/Servers/Tests/OSHttpTests.cs +--- OpenSim-/Framework/Servers/Tests/OSHttpTests.cs 2018-04-22 14:34:37.643698006 +0900 ++++ OpenSim/Framework/Servers/Tests/OSHttpTests.cs 2018-04-22 14:35:35.331826307 +0900 +@@ -62,6 +62,12 @@ + _secured = secured; + } + ++ // by Fumi.Iseki for DTLNSLMoenyServer ++ public string SSLCommonName ++ { ++ get { return "";} ++ } ++ + public void Disconnect(SocketError error) {} + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body) {} + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason) {} +diff -Nur OpenSim-/Tests/Common/Mock/TestHttpClientContext.cs OpenSim/Tests/Common/Mock/TestHttpClientContext.cs +--- OpenSim-/Tests/Common/Mock/TestHttpClientContext.cs 2018-04-22 14:34:37.643698006 +0900 ++++ OpenSim/Tests/Common/Mock/TestHttpClientContext.cs 2018-04-22 14:35:35.332826313 +0900 +@@ -72,6 +72,12 @@ + // Console.WriteLine("TestHttpClientContext.Disconnect Received disconnect with status {0}", error); + } + ++ // by Fumi.Iseki for DTLNSLMoenyServer ++ public string SSLCommonName ++ { ++ get { return "";} ++ } ++ + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body) {Console.WriteLine("x");} + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason) {Console.WriteLine("xx");} + public void Respond(string body) { Console.WriteLine("xxx");} diff --git a/patch/opensim-0.9.1.patch b/patch/opensim-0.9.1.patch new file mode 100644 index 0000000..e803d15 --- /dev/null +++ b/patch/opensim-0.9.1.patch @@ -0,0 +1,15 @@ +diff -Nur OpenSim-/Framework/Servers/HttpServer/BaseHttpServer.cs OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs +--- OpenSim-/Framework/Servers/HttpServer/BaseHttpServer.cs 2019-02-13 17:07:00.037657561 +0900 ++++ OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs 2019-02-13 17:29:49.371393303 +0900 +@@ -158,6 +158,11 @@ + m_port = port; + } + ++ public RemoteCertificateValidationCallback CertificateValidationCallback ++ { ++ set { m_certificateValidationCallback = value; } ++ } ++ + private void load_cert(string CPath, string CPass) + { + try diff --git a/patch/opensim-0.9.2.2.patch b/patch/opensim-0.9.2.2.patch new file mode 100644 index 0000000..5b9a527 --- /dev/null +++ b/patch/opensim-0.9.2.2.patch @@ -0,0 +1,30 @@ +diff -Nur OpenSim-/Framework/Servers/HttpServer/OSHttpServer/HttpClientContext.cs OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpClientContext.cs +--- OpenSim-/Framework/Servers/HttpServer/OSHttpServer/HttpClientContext.cs 2023-06-02 17:03:08.247494109 +0900 ++++ OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpClientContext.cs 2023-06-02 17:07:01.767935412 +0900 +@@ -10,6 +10,8 @@ + using System.Security.Cryptography.X509Certificates; + using OpenMetaverse; + ++using System.Text.RegularExpressions; ++ + namespace OSHttpServer + { + /// +@@ -133,8 +135,15 @@ + if (_cert1 != null) + { + X509Certificate2 _cert2 = new X509Certificate2(_cert1); +- if (_cert2 != null) +- SSLCommonName = _cert2.GetNameInfo(X509NameType.SimpleName, false); ++ if (_cert2 != null) { ++ string _sbj = _cert2.Subject.ToString(); ++ Match _cn = Regex.Match(_sbj, @"CN=(.*?),"); ++ if (_cn.Success) { ++ SSLCommonName = _cn.Groups[1].Value; ++ } ++ // ++ //SSLCommonName = _cert2.GetNameInfo(X509NameType.SimpleName, false); ++ } + } + } + diff --git a/patch/opensim-0.9.2.patch b/patch/opensim-0.9.2.patch new file mode 100644 index 0000000..e5b1a46 --- /dev/null +++ b/patch/opensim-0.9.2.patch @@ -0,0 +1,19 @@ +diff -Nur OpenSim.orig/Framework/Servers/HttpServer/OSHttpServer/HttpClientContext.cs OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpClientContext.cs +--- OpenSim.orig/Framework/Servers/HttpServer/OSHttpServer/HttpClientContext.cs 2021-08-05 13:44:09.896571627 +0900 ++++ OpenSim/Framework/Servers/HttpServer/OSHttpServer/HttpClientContext.cs 2021-08-05 13:43:51.435572375 +0900 +@@ -134,7 +134,14 @@ + { + X509Certificate2 _cert2 = new X509Certificate2(_cert1); + if (_cert2 != null) +- SSLCommonName = _cert2.GetNameInfo(X509NameType.SimpleName, false); ++ { ++ try { ++ SSLCommonName = _cert2.GetNameInfo(X509NameType.SimpleName, false); ++ } ++ catch { ++ SSLCommonName = "unknownName"; ++ } ++ } + } + } + diff --git a/patch/opensim_server.patch b/patch/opensim_server.patch new file mode 120000 index 0000000..59381f5 --- /dev/null +++ b/patch/opensim_server.patch @@ -0,0 +1 @@ +opensim-0.9.2.2.patch \ No newline at end of file diff --git a/prebuild.net.xml b/prebuild.net.xml new file mode 100644 index 0000000..ab296c7 --- /dev/null +++ b/prebuild.net.xml @@ -0,0 +1,142 @@ + + + + + + TRACE;DEBUG + false + false + false + 4 + false + + bin + true + true + false + + + + + TRACE + true + false + false + 4 + false + + bin + false + true + false + + + + + + + ../bin/ + + + + + ../bin/ + + + + ../../bin/ + + + + + + + + + + + + + + + + + + + + + + + + + ../bin/ + + + + + ../bin/ + + + + ../../bin/ + + + + + + + + + + + + + + + + + + + + + + + + + + ../bin/ + + + + + ../bin/ + + + ../../bin/ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/prebuild.xml b/prebuild.xml new file mode 100644 index 0000000..2f5e309 --- /dev/null +++ b/prebuild.xml @@ -0,0 +1,159 @@ + + + + + + TRACE;DEBUG + false + false + false + 4 + false + + bin + true + true + false + + + + + TRACE + true + false + false + 4 + false + + bin + false + true + false + + + + + + + ../bin/ + + + + + ../bin/ + + + + ../../bin/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../bin/ + + + + + ../bin/ + + + + ../../bin/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../bin/ + + + + + ../bin/ + + + ../../bin/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/release/0.8.2/bin/MoneyServer.exe b/release/0.8.2/bin/MoneyServer.exe new file mode 100755 index 0000000000000000000000000000000000000000..f6e823c3e5197605d4c70d6e53d758f4c3c505fc GIT binary patch literal 53248 zcmeIb34B~fl`dZQcK7W?Qp>knmb}L8Bz6=hQ7qd@Bs+^`Nw$@Ei)F`lmT_dMZ7Y#< zmF`w-A(Y4=WC(-|VabqT%Lb1Sn1L`X4~BUIga84;gqJrAGZ@G)$&bvyFz{vrCgFYG zseAk0UL?!0!<+x_|Jqh{EvKqZojT{MQ+2Dmw_f`;6;eut@%P)`D)m{s^R-^WnS&`5 zCzpIKss1$nt)-tewtQ>puET@*%xKOzl2N2x|P~ukOTkqBV`&jMog)%A+LL=5M@sXCv2mbJOK~w+4}rAgvO^9Cg;($_GI*v)A`#GSD9ur7 z2jz+vc{5c+wK+=t=vFP|tBcYXl34|RZsyT>ViIhI&BjTKZ_OB2LYbjSyoXJ5f^SO_ z&5dY&#ZA-FuTbfgA$2=?MT537!;=xz3*ClEN;ISSM7zwJ8OkF+F^TMOW1?*txy+;6 zL`~E!i6UW6kRdDTU``UsYTBEy%n7!4#kYS8wQpOW5Q8a8UVopq@7pk+tvSA{A$rVcI z7Oo7dEhvR(EBXvaoQ7B1tJmk;#eSdd9kc3l?rYPhmTk3hI?&?(3$n%hzPesdLALW= zn?7fd?fh5UbiBU9nEQ zX>avf7wcLV;w>Gn02@2(qYc_FL7UhLPkuM&)p8GO?t4JXFAc1@3(yQevsL~KFq1Gt z%^AX+)qW)kEN2Nao7P{BxH2sVMTusptW{$AYrWo7Mgm zDX^R~z3Ow<11hCOW;Hf8Lsr&usmlzvE|kV13GvdoEi3LgoDz;yDADH zSg+>Xh#IXoUTMU_W9V7q5L9Dr8)>-ca85qWc(pls9SXcT8DR~~$*UG>P1_DOp@%&T z#!7@bFZWesxdKvcvlv5-jDhRhLiiY8Letf-z*$D2E=SP5#_!&+)M&^#hegc@%n2;H z;gEAK$wUKcj^|0;)`GxWfzeZi%&roHILul22q%w0+DEA7m%v9jZ+5-Ttw25xE2{Yj*E9p*5)S;GL^CEKF$Kd9 zi@t^g4jrCM<^&wc@V<1|oPdKE z2AIRttmP{fu8gSbA!7*0R=1(Sa4v;WIZ)|@$+@u9UxLzTUeD&O+(8z0AYKz~Xx@PL zc1TY}8yXw(ok%q}T?owyie4nIz5P2uqp*A6JT>W3lU{T<-wpD~jZ%lx(7ub5Uegd; zplx%4v-66z3DkMWAt8Nfp8we6S+xn|>)bP9?sfppw4rj5hsJ!5*RL`i=oxktWWu=; zf!TZ&LUo*?u61P+imTDaw_;KZXA25-^H96W%uGU!#ssgMnzu?j?01C$@dG5(%V}dX z25wO9>tR^XM(77r#!8sh1eURI^ETGfac4W?xtq|W0}zGGbFV{O1nXRljM`X0$OJGm z)(q3Jg0u_-=3uSza^M&s`X%%^ znbCzy{lYW-=vjXBY(^U<;X8VbV2VB8;2g>QE4J6{6Pzp2hB?T^*io&E(er!)C>e66 zm<#ns^hrirBnlCYPF^U{m5i>G=wU`%8BM8@MPA9WPG7@2n1i1IKOXuHNn&0@lTeDV z8JWa_5;l|JNvxn@oW4Puh;Mw$eF?40v6O~Bk)$s95lVdEfge&Gc0}lIW3dzgfjJJe zJ)tK=x3&^RK3LY6QE6^c=1EgrNs1!S)yAgjPo~uE?^fxLIl8}K*~29rPRjJV)xOCh zYJvuCQ3to^-%4wVGPp&H(!mi-igHYlAb@C?3uv$@+2#aE@`ou1s?coSdL%ft}fGPr8sh4!e+ z#4_Eoi=~VPs>{Sw%?a8X_j=YOz>nr882~J6vOSOiOSLB9;lqTHZLLUf`H=niQ@}TQ zd36ES)+GFYECLL31MK(}>r!OrZ^qm?LkO0;g|F9zT~!P3*M*%`3qPt0d#V;btqY-2 zRr>siF1)O2VYo@Sy}WAS3SHPywQ#d8#9CRU&x5*febvG@>B1|k7XFbgysB#9AM3&` zRSTcigjB(1t_RIfG;6u;s8M$Wr85%wQS@1pzXf5f z?567^Oo#PbM88@3E!volMPp75#8z(JJfLVE(ROsN+;jnH89|+uYbg>y^+6!%B0)NA z<+hbkNZO}}Sh?4gQApaSu&mtuWfYS3DWY~%r^gIeQmisX3G~NSCGMq7F$8w*Q z3_yv_h^FIh_rw1%CuqRwL}R+fPNZs@Zw0ZN8F!8#v34#S)MRmNrH_)JEm@K`&b;Hb z$g^wCkJ^dm*CFHk<^-W2UE7$hvujgzUenAhZCY~dNJ-OP-nid%9h$DyO@p5`(hN|< zO2_Qj-1YZqHYMAf;7Z@K)6s<2HUGbixlvEqq^J!5M_5o(wAmZJsSwxG>OI*EkJSXvB!=x43>wNt5D()OKLm zCyNA{QAztv#I4*{$|xl5Qvfi3SVke~Vkg8yYFJPllJ?uE(Le(*AojFBCkRC>@N&My zi7yh}0U|r94+{QUkX8y}y5L?`(c1r8@MlM0M{htyIX_G%Q%Tur$O&Y)c#4>=(@$^G z^>)2o>)gqX>cPYtb;NS+l1SY7eVq|^-lQW5=WZPdJ8#yJI_E7C(JC<~H797S_efEb z^HzB`n}OJtbFZ#gle=>fHjx0ZMA}Z-uQVZFf9#v)CiWmmVDsv@$h zTuxa73aZlcT*bPd)bj!$7oSe%>)Da+qV)`Rp+T`KeJuAikmUXvfhNa@g?A3L z9Ljw*{;tIIU4oW9tUCkg#?y~=8r|Yrm7#px4X=fLdP7>X1gy?9ma8+xvDCD^4gh_6 z5b{*f4G?+)bOVH*GP=d}yuSxBC_|w-_(yOUAko#3sEqfmp3LUUada_K5i2z2KXMH*Zb za_K5i2;~c*@LqovC^Xl_3)4>64ffpGqfpK9L0VD}g`TDCrbnTgu->KJ-BvWW^enhZvD`Ag zd3aK>NLjgFln4CuVwBwM$ z1I;YQNYK>q@Xub-h5v`nb|EvC`!S+{ZAdg-+jbEqO&0`q7rLQefEtj1CH5qI9T#N+ zzD~5v*U_Ua^J4^e>OI`4_i(4)F77#3h-g=0$;3$ zmdWtX!tf!eZRwJXyHu6fDYK3Sz7M{^M_`qcUEeTcliB2 zfkr)m;3$`)sE7w!h3q+)%$7Uo~H{~tg)&*1N)uy?kh{fLOV1Ijw@wXpwRgST`n z>_GAfL9Dd5gVHNhO$8X36K6n}?byL#rXYX!Xj)D^2w5%&u4pc?rq6px*P8zHT-Tat zJRihpt=zvcD-S-}_0C7qCx>9wmz*VIZHYDw{k!XG=b*Ij*Ykn6Zt_W73k{#Fk zfg32w`$+_Mq4^J)+KnLB0vcH)op-YkHYqLG^*PoyCo+)bNw%O{w(B*PO)vdb9223< zmHvmA9z&Ya$2~H4&Kk}WQ}vMwI8oIfn&bLI@DS`8?GL3qe<%g46Mrb>`a>y=ROwVY zXqSRiDOrECla%tnDurVTagtJ61shTg!HNP_Dc4C#1)Zdn9Sb-~b5nC?0alH6V-T#~ zrX^4XtWw%1YOFtU63oP`3M33qbfqwy9j|z2t0JzG1;gn=Qg&tW3pX@u(7EUJg~7px|=N79vIkI zq6G|WJ(=QylzA?;3kG=d@1NV)c3l7r1bpBdPIGQ!`^PSmG;Y@g2^_oJ$n3a2#Wp9- zuV$f*-l<2@)<-h4-*Pln9(+8iB~2u%cJR?udhl_@g_xIF9DFn#U(bPP$Mi*3I=O?7 zCVlYH6!ckMfqG=|*AL_65~O{Wu=)Vf@B?wqsl^#`P-3;OMs_iW5Q!VvXCP1C{#~19 zaZ^oGZSbnH)dSMuy4)L90tUkI_85xeatSvb z4(mJE;tbkh?FmNQ8{m+}MpRU>_U*wIGnfSJ+giHGsBN`_bp=Jn6YoSi8VNQU)s4p7 zE6lJjY1Cz0HnPjexjYL?$Fa5N6D~boS(TCVK5*_1h3m97SeHcXcn=!T)*8(1KrW8$ z&whe&>Gz=$7{b_Q84%mh+FNI*~gB%da^oaduDaa;924v;2 z#8Ym|c_6?fw~LnZM_!IrA$O>p+;pv7+x}PN)sbg)t9cVzYe(Fp7-_9~&)vO74t)>jqN}Qs zDE(7+h

qP~N2ko;RJ;7OuCChjaR8D^*SkayloJ6%d0ee8{L8Kb z&1*|et|;z3i1LSEeTVVK_F%1eUK3VVL056oa+1$$WF;G`V$6P)75M_w7gfiXnbg|ucaadv+n` zeHeY? zWi1PX)Zi28bKAVy^GKj;wpYD27Zk|xhf1`#tkGzY?MG!|am`4D2LT`cG2e={a^K&88sOM(LLa0$MBzpj$Bo$UC z+)Fw!aZ5rn?3j2ImK*2dA9drq!M^9;VXgvz61=lWWsK#rpbbksuZFm3aXC!F2MKax zAc*^`I1CVuCdar5O)n8_5a10t{|!S8$$mH_pVxD+NLlUkLFQt!N0Jdjs7HeL5Ulop z1%;~>_n|jUmPo0E+alljwP$Q8AvV zIEr;KaYbXUD{4-(N+0B|%pWZ;iEE$I3ORZ}>`b5fU^B#fUCegfSEN`Btl0>Tn*h7? zRpCMCBNw#c$gv(t=|3$s&^~NfV>ml_dOaU4- z+j({8u$sT7C);w5V}2*zo2wY2O4T(5u!?n8Yl`Q%9f6?RAB8@b_;&9=26FTge+NiB z-)=lWDfzB%*T)Jl8&NsnVHK5&$<>+M*yZkepta)>CbbI2+PZbO1O*(0iVk9r*N!#S zYcKGKAtpN_9E2;?fk$wd-u6K`@(s z8QT5`vq`Ahun2n01s-gFHbdsu}c0W1=D zCFvpo4izdviUwH;QZc>czlrTp*wJ*P?M@Jy6L5;c>2gn!nc9=|u6#-kKIPez_Af35v)?x8JHDcHxW(pl_SPM_N9p={aU4Mj`H-MXBm zkxQ$f0HeDZ7B3uPBBr^UUU=R*C5JHp<@Whho|s&dcF)CJaG?RY1>za$Y&$Y{{d5>B zKKIjLQb=RDpCB5%rP?N&+sO2rTUM|mK0?ao79;XExBo#A1UI)8>#b|J4|EolDk>kl zcUSC}kU#VGS4I&)HQ)Vm*X=)3UpZ!UYFvw`&xUaK<5lX&+;pT-pyc#vij{k?3nLFa z@sKnjQ0}UxaU=Fm%P1u6Q{Y<2lVuc=_9>u6KH#;F!%B+A7}UT0Eb#Qtwy>;XZ;Bh| z4R~6;06LlbYjk5rn)b?!$HhOhV|HZT-pl8)W-RA)mt5H>KG$b{U^x!<0WUDYUbuK4 z(3U}s$HJL`474NZd4X~9xOEqvunIh0ZgD1tdinp>V^3}=G6E(s_jh8CrN!H ziRDP>r`^>hz;G=0IGgf@?%K<@m=~8#Qo1%(8{FII{(1e`8;H)~b!X&&4eb+5>zGXA zflTAC{7iF#y<3UDQt5$UCp7)y`J3Pvn;-XhgjLrahn}c((lyHdjkN0I3|_Qr)eW19 zR$Ujg>LKKxoK@H9m9(yS7jzzUWg!%n4>~%*9POKX*vFBN!-`kC2Ee|5hsKQy@h32D zd=NNbx^PIVn=V{)-*S29TPznx8jV+Uut?kQy3NqOXux_aT$WhwJ3fuOu%y{Q1#Uwt zVr^IWZT|@MPHswz?q|j4jR`g@&d{GQrn|?_MRDC*&UtPAC=@e~lPx^baz2K*#jkmh zecFcJD{=Hp5D|S)x2|bf*|K`&>Q-3+BWeiwUD!IGb(>Nj$NQrVi1!w9gCmCkz)I~x z2T%4OZ+EYn2hWCX@mZU8_jKXC8R^ermwDEPA?E;z@q+rz*|WrqVfUfbuZ`8bgv{Q1 z@rSLg!gCLGHU38M2U_(x{IMKYuH;!h^SrcD?`Y7RTk5^)Avm00QqSSj?RCZiBcU!b zp2H`kSDTy7M)im02M|7vaDiGIx+-*r`n&Ki!b$ZPk*`LQ>W326T8y_?7g=YjyR5fZ z3H5}<)*7N;jU?3mDC18?za34gUrJaX`)b5e4Y7y7*SWEExQ(+rb|AJ~y&mxvbzkfu zgr7vXKz%dz9fZF^m{b?WzZzMkw#N^|lj?TFlj^?sxA9!&QRJ*ree|FtBaQ0T1jllxgbyKHpk7H>HA%IqhOM>LbRxc0!hJP|5YJ2aM#=f8 zgij+}pf0Jss`d%Iz@;pOWwe z315-0Db3PV5^hcR0W+s0yjOA_Pd|yAAEd3CO~x-_SV?6xEP)nkJz!{H?xAHo&n>1_ zyD9499Mz4~xhP|`IcgVD`|u51FbJduq-?&z5{lI8C53TH>Rw4*s&=SWb-%Rz7`|K5 zP@jaZhSkeJPy;7`bMatyFG7#zph21=#yo^~OZ=S@J|f{05`J02|7k1)%?qe&sGmz{ zm}ek9*E|#9a&tMt-!t)4LftIkZ4%yVu0oIZnim&){JhB)zG#Op)M zSsG%g?mfuQ>d;!`j|+|N@otGfEa9I^_+1JAw}f?Jwsww$>m}SL;jIX-p8A6@Ie9#M zIl{SS2g1*WyCEy`Y~T}CtXn=J+#(JGLge*SWT!O=8ZUL!x=V0?>8BzOAQdT=J!0J} zn4!zQWW7^=J(N&BZN0~PxPW?pXFVd{2B9f`X?++otzSFHWt3pEdbgXR1kY8qaP?V637)T}+!Q5vq52m$ zMG3A{hv77kk`i2{zF$ne(_F1SjCFx!l-VWfQkoi4lo`AdEIv$8X6@>yZi+Hnt8V1t zg)&NhoqA4E_suKJd7F8;`l+NSm)+`e_1^PHd6$})!_+@Z>X@3EQxm#eb)C<$C)JgS z-DeR>Q>__Uol6unkA1LckbLbwUcg|RcI^Ruk#yZqyH^mw2 zPy=p?GuEMQmz3tSL%my4n$HgPeVtN2sM(Jmei|s-puz+ljA8ej>q8sVQeL62+N-{H z9aG2D6ZW&=Yt`Fs(5Y|RKQwMoh2LZ9PIcVASM66{+t1Wp>KCYYqk6DUr>xXi=zy9U z(5Zz;4XD4*>eLFP4yrYWbZRY9ht=Leo!W-fO{#5Jrw$@Dq<&7fLFsuIQ4vSVL|;bK z97)};j-}ooA(^2X0VRf7jL=l)Aq=TCft+=LoQ;xCdQW4OO zMQBRPx_#Z3sg4QdTM&lTJA_iV>E|2jeUh)kj}>Wv+!8-%u;ph0`TuwMpN`wgaV?LN z^9v9f>Q`8*z3|8IyG`{=gdz1Sgkk-qLF&ESo=9k_e?R3o`83rhlkDZs5l-Lof1=b> z7EX^sst%zycX^3xO(1WlgR5XWkJn@5Slw#zj|g|Qg!|NskqEq%pGNk`rARPvXq zUq@n~G_3)wdX_bWXX`AjRgKmypjl!qM7Z4Atp?Rb>nLcpfW}r_8*Ft0@~>8htUEw6 ziny(~O4y34gRQtK*ov!xt+)!pd*e8pSzyNDl?(tA*9t3Qk$NBAKLiTwAe=cE6i9#Ahwf36-= zOJe_mC4(|)knj@{{&$3?@kRCfvAFT9`X8}6<9m|-J@unl8u6dS<{8f;XEEY+@v{)l zkMCArS4$ASg#2@jmsDGPCBhByHAY-r86SclIUH{{9#Zd#uQv?ir;$y@FF<)U;$Mkh zZ}cF3!1#oCE9a_b;x`$s2HR);QG{&&6SC%T)VCS!#=*o<^+k1a;*Ca+@vg)@#??ln z^>(9GJ&|}f!aqyA-`Jea8Hnj~n}p^J<<_ zw-`HXK5dMn-`)5O`|E4IVBBroQ}bnnZ?D;nzTRCELHIDj`;1T5e8YIa_;$@lj9ZNF z)_mJ&2cP?~;<wRjJzV86C*^p>eKSTzix8ka11z3&vyah~85BQ{xlH7i#~-_@eQ% z+NbbLw$b{v@vQXwbqSv}7S|1_uN&vo{nq$8O2g*YjdgX`8_yeC>f&aDI;U<9Lh5IO zT30uqo`*CV&AZX#Zp{A9y8Q@mknj+~TU4%YAxe)({0((WP?wqKjRF_1_@8Tm2)#2KBGWIT$Y` z1G}vMwy{qwslVPNx3?L#$uZidY4w@&5kH7&L;#ME#_Bi@Ll1 z4#eMHPuqD_et0%;UL0!#w`*b zm++W`cOx9K9zeJo2=g`doYA84k;ft}Y63go7WD=R-xB#2;_s0B_e8#r_#=}4xP+gK zyo{XBN6xWY)R!Y`5Pl<)NBBP@ueUy9e#v~={EqoI=8w&PHvipx*|b9G(BjZJp|((W z=*m!UXiw-+=ve5%&^JT>7CJNB8-7#xL*dVdzaRd$u!N&{^yEJ@z)ViX9@1=B`-MnCKpX8O+#kcXwD~6Te z*AMFWFM=dGOC6NDZo++A$HNS~V&M8W4@y)W2;}|B&3j1lf}DKJ8n4pcJzn0WTb+Ti zq3q>q`)GEgcW}66Q*LmeWveri9q-NNZq4RYk0iP_Fge(tb<=LD)5&F389|`(hCY1% zTp-=)j2s+1)a#7p(2(Ts9Uj^_+OL^VJM>RvsNwxX_ztq3EeH@n((>=Pkl!*I5H?rVFZ2of}5lK zPV`ZiHwG=?9Zrs;I_%^N+xmvHi1uf5g^hzl*%3dvqc5L7;^YQc+E-pa)Snv)(jU(D z-`qJgm>nr}q9xVU-O;swSI^dNwY6jK{;m!CJGXD!*4??QXZtpflagt0CG1bR=I9y> z{@}RU*1LtR4jvrr?<-{e%$7iI%Pz+m%A@goHlK$;AX!QDkL9wx!K@?M10`ul3JRin zAUVEo8yp$L6zIH>K^=3|2eX$w*f%y**w8oBHv)Yc-ha@6UhT^DjpPr?^d9INxjCEb zxD|8G+0OUp21g6M*^vQJgDkdYFpxtga{4JvJyRl0Eey5yqLuA| z5f>GvH;X|I7RFmTbK|20{^ZN(;qjI-Y7tF-sM8sSF1wTN4r42Ng<@~%8yV>B8{()+ z3wHM3I&B$;wrTJnwW+L9wm&;~Yqo0Hj=u5E!+p6!m0fKZ8{dB5CT(-2?QO~yJQ0=_ zpi1?EotlGb3Snd$`ucBHTeolP-nVso*X}Ld`*(I?b{N*odFv$-SIEzF}u&VwYwr^n9AS^sj z9m#g$H-8k!as|mm^YTvM=s{OUgZ=O35jffMutc@=7o41O=SPE;0tj_&fRPko$c3Bk z;nBjlaz^*}jAU;a>l=c4Di69k1_o4jE{C?dkM?Ir318|^wy?hk=%=~2}cGChX-*>K1iQF-IEd`j}Ha4svw8PGTEVb^UwKIFrU9>oL zonyHimI6%%>fr+crn<8ILw$VwvTJ-at0cg(>nR6nBgJKw&AYbKtNns$Es0vw2W%V3 zJHW3>GOt9pBYXo>U2-7*@G!8?BdVwl#T^{+O7m3I5!ev%pVpHrDkl?pSQ-fm;J9x{ z65S(x2Zo>q-9vq&`RqXVt&q%S^f!d65c7UFUtzwX0&qxRIJ~0i(=yb2biUhX$lAP)kDmpgmYp zz+ul$*l#vp*wF_Y$$4Es#i7+>*`jXyYrnfnm#ZA=d zF#V9F>;-l>gTNKn5qGf%iX&59QK^c~xL&8=m+BZ8V0uTVXO20o>q=9(ny2J|F zVtTNIzTrG<1OxV_!q*lQ_z9u!bk}ZCXmOF%=Dz&l0#TvZQ+^a^#AUj$UmF&5tN>#X zAJLNzOJ$M7GBwDxxSKd5m0YAJ1-4W@J0#OZ`D?Q|N76g;N7%F43G~4in37n4T^2*B zs@EAti;D{r?xMq}UjBAr>x<>|N@_K>N=aVMSg>m02e(^4TVI2h-0}v>YTeh_V!3#S=NJN)#XSZbac8L zOpo@d&>P}K|3X}#Vro;iP`YRZc4#Qm8dRFMr(_$~1A7>Rn*|dBz4jr&nd_Cpa~0Qh z51en+$<0;3pD9_FJ2>K<@Ho++9+``-`*-wo2{`s<3uB`)Bo~qLY6k#3zyMVM8~L0I zDgx}i*unZcufwwX>m?J;4wgin6XZ(gK>OY8rR$68Y)Ep0Y&5lObtWfY(w;M{v$5Hd zTzAK{&A~o;yf{Oe1~MzHROHPWqaw*6gvS5=@x5EO05Uyr+$~P9S9_7TswL#;)^=6H^o^Rv~g^3z$f;`PTjsnulj!BF7D9_#eJ(^P}YT8k#2|V z$PRI7^Qm794d8PTL^)K zPtiwY^4XZSA#KMw5u~TbdU$=Xyiu_@jRh0}fsAeB5&94ZOWHFXH@je;)7M;Ew4K9f zkv2-3ABC7`WdOQ;)3u8`O88eQ!0D%@ihZ^d*Jj&7P%YHpbUbYodDG}Te5C1}>5lPC zK`ze85h))99YLPbAu{P18J0Y~bj+~Q>_!Ey4g^k59}h}1Gj;9_IfxTny=WQJ;6i!` z3=`0YFp=0Pf?+&wtP9$l>Ma#Wuh{1`66$ra7hDphM$yw|Sn)0_8eABNO@vvBNy9vW zmB^$vb)v^Xq_{|Q3Jq6Oj*;9@Y}W`^Tu7MKBI}gAAxMS_dNaP4V0OG^fF;<=VT5e? z6#AR`@rgU+!k$A%6i6@rxSn`J({2Q9hU`}p0=0#fLR;kO#x-~YVjB9n#JgQRAmR#*L5x-5F1LsaRSnzKZmsv`j6g9y;}| zcZk(^?uI{H4xPGKu4frsSJa-D=XmS@tJKmXA~d=ZT$7H13f9qc>KtkyR`tC_p>T>+ z{SLTkT}5tGjqlqj+p|vM0^J~Gm-~>aemh#BMfHMH+$^*`@GsVhbETwOD0{SQjH;e0 zLN`l0ajJ%(h*NC#75CIf#aFAB&Ep_({%{-U!>q(cdAMjKSjZQw>^ zx-7`Ir!Ag#6s1^|s+>E2;g^)zZ?!lA|x`HGC9+K6XOcN!vr(9|^{a~CvH0x=& z)bL0}_1LBl;HkC747J2JhHA*8;vVLx#hq})yp}2ia8_w;`t21oQ|~*y!|xD55IDJF zln&e55(L@Gi`8-%8T_jd#eWs{c8bGW$=;_#xvM9=an`H0~_)5>twr{l!IM zx}~NC^{IX(*qA*L*X3xT_srU{^R1V@1M}#N9s+-*zJyxhQwHtT<4C1jaiRYbaJJI@ z!!7yCXrJ^YxFBhl)o&$(NjSUcs(P;T7SwPzk{7Zcy`AOO@gUkLt)oYvcVTds*U_~5 z@u&9`?6rD5e@{@X+qnTF*KWPHAk>Usz zccBliUflf_q*)7`Q(2D{IgaE{9V1Wp@s8; zJ@uj&o)V3MBdtvX0VhtK7wqi>wCDPB5v1%u4;%?Sm#grOUH>$cJy?zPUV+vx{`j&W zc1{cYRjDdhzOudRPITAl9--;AjsQ;QLLY~5Vta%Q`nwqjk!#-Up?>zOUJx$4L8_@& z!EX8DJR6pSookl4HdR3X1=J^BU$?q5q><3Wq+X$c53_nE>YdPsTVXRA$BK1k?7`Db z53f$tLlEbRb$}t$kAeC!4u#;60G^{^d}>B|Zhpo~@K4P+f=y_#5VWlssdekVMX8Hs z;=L5>0<~uBqa5i}Q_$MB0D}&}6h`p<-j(@W3*fKM)uSrqO)b{BK6V9}1J>FHYfA&}a$8x!Q8>d|$`fIEH8=ns3kezco10HtQ10ntsI z>gcthpCjV;rlDN;`l*S}rF#f*7MC(Y1RWEt7TiwJmC%ZcQ`*JbP<{lm=Tj0rUY_2P z4xnY{QR+R45T&0>nX^sr8HzTQAYB#pKd-#p2fE;fv&LFN&>2o|VKDKanBBIZRFCy z4v)Aqqmi4aqR+;)4Qmtg4uE`Tf(|wZ%$9=NuxcE5ZE!#t#gTZ1$`-sw7&l12+-Oo0 z^iD-vOm+I=aYT=2XQwuzrGSK48bP%|mW$b^sT8ND@+W~i7 z;Y-9v@N%hnXm$`PqR$P2(hE}ic3HmUmM#8Me{%Y4=TtFnHp5b(l}cUU3BuQX3WC-V zJOc4N1o|T0CP3Ebsk>)4Xx$CHS+Ba^;Y?tmp}Ta=)$H{JPH5y|*KFl~B33-K2{KzM zixcC^tnj2`Iy{-}pjBOPKGfLhnF=kR6N$X&Sxla;Ui|~Nlksc5aJssu^Ish=nM+5D z2U&D2O80C2-5jpAoYKl`1Dqy*|3rOvo%Xt3+)V3*r-{#4&VE@qkp_D+I2#nuG{MbY zb+_&D(6tQl$8*;DVnXIgIJwDMcH~_d)Up#$t>@oVgSv7n) z&e9F`*qd37`poNet`xI$gJ;9u?_{-Ds87H3g(~k!hU@U|pPjbMbbe0d^Li*hEt>U4 zaJC(kLPfRnvDt5DW_gr*BGyiR4o89MJfNJ-VARxmPR|rk9sMJJE-RnX-oQNfVjHMfIk3sKHOS|9R6>yIg zUJBPt1YL8|YFRz>yJF1qnyN8YIs|=%ng?Q(CGPaUmMqS8`=SoLmRtm$l1#nh)pwMg z@gYa>@R#1x^k+I+wX|a(p9MHFJx{?0SJj?RvK5`r2QT^=0aeo97)%ekvt9wF>v%R# zjHcV;(V(lrv%Y|%rap6OGI@27s*Rk-_j(5PW3ZEvtlC=M^QoMWtJ`NMg~~^o`pGu6 zVI_Sm{Dk{wb9p)>Afo62+&%{dlO#CkeCx-W3>Ed$Ejc z!F_pN+S!R*|3g;f$$L)4r&hd`T)@9k^u88fuzMje>80t1eJb7ZY5})n_~r*;Tz{;f zQ{vp;hTemD;UO1SPwuWcC*GsY(wmW7rFo%LZGAo!or^2Jy$!qt9*OO#W?=f0h#LF&p|YTF!OvCj*$}T|2Cv$ePJ^=j z9yuXBcoATk-jrDwD{kcNkcIDLyeg<=vkMB;zT%{|1M5M>uZ8>^c7!1M?cbkj~G`D$K|e^-V} zDCO@vA=3|y;H+U<26@4*sun<<_SUw5BF*x^aTEB#Hx*TFd3sEkrcZn}gdZxxu_t2g zU70$XiFfly*&KgxZ>n0$m#*r(aM^ON(`}vIJe_D{F7nS-t54LNR*UkTZZ*i#muS4j zgQwfxl`7Afc3G?QJU4-@GzIA&BS>Co|(t!_gyy1lj>b-Vd>&eGu8Pgvy*XkY_ZS3=j>7odANj2ef6b# z%~@llR)*=At!65v9dU~ELh7l2r$SWLY6$Bt1ly~X&4j*GL2gf~Z?4r;*=MX~qy4^a z^rCU!VrP5sSH3{Z%wjcBDPV>7QQBP|B-d*gE~fFRZrWl2uBV3Qqwqdvc}*=izt!=& zno<@5s|+R1h4(ra8hCT6+MLVESHqoKw^m8<_q_F_c+t7y%(GasG}x>EeFcF*PjlO| zxZYp|qcHhdZG0&GP_%^BVq$gtHlkti4e+V7spn6Z%zY+*w~`MIr`oWUdU<{N?w7{qcYN%f4_>zUBf0;r zLRKnbT1mXcW7gbMof%8nrWG@-xM`WlU4*|m_?sJ*(j+P8<8LwkmYP-qZ!!GU;%`~h zt&dCEMnsurG8xVoMlzZ)jkHP|Of0f8MjENGGK?59Vo4B#B4pa_$yg*~n5oMwq>{0i z&Wy1QGiGJboZXJvsmtv4L?*I0bs1>laWoWYU+)#)YRQleNi+MHsa`~E)zBvrKaARn!3-PdKb$eVfMY4GC`e( zB4BRnF|s}%tfLA;unxA8i<1y2b1;gjkFoQqPjP&UBNQIqz#4u-FRTnR8D5EBSVdJc zDI>rT72AIR^I#@ZQ~yi)IgSx7;tZIJ7+Yj1jCBqw7^a>bOuXIBp|&H>KonujQXw#b z2JuEIgPC6mOD{|KMT}%|WHEl(1{_D&IA<>g8BoSk#}k=wYU-sV6o-OD$7nqY(Iw}g z7mjZc24TcN#mN@kd%nw5&BYOQEkukYT4XLVQ{8rZs`b3+{9~SztF9<&v-3yq@hG@LV16ZYqQmu2N2|rUL){1=CpX5TA^P?bq zA$1IOUrHUTOQ9Ho=KnaSgVFA~XbfZNrT~*ka{;EA1ehDt*Mqza6ET)*omW!i7q+H! zFK!*wU1ZLU2HB0t9Hm;%h}H)hhkD1LljvD@)0!@=gwm;(U`NJcDpRUqv7zR9-@#q+ z(;zBh;OqHR(opfOgZ()t@8GuwRK`#Xi(j0W`O;&VVp(R@%9Z%3xpNJ5_Q8uTTGQXQ zrtgA*w*E^lSks#AyP$3L>VXSZt!Z1`KagG3o?UqmKOt+VXe)j}7XR?Mc0(;_+191#iFjkJiaYNsGfHovIf8U7yQ z8OADKJBoDpe%)O6(Ja5c9N$}uuT9KmTLy;k&={}ZE>oHH68nF_e?el~skN)PFXWcg+ZnR_ zcJ=Y_hta43p6Fw9pLD27OLq#*N{ssww7n9)bbEj@!0(k(9(*b=Ea#H^Bp~*1@>+x6 zTJp+xEocNMmc?BoD7KfDv^)esS6;_TMGjd;j94-qEu0(kDMHIG!Qz z{E{gD_6_}E?{h<}vl73g(xq|G!kn;oBZx0g|lnU$qux`E4Ta#yTF$0 literal 0 HcmV?d00001 diff --git a/release/0.8.2/bin/OpenSim.Data.MySQL.MySQLMoneyDataWrapper.dll b/release/0.8.2/bin/OpenSim.Data.MySQL.MySQLMoneyDataWrapper.dll new file mode 100755 index 0000000000000000000000000000000000000000..ecf5bf37a62de3e491830e42c1b297d7c54f228a GIT binary patch literal 29184 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 z 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 literal 0 HcmV?d00001 diff --git a/release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs b/release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs new file mode 100644 index 0000000..17d3d7b --- /dev/null +++ b/release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs @@ -0,0 +1,71 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +//using System.Linq; +using System.Text; +using OpenMetaverse; + +namespace OpenSim.Data.MySQL.MySQLMoneyDataWrapper +{ + public interface IMoneyManager + { + int getBalance(string userID); + + //int getBalanceStatus(string userID); + + //bool updateBalanceStatus(string userID,int status); + + bool withdrawMoney(UUID transactionID,string senderID, int amount); + + bool giveMoney(UUID transactionID,string receiverID, int amount); + + bool addTransaction(TransactionData transaction); + + bool updateTransactionStatus(UUID transactionID, int status,string description); + + TransactionData FetchTransaction(UUID transactionID); + + TransactionData[] FetchTransaction(string userID, int startTime, int endTime, uint index,uint retNum); + + int getTransactionNum(string userID, int startTime, int endTime); + + bool addUser(string userID, int balance, int status); + + bool SetTransExpired(int deadTime); + + bool ValidateTransfer(string secureCode, UUID transactionID); + + bool addUserInfo(UserInfo user); + + UserInfo fetchUserInfo(string userID); + + bool updateUserInfo(UserInfo user); + + } +} diff --git a/release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs b/release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs new file mode 100644 index 0000000..0be9f48 --- /dev/null +++ b/release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs @@ -0,0 +1,1133 @@ +/* + * 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. + */ + +using System; +using System.Data; +using System.Reflection; +using System.Collections; +using System.Collections.Generic; +//using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using log4net; +using MySql.Data.MySqlClient; +using OpenMetaverse; + +namespace OpenSim.Data.MySQL.MySQLMoneyDataWrapper +{ + public class MySQLMoneyManager:IMoneyManager + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + // by Fumi.Iseki + private string Table_of_Balance = "balances"; + private string Table_of_UserInfo = "userinfo"; + private string Table_of_Transaction = "transactions"; + //private string Table_of_UserInfo = "currency_users"; // for Aurora-Sim + + private string connectString; + + private int m_defaultMoney = 0; + + private MySqlConnection dbcon; + + + + public MySQLMoneyManager(string hostname,string database,string username ,string password,string cpooling, string port) + { + string s = "Server=" + hostname + ";Port=" + port + ";Database=" + database + ";User ID=" + + username + ";Password=" + password + ";Pooling=" + cpooling + ";"; + Initialise(s); + } + + + + public MySQLMoneyManager(string connect) + { + Initialise(connect); + } + + + + private void Initialise(string connect) + { + try + { + connectString = connect; + dbcon = new MySqlConnection(connectString); + try + { + dbcon.Open(); + } + catch (Exception e) + { + throw new Exception("[MONEY DB]: Connection error while using connection string ["+connectString+"]", e); + } + //m_log.Info("[MONEY DB]: Connection established"); + } + + catch(Exception e) + { + throw new Exception("[MONEY DB]: Error initialising MySql Database: " + e.ToString()); + } + + try + { + Dictionary tableList = new Dictionary(); + tableList = CheckTables(); + + // Balances Table + if (!tableList.ContainsKey(Table_of_Balance)) + { + try + { + CreateBalanceTable(); + } + catch (Exception e) + { + throw new Exception("[MONEY DB]: Error creating balance table: " + e.ToString()); + } + } + else + { + string version = tableList[Table_of_Balance].Trim(); + int nVer = getTableVersionNum(version); + switch (nVer) + { + case 1: //Rev.1 + UpdateBalanceTable1(); + break; + } + } + + // + // UserInfo Table + if (!tableList.ContainsKey(Table_of_UserInfo)) + { + try + { + CreateUserTable(); + } + catch (Exception e) + { + throw new Exception("[MONEY DB]: Unable to create currency userinfo table: " + e.ToString()); + } + } + else + { + string version = tableList[Table_of_UserInfo].Trim(); + int nVer = getTableVersionNum(version); + switch (nVer) + { + case 1: //Rev.1 + UpdateUserInfoTable1(); + break; + } + } + + // + // Transaction Table + if (!tableList.ContainsKey(Table_of_Transaction)) + { + try + { + CreateTransactionTable(); + } + catch (Exception e) + { + throw new Exception("[MONEY DB]: Error creating transaction table: " + e.ToString()); + } + } + else // check transaction table version + { + string version = tableList[Table_of_Transaction].Trim(); + int nVer = getTableVersionNum(version); + switch (nVer) + { + case 2: //Rev.2 + UpdateTransactionTable2(); + UpdateTransactionTable3(); + UpdateTransactionTable4(); + UpdateTransactionTable5(); + UpdateTransactionTable6(); + break; + case 3: //Rev.3 + UpdateTransactionTable3(); + UpdateTransactionTable4(); + UpdateTransactionTable5(); + UpdateTransactionTable6(); + break; + case 4: //Rev.4 + UpdateTransactionTable4(); + UpdateTransactionTable5(); + UpdateTransactionTable6(); + break; + case 5: //Rev.5 + UpdateTransactionTable5(); + UpdateTransactionTable6(); + break; + case 6: //Rev.6 + UpdateTransactionTable6(); + break; + } + } + } + catch (Exception e) + { + m_log.Error("[MONEY DB]: Error checking or creating tables: " + e.ToString()); + throw new Exception("[MONEY DB]: Error checking or creating tables: " + e.ToString()); + } + } + + + + 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; + } + + + + private void CreateBalanceTable() + { + string sql = string.Empty; + + sql += "CREATE TABLE `" + Table_of_Balance + "`("; + sql += "`user` varchar(128) NOT NULL,"; + sql += "`balance` int(10) NOT NULL,"; + sql += "`status` tinyint(2) default NULL,"; + sql += "PRIMARY KEY (`user`))"; + sql += "Engine=InnoDB DEFAULT CHARSET=utf8 COMMENT='Rev.2';"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + } + + + + private void CreateTransactionTable() + { + string sql = string.Empty; + + sql += "CREATE TABLE `" + Table_of_Transaction + "`("; + sql += "`UUID` varchar(36) NOT NULL,"; + sql += "`sender` varchar(128) NOT NULL,"; + sql += "`receiver` varchar(128) NOT NULL,"; + sql += "`amount` int(10) NOT NULL,"; + sql += "`objectUUID` varchar(36) DEFAULT NULL,"; + sql += "`regionHandle` 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) default NULL,"; + sql += "`description` varchar(255) default NULL,"; + sql += "PRIMARY KEY (`UUID`))"; + sql += "Engine=InnoDB DEFAULT CHARSET=utf8 COMMENT='Rev.7';"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + } + + + + private void CreateUserTable() + { + string sql = string.Empty; + + sql += "CREATE TABLE `" + Table_of_UserInfo + "`("; + sql += "`user` varchar(128) NOT NULL,"; + sql += "`simip` varchar(64) NOT NULL,"; + sql += "`avatar` varchar(50) NOT NULL,"; + sql += "`pass` varchar(36) DEFAULT NULL,"; + sql += "PRIMARY KEY(`user`))"; + sql += "Engine=InnoDB DEFAULT CHARSET=utf8 COMMENT='Rev.2';"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + } + + + + + /////////////////////////////////////////////////////////////////////// + + // + private void UpdateBalanceTable1() + { + m_log.Info("[MONEY DB]: Converting Balance Table..."); + string sql = string.Empty; + + sql = "SELECT COUNT(*) FROM " + Table_of_Balance; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + int resultCount = int.Parse(cmd.ExecuteScalar().ToString()); + cmd.Dispose(); + + sql = "SELECT * FROM " + Table_of_Balance; + 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=0) { + amount += balance; + updatedb = updateBalance(uuid, amount); + } + else { + updatedb = addUser(uuid, amount, int.Parse(row[i,2])); + } + if (!updatedb) break; + } + } + + // Delete + if (updatedb) { + for (int i=0; i + /// update transaction table from Rev.2 to Rev.3 + ///

+ private void UpdateTransactionTable2() + { + string sql = string.Empty; + + sql += "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_Transaction + "`"; + sql += "ADD(`objectUUID` varchar(36) DEFAULT NULL),"; + sql += "COMMENT = 'Rev.3';"; + sql += "COMMIT;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + } + + + /// + /// update transaction table from Rev.3 to Rev.4 + /// + private void UpdateTransactionTable3() + { + string sql = string.Empty; + + sql += "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_Transaction + "`"; + sql += "ADD(`secure` varchar(36) NOT NULL),"; + sql += "COMMENT = 'Rev.4';"; + sql += "COMMIT;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + } + + + /// + /// update transaction table from Rev.4 to Rev.5 + /// + private void UpdateTransactionTable4() + { + string sql = string.Empty; + + sql += "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_Transaction + "`"; + sql += "ADD(`regionHandle` varchar(36) NOT NULL),"; + sql += "COMMENT = 'Rev.5';"; + sql += "COMMIT;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + } + + + /// + /// update transaction table from Rev.5 to Rev.6 + /// + private void UpdateTransactionTable5() + { + string sql = string.Empty; + + sql += "BEGIN;"; + sql += "ALTER TABLE `" + Table_of_Transaction + "`"; + sql += "ADD(`commonName` varchar(128) NOT NULL),"; + sql += "COMMENT = 'Rev.6';"; + sql += "COMMIT;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.ExecuteNonQuery(); + } + + + /// + /// update transaction table from Rev.6 to Rev.7 + /// + private void UpdateTransactionTable6() + { + m_log.Info("[MONEY DB]: Converting Transaction Table..."); + string sql = string.Empty; + + sql = "SELECT COUNT(*) FROM " + Table_of_Transaction; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + int resultCount = int.Parse(cmd.ExecuteScalar().ToString()); + cmd.Dispose(); + + sql = "SELECT UUID,sender,receiver FROM " + Table_of_Transaction; + 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 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 DB]: Error checking tables" + e.ToString()); + } + } + r.Close(); + } + return tableDic; + } + } + + + /// + /// Reconnect to the database + /// + public void Reconnect() + { + m_log.Info("[MONEY DB]: Reconnecting database"); + lock (dbcon) + { + try + { + dbcon.Close(); + dbcon = new MySqlConnection(connectString); + dbcon.Open(); + m_log.Info("[MONEY DB]: Reconnected database"); + } + catch (Exception e) + { + m_log.Error("[MONEY DB]: Unable to reconnect to database" + e.ToString()); + } + } + } + + + + /////////////////////////////////////////////////////////////////////// + + /// + /// Get balance from database. returns -1 if failed. + /// + /// + /// + public int getBalance(string userID) + { + string sql = string.Empty; + int retValue = -1; + sql += "SELECT balance FROM " + Table_of_Balance + " WHERE user = ?userid"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?userid", userID); + try + { + using (MySqlDataReader dbReader = cmd.ExecuteReader(CommandBehavior.SingleRow)) + { + if (dbReader.Read()) + { + retValue = Convert.ToInt32(dbReader["balance"]); + } + dbReader.Close(); + cmd.Dispose(); + } + } + catch (Exception e) + { + m_log.ErrorFormat("[MoneyDB]: MySql failed to fetch balance {0}" + Environment.NewLine + e.ToString() + + Environment.NewLine + "Reconnecting" + userID); + Reconnect(); + return -2; + } + return retValue; + } + + + public bool updateBalance(string uuid, int amount) + { + bool bRet = false; + string sql = string.Empty; + + sql += "UPDATE " + Table_of_Balance + " SET balance = ?amount where user = ?uuid;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?amount", amount); + cmd.Parameters.AddWithValue("?uuid", uuid); + + try + { + if (cmd.ExecuteNonQuery() > 0) bRet = true; + cmd.Dispose(); + } + catch (Exception e) + { + m_log.Error("[MONEY DB]: update money error " + e.ToString()); + return false; + } + + return bRet; + } + + + public bool addUser(string userID,int balance,int status) + { + bool bRet = false; + string sql = string.Empty; + + sql += "INSERT INTO " + Table_of_Balance + " (`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 (cmd.ExecuteNonQuery() > 0) bRet = true; + + return bRet; + } + + + /// + /// Add user,for internal use + /// + /// + private void addUser(string userID) + { + string sql = string.Empty; + + sql += "INSERT INTO " + Table_of_Balance + " (`user`,`balance`,`status`) VALUES "; + sql += "(?userID,?balance,?status)"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + + cmd.Parameters.AddWithValue("?userID", userID); + cmd.Parameters.AddWithValue("?balance", m_defaultMoney); + cmd.Parameters.AddWithValue("?status", 0); + cmd.ExecuteNonQuery(); + } + + + /// + /// 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; + + sql += "BEGIN;"; + sql += "UPDATE " + Table_of_Balance + " SET balance = balance - ?amount where user = ?userid;"; + sql += "UPDATE " + Table_of_Transaction + " SET status = ?status where UUID = ?tranid;"; + sql += "COMMIT;"; + MySqlCommand 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()); + + try + { + if (cmd.ExecuteNonQuery() > 0) bRet = true; + cmd.Dispose(); + } + catch (Exception e) + { + m_log.Error("[MONEY DB]: withdraw money error " + e.ToString()); + return false; + } + + 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; + + sql += "BEGIN;"; + sql += "UPDATE " + Table_of_Balance + " SET balance = balance + ?amount where user = ?userid;"; + sql += "UPDATE " + Table_of_Transaction + " SET status = ?status where UUID = ?tranid;"; + sql += "COMMIT;"; + MySqlCommand 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()); + + try + { + if (cmd.ExecuteNonQuery() > 0) bRet = true; + cmd.Dispose(); + } + catch (Exception e) + { + m_log.Error("[MONEY DB]: give money error " + e.ToString()); + return false; + } + + return bRet; + } + + + + /////////////////////////////////////////////////////////////////////// + // + // transaction + // + public bool addTransaction(TransactionData transaction) + { + bool bRet = false; + string sql = string.Empty; + + sql += "INSERT INTO " + Table_of_Transaction; + sql += " (`UUID`,`sender`,`receiver`,`amount`,`objectUUID`,`regionHandle`,`type`,`time`,`secure`,`status`,`commonName`,`description`) VALUES"; + sql += " (?transID,?sender,?receiver,?amount,?objID,?regionHandle,?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("?objID", transaction.ObjectUUID); + cmd.Parameters.AddWithValue("?regionHandle", transaction.RegionHandle); + 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); + + try + { + if (cmd.ExecuteNonQuery() > 0) bRet = true; + cmd.Dispose(); + } + catch (Exception e) + { + m_log.Error("[MONEY DB]: Error adding transation to DB: " + e.ToString()); + return false; + } + return bRet; + } + + + public bool updateTransactionStatus(UUID transactionID, int status, string description) + { + bool bRet = false; + string sql = string.Empty; + + sql += "UPDATE " + Table_of_Transaction + " 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); + + try + { + if (cmd.ExecuteNonQuery() > 0) bRet = true; + cmd.Dispose(); + } + catch (Exception e) + { + m_log.Error("[MONEY DB]: Error updating transation in DB: " + e.ToString()); + return false; + } + return bRet; + } + + + public bool SetTransExpired(int deadTime) + { + bool bRet = false; + string sql = string.Empty; + + sql += "UPDATE " + Table_of_Transaction; + 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); + + try + { + if (cmd.ExecuteNonQuery() > 0) bRet = true; + cmd.Dispose(); + } + catch (Exception e) + { + m_log.Error("[MONEY DB]: Error updating transation in DB: " + e.ToString()); + return false; + } + 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_Transaction + " 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 DB]: get transaction from DB failed: " + e.ToString()); + return false; + } + if (secureCode == secure) bRet = true; + else bRet = false; + } + r.Close(); + } + return bRet; + } + + + public TransactionData FetchTransaction(UUID transactionID) + { + TransactionData transactionData = new TransactionData(); + transactionData.TransUUID = transactionID; + string sql = string.Empty; + + sql += "SELECT * from " + Table_of_Transaction + " 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.ObjectUUID = (string)r["objectUUID"]; + transactionData.RegionHandle = (string)r["regionHandle"]; + transactionData.Type = Convert.ToInt32(r["type"]); + transactionData.Time = Convert.ToInt32(r["time"]); + transactionData.Status = Convert.ToInt32(r["status"]); + transactionData.CommonName = (string)r["commonName"]; + transactionData.Description = (string)r["description"]; + } + catch (Exception e) + { + m_log.Error("[MONEY DB]: Fetching transaction failed: " + e.ToString()); + return null; + } + + } + r.Close(); + } + + return transactionData; + } + + + + 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_Transaction + " 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"]); + if (r["objectUUID"] is System.DBNull) + { + transactionData.ObjectUUID = "null"; + } + else + { + transactionData.ObjectUUID = (string)r["objectUUID"]; + } + transactionData.Type = Convert.ToInt32(r["type"]); + transactionData.Time = Convert.ToInt32(r["time"]); + transactionData.Status = Convert.ToInt32(r["status"]); + transactionData.CommonName = (string)r["commonName"]; + transactionData.Description = (string)r["description"]; + rows.Add(transactionData); + } + + catch (Exception e) + { + m_log.Error("[MONEY DB]: Fetching transaction failed: " + e.ToString()); + return null; + } + + } + } + r.Close(); + } + + cmd.Dispose(); + return rows.ToArray(); + } + + + public int getTransactionNum(string userID, int startTime, int endTime) + { + int iRet = -1; + string sql = string.Empty; + + sql += "SELECT COUNT(*) AS number FROM " + Table_of_Transaction + " 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 DB]: Unable to get transaction info: " + e.ToString()); + return -1; + } + } + } + cmd.Dispose(); + return iRet; + } + + + + /////////////////////////////////////////////////////////////////////// + // + // userinfo + // + public bool addUserInfo(UserInfo userInfo) + { + bool bRet = false; + string sql = string.Empty; + + if (userInfo.Avatar==null) return false; + + 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); + + try + { + if (cmd.ExecuteNonQuery() > 0) bRet = true; + cmd.Dispose(); + } + catch (Exception e) + { + m_log.Error("[MONEY DB]: Unable to add user information to database: " + e.ToString()); + bRet = false; + } + + return bRet; + } + + + public UserInfo fetchUserInfo(string userID) + { + UserInfo userInfo = new UserInfo(); + userInfo.UserID = userID; + 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.SimIP = (string)r["simip"]; + userInfo.Avatar = (string)r["avatar"]; + userInfo.PswHash = (string)r["pass"]; + } + catch (Exception e) + { + m_log.Error("[MONEY DB]: Fetching UserInfo failed: " + e.ToString()); + return null; + } + } + else + { + return null; + } + r.Close(); + } + + return userInfo; + } + + + public bool updateUserInfo(UserInfo user) + { + bool bRet = false; + string sql = string.Empty; + + sql += "UPDATE " + Table_of_UserInfo + " SET simip=?simip,avatar=?avatar,pass=?pass WHERE user=?user;"; + MySqlCommand cmd = new MySqlCommand(sql, dbcon); + cmd.Parameters.AddWithValue("?simip", user.SimIP); + cmd.Parameters.AddWithValue("?avatar", user.Avatar); + cmd.Parameters.AddWithValue("?pass", user.PswHash); + cmd.Parameters.AddWithValue("?user", user.UserID); + + if (cmd.ExecuteNonQuery() > 0) bRet = true; + else bRet = false; + cmd.Dispose(); + + return bRet; + } + + + } +} diff --git a/release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs b/release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs new file mode 100644 index 0000000..cd2bae4 --- /dev/null +++ b/release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs @@ -0,0 +1,52 @@ +/* + * 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 OpenSimulator 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. + */ +using System; +using System.Threading; + +namespace OpenSim.Data.MySQL.MySQLMoneyDataWrapper +{ + // This bit of code is from OpenSim.Data.MySQLSuperManager + public class MySQLSuperManager + { + public bool Locked; + private readonly Mutex m_lock = new Mutex(false); + public MySQLMoneyManager Manager; + public string Running; + + public void GetLock() + { + Locked = true; + m_lock.WaitOne(); + } + + public void Release() + { + m_lock.ReleaseMutex(); + Locked = false; + } + } +} diff --git a/release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/Properties/AssemblyInfo.cs b/release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f4a2a82 --- /dev/null +++ b/release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +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 +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs b/release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs new file mode 100644 index 0000000..7ae23ed --- /dev/null +++ b/release/0.8.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs @@ -0,0 +1,160 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +//using System.Linq; +using System.Text; +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_type; + int m_time; + int m_status; + string m_description = string.Empty; + string m_objectID = string.Empty; + string m_regionHandle = string.Empty; + string m_secureCode = string.Empty; + string m_commonName = string.Empty; + + //public TransactionData(string uuid, string sender, string receiver, + // int amount, int time, int status, string description) + //{ + // this.m_uuid = uuid; + // this.m_sender = sender; + // this.m_receiver = receiver; + // this.m_amount = amount; + //} + public UUID TransUUID + { + get { return m_uuid; } + set { m_uuid = value; } + } + public string Sender + { + get { return m_sender; } + set { m_sender = value; } + } + public string Receiver + { + get { return m_receiver; } + set { m_receiver = value; } + } + public int Amount + { + get { return m_amount; } + set { m_amount = value; } + } + public int Type + { + get { return m_type; } + set { m_type = value; } + } + public int Time + { + get { return m_time; } + set { m_time = value; } + } + public int Status + { + get { return m_status; } + set { m_status = value; } + } + public string Description + { + get { return m_description; } + set { m_description = value; } + } + public string ObjectUUID + { + get { return m_objectID; } + set { m_objectID = value; } + } + public string RegionHandle + { + get { return m_regionHandle; } + set { m_regionHandle = value; } + } + public string SecureCode + { + get { return m_secureCode; } + set { m_secureCode = value; } + } + public string CommonName + { + get { return m_commonName; } + set { m_commonName = value; } + } + } + + + public enum Status + { + SUCCESS_STATUS = 0, + PENDING_STATUS = 1, + FAILED_STATUS = 2 + } + + + public class UserInfo + { + string m_userID = string.Empty; + string m_simIP = string.Empty; + string m_avatarName = string.Empty; + string m_passwordHash = string.Empty; + + public string UserID + { + get { return m_userID; } + set { m_userID = value; } + } + + public string SimIP + { + get { return m_simIP; } + set { m_simIP = value; } + } + public string Avatar + { + get { return m_avatarName; } + set { m_avatarName = value; } + } + public string PswHash + { + get { return m_passwordHash; } + set { m_passwordHash = value; } + } + } +} diff --git a/release/0.8.2/src/OpenSim.Grid.MoneyServer/IMoneyDBService.cs b/release/0.8.2/src/OpenSim.Grid.MoneyServer/IMoneyDBService.cs new file mode 100644 index 0000000..b3b6f00 --- /dev/null +++ b/release/0.8.2/src/OpenSim.Grid.MoneyServer/IMoneyDBService.cs @@ -0,0 +1,72 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +//using System.Linq; +using System.Text; +using OpenMetaverse; +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; + + +namespace OpenSim.Grid.MoneyServer +{ + public interface IMoneyDBService + { + int getBalance(string userID); + + bool withdrawMoney(UUID transactionID, string senderID, int amount); + + bool giveMoney(UUID transactionID, string receiverID, int amount); + + bool addTransaction(TransactionData transaction); + + bool addUser(string userID, int balance, int status); + + bool updateTransactionStatus(UUID transactionID, int status, string description); + + bool SetTransExpired(int deadTime); + + bool ValidateTransfer(string secureCode, UUID transactionID); + + TransactionData FetchTransaction(UUID transactionID); + + TransactionData FetchTransaction(string userID, int startTime, int endTime, int lastIndex); + + int getTransactionNum(string userID, int startTime, int endTime); + + bool DoTransfer(UUID transactionUUID); + + bool DoAddMoney(UUID transactionUUID); // Added by Fumi.Iseki + + bool TryAddUserInfo(UserInfo user); + + UserInfo FetchUserInfo(string userID); + + + } +} diff --git a/release/0.8.2/src/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs b/release/0.8.2/src/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs new file mode 100644 index 0000000..9f31284 --- /dev/null +++ b/release/0.8.2/src/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs @@ -0,0 +1,44 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +//using System.Linq; +using System.Text; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; + +namespace OpenSim.Grid.MoneyServer +{ + public interface IMoneyServiceCore + { + BaseHttpServer GetHttpServer(); + Dictionary GetSessionDic(); + Dictionary GetSecureSessionDic(); + Dictionary GetWebSessionDic(); + } +} diff --git a/release/0.8.2/src/OpenSim.Grid.MoneyServer/MoneyDBService.cs b/release/0.8.2/src/OpenSim.Grid.MoneyServer/MoneyDBService.cs new file mode 100644 index 0000000..a0f7cb2 --- /dev/null +++ b/release/0.8.2/src/OpenSim.Grid.MoneyServer/MoneyDBService.cs @@ -0,0 +1,496 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +//using System.Linq; +using System.Text; +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; +using log4net; +using System.Reflection; +using OpenMetaverse; + + +namespace OpenSim.Grid.MoneyServer +{ + class MoneyDBService: IMoneyDBService + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private string m_connect; + private MySQLMoneyManager m_moneyManager; + + // DB manager pool + protected Dictionary m_dbconnections = new Dictionary(); + private int m_maxConnections; + public int m_lastConnect = 0; + + + public MoneyDBService(string connect) + { + m_connect = connect; + Initialise(m_connect,10); + } + + + public MoneyDBService() + { + } + + + public void Initialise(string connectionString,int maxDBConnections) + { + m_maxConnections = maxDBConnections; + if (connectionString != string.Empty) + { + m_moneyManager = new MySQLMoneyManager(connectionString); + //m_log.Info("Creating " + m_maxConnections + " DB connections..."); + for (int i = 0; i < m_maxConnections; i++) + { + //m_log.Info("Connecting to DB... [" + i + "]"); + MySQLSuperManager msm = new MySQLSuperManager(); + msm.Manager = new MySQLMoneyManager(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"); + } + } + + + private MySQLSuperManager GetLockedConnection() + { + int lockedCons = 0; + while (true) + { + m_lastConnect++; + + // Overflow protection + if (m_lastConnect == int.MaxValue) + m_lastConnect = 0; + + MySQLSuperManager x = m_dbconnections[m_lastConnect % m_maxConnections]; + if (!x.Locked) + { + x.GetLock(); + return x; + } + + lockedCons++; + if (lockedCons > m_maxConnections) + { + lockedCons = 0; + System.Threading.Thread.Sleep(1000); // Wait some time before searching them again. + m_log.Debug( + "WARNING: All threads are in use. Probable cause: Something didnt release a mutex properly, or high volume of requests inbound."); + } + } + } + + + public int getBalance(string userID) + { + MySQLSuperManager dbm = GetLockedConnection(); + try + { + return dbm.Manager.getBalance(userID); + } + catch(Exception e) + { + dbm.Manager.Reconnect(); + m_log.Error(e.ToString()); + return 0; + } + finally + { + dbm.Release(); + } + } + + + public bool withdrawMoney(UUID transactionID, string senderID, int amount) + { + MySQLSuperManager dbm = GetLockedConnection(); + try + { + return dbm.Manager.withdrawMoney(transactionID, senderID, amount); + } + catch (Exception e) + { + dbm.Manager.Reconnect(); + m_log.Error(e.ToString()); + return false; + } + finally + { + dbm.Release(); + } + } + + + public bool giveMoney(UUID transactionID, string receiverID, int amount) + { + MySQLSuperManager dbm = GetLockedConnection(); + try + { + return dbm.Manager.giveMoney(transactionID, receiverID, amount); + } + catch (Exception e) + { + dbm.Manager.Reconnect(); + m_log.Error(e.ToString()); + return false; + } + finally + { + dbm.Release(); + } + } + + + public bool addTransaction(TransactionData transaction) + { + MySQLSuperManager dbm = GetLockedConnection(); + try + { + return dbm.Manager.addTransaction(transaction); + } + catch (Exception e) + { + dbm.Manager.Reconnect(); + m_log.Error(e.ToString()); + return false; + } + finally + { + dbm.Release(); + } + } + + + public bool addUser(string userID, int balance, int status) + { + MySQLSuperManager dbm = GetLockedConnection(); + try + { + return dbm.Manager.addUser(userID, balance, status); + } + catch (Exception e) + { + dbm.Manager.Reconnect(); + m_log.Error(e.ToString()); + return false; + } + finally + { + dbm.Release(); + } + } + + + public bool updateTransactionStatus(UUID transactionID, int status, string description) + { + MySQLSuperManager dbm = GetLockedConnection(); + try + { + return dbm.Manager.updateTransactionStatus(transactionID, status, description); + } + catch (Exception e) + { + dbm.Manager.Reconnect(); + m_log.Error(e.ToString()); + return false; + } + finally + { + dbm.Release(); + } + } + + + public bool SetTransExpired(int deadTime) + { + MySQLSuperManager dbm = GetLockedConnection(); + try + { + return dbm.Manager.SetTransExpired(deadTime); + } + catch (Exception e) + { + dbm.Manager.Reconnect(); + m_log.Error(e.ToString()); + return false; + } + finally + { + dbm.Release(); + } + } + + + public bool ValidateTransfer(string secureCode, UUID transactionID) + { + MySQLSuperManager dbm = GetLockedConnection(); + try + { + return dbm.Manager.ValidateTransfer(secureCode, transactionID); + } + catch (Exception e) + { + dbm.Manager.Reconnect(); + m_log.Error(e.ToString()); + return false; + } + finally + { + dbm.Release(); + } + } + + + public TransactionData FetchTransaction(UUID transactionID) + { + MySQLSuperManager dbm = GetLockedConnection(); + try + { + return dbm.Manager.FetchTransaction(transactionID); + } + catch (Exception e) + { + dbm.Manager.Reconnect(); + m_log.Error(e.ToString()); + return null; + } + finally + { + dbm.Release(); + } + } + + + public TransactionData FetchTransaction(string userID, int startTime, int endTime, int lastIndex) + { + MySQLSuperManager dbm = GetLockedConnection(); + TransactionData[] arrTransaction; + try + { + if (lastIndex < 0) + { + arrTransaction = dbm.Manager.FetchTransaction(userID, startTime, endTime, 0, 1); + if (arrTransaction.Length > 0) + { + return arrTransaction[0]; + } + else + return null; + } + else + { + uint index = Convert.ToUInt32(lastIndex); + arrTransaction = dbm.Manager.FetchTransaction(userID, startTime, endTime, index + 1, 1); + if (arrTransaction.Length > 0) + { + return arrTransaction[0]; + } + else + return null; + } + } + catch (Exception e) + { + dbm.Manager.Reconnect(); + m_log.Error(e.ToString()); + return null; + } + finally + { + dbm.Release(); + } + } + + + public bool DoTransfer(UUID transactionUUID) + { + 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) + { + addUser(transaction.Receiver, 0, (int)Status.SUCCESS_STATUS); + } + if (giveMoney(transactionUUID, transaction.Receiver, transaction.Amount)) + return true; + else // give money to receiver failed. + { + 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); + } + } + } + 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()); + } + return false; + } + + + // by Fumi.Iseki + public bool DoAddMoney(UUID transactionUUID) + { + 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) + { + addUser(transaction.Receiver, 0, (int)Status.SUCCESS_STATUS); + } + + if (giveMoney(transactionUUID, transaction.Receiver, transaction.Amount)) 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()); + } + return false; + } + + + public bool TryAddUserInfo(UserInfo user) + { + MySQLSuperManager dbm = GetLockedConnection(); + try + { + if (dbm.Manager.fetchUserInfo(user.UserID) != null) + { + m_log.InfoFormat("[MONEY DB]: Found user \"{0}\", now update information", user.Avatar); + if (m_moneyManager.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; + } + return false; + } + catch (Exception e) + { + dbm.Manager.Reconnect(); + // Fumi.Iseki + m_moneyManager.Reconnect(); + m_log.Error(e.ToString()); + return false; + } + finally + { + dbm.Release(); + } + } + + + public UserInfo FetchUserInfo(string userID) + { + MySQLSuperManager dbm = GetLockedConnection(); + try + { + return dbm.Manager.fetchUserInfo(userID); + } + catch (Exception e) + { + dbm.Manager.Reconnect(); + m_log.Error(e.ToString()); + return null; + } + finally + { + dbm.Release(); + } + } + + + public int getTransactionNum(string userID, int startTime, int endTime) + { + MySQLSuperManager dbm = GetLockedConnection(); + try + { + return dbm.Manager.getTransactionNum(userID,startTime,endTime); + } + catch (Exception e) + { + dbm.Manager.Reconnect(); + m_log.Error(e.ToString()); + return -1; + } + finally + { + dbm.Release(); + } + } + } +} diff --git a/release/0.8.2/src/OpenSim.Grid.MoneyServer/MoneyServerBase.cs b/release/0.8.2/src/OpenSim.Grid.MoneyServer/MoneyServerBase.cs new file mode 100644 index 0000000..d4f92d1 --- /dev/null +++ b/release/0.8.2/src/OpenSim.Grid.MoneyServer/MoneyServerBase.cs @@ -0,0 +1,323 @@ +/* + * 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. + */ + +using System; +using System.IO; +using System.Collections.Generic; +using System.Text; +using System.Net; +using System.Net.Security; +using System.Reflection; +using System.Timers; +//using System.Security.Authentication; +//using System.Security.Cryptography; +//using System.Security.Cryptography.X509Certificates; + +using HttpServer; +using Nini.Config; +using log4net; + +using OpenSim.Framework; +using OpenSim.Framework.Console; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Data; + +using NSL.Certificate.Tools; + + + +namespace OpenSim.Grid.MoneyServer +{ + class MoneyServerBase : BaseOpenSimServer, IMoneyServiceCore + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private string connectionString = string.Empty; + private uint m_moneyServerPort = 8008; + private string m_hostName = "localhost"; + + 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; + private int MAX_DB_CONNECTION; + + private MoneyXmlRpcModule m_moneyXmlRpcModule; + private MoneyDBService m_moneyDBService; + + private NSLCertificateVerify m_certVerify = new NSLCertificateVerify(); // クライアント認証用 + + private Dictionary m_sessionDic = new Dictionary(); + private Dictionary m_secureSessionDic = new Dictionary(); + private Dictionary m_webSessionDic = new Dictionary(); + + IConfig m_config; + + + public MoneyServerBase() + { + m_console = new LocalConsole("Money "); +// m_console = new CommandConsole("Money "); + MainConsole.Instance = m_console; + } + + + public void Work() + { + //m_console.Notice("Enter help for a list of commands\n"); + + //The timer checks the transactions table every 60 seconds + Timer checkTimer = new Timer(); + checkTimer.Interval = 60*1000; + checkTimer.Enabled = true; + checkTimer.Elapsed += new ElapsedEventHandler(CheckTransaction); + checkTimer.Start(); + + while (true) + { + m_console.Prompt(); + } + } + + + /// + /// Check the transactions table, set expired transaction state to failed + /// + private void CheckTransaction(object sender, ElapsedEventArgs e) + { + long ticksToEpoch = new DateTime(1970, 1, 1).Ticks; + int unixEpochTime =(int) ((DateTime.Now.Ticks - ticksToEpoch )/10000000); + int deadTime = unixEpochTime - DEAD_TIME; + m_moneyDBService.SetTransExpired(deadTime); + } + + + protected override void StartupSpecific() + { + m_log.Info("[MONEY SERVER]: Setup HTTP Server process"); + + ReadIniConfig(); + + try { + //HttpContextFactory.ClientCertificateValidationCallback = null; + Type typeHttpContextFactory = typeof(HttpContextFactory); + FieldInfo finfo = typeHttpContextFactory.GetField("ClientCertificateValidationCallback"); + if (finfo!=null) finfo.SetValue(new HttpContextFactory(null, 0, null), null); + + if (m_certFilename!="") + { + m_httpServer = new BaseHttpServer(m_moneyServerPort, true, m_certFilename, m_certPassword); + if (m_checkClientCert) { + if (finfo!=null) { + //HttpContextFactory.ClientCertificateValidationCallback = m_certVerify.ValidateClientCertificate; + finfo.SetValue(new HttpContextFactory(null, 0, null), (RemoteCertificateValidationCallback)m_certVerify.ValidateClientCertificate); + m_log.Info("[MONEY SERVER]: Set RemoteCertificateValidationCallback"); + } + else { + m_log.Error("[MONEY SERVER]: StartupSpecific: CheckClientCert is true. But this MoneyServer does not support CheckClientCert!!"); + + } + } + } + else + { + m_httpServer = new BaseHttpServer(m_moneyServerPort, false); + } + + 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); + } + + //TODO : Add some console commands here + } + + + + protected void ReadIniConfig() + { + MoneyServerConfigSource moneyConfig = new MoneyServerConfigSource(); + Config = moneyConfig.m_config; // for base.StartupSpecific() + + 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", 10); + + connectionString = "Server=" + sqlserver + ";Port=" + port + ";Database=" + database + ";User ID=" + + username + ";Password=" + password + ";Pooling=" + pooling + ";"; + + // [MoneyServer] + m_config = moneyConfig.m_config.Configs["MoneyServer"]; + DEAD_TIME = m_config.GetInt ("ExpiredTime", 120); + m_hostName = m_config.GetString("HostName", "localhost"); // be not used + + // サーバ証明書 + m_certFilename = m_config.GetString("ServerCertFilename", ""); + m_certPassword = m_config.GetString("ServerCertPassword", ""); + if (m_certFilename!="") { + m_log.Info("[MONEY SERVER]: ReadIniConfig: Execute HTTPS comunication. Cert file is " + m_certFilename); + } + + // クライアント認証 + string checkcert = m_config.GetString("CheckClientCert", "false"); + if (checkcert.ToLower()=="true") m_checkClientCert = true; + + m_cacertFilename = m_config.GetString("CACertFilename", ""); + m_clcrlFilename = m_config.GetString("ClientCrlFilename", ""); + // + 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); + } + } + } + + catch (Exception) + { + m_log.Error("[MONEY SERVER]: ReadIniConfig: Fail to setup configure. Please check MoneyServer.ini. Exit"); + Environment.Exit(1); + } + } + + + // 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); + + m_moneyXmlRpcModule = new MoneyXmlRpcModule(); + m_moneyXmlRpcModule.Initialise(m_version, m_config, m_moneyDBService, this); + m_moneyXmlRpcModule.PostInitialise(); + } + + + 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); + } + else + { + //TODO: create default configuration. + //m_config = DefaultConfig(); + } + } + + public void Save(string path) + { + m_config.Save(path); + } + + } +} diff --git a/release/0.8.2/src/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs b/release/0.8.2/src/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs new file mode 100644 index 0000000..fa05e6c --- /dev/null +++ b/release/0.8.2/src/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs @@ -0,0 +1,1761 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Reflection; +using System.Collections; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +using log4net; +using Nini.Config; +using Nwc.XmlRpc; + +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; +using OpenSim.Modules.Currency; + +using NSL.Network.XmlRpc; +using NSL.Certificate.Tools; + + +namespace OpenSim.Grid.MoneyServer +{ + class MoneyXmlRpcModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private int m_defaultBalance = 0; + // + private bool m_forceTransfer = false; + private string m_bankerAvatar = ""; + + private bool m_scriptSendMoney = false; + private string m_scriptAccessKey = ""; + private string m_scriptIPaddress = "127.0.0.1"; + + private bool m_checkServerCert = false; + private string m_cacertFilename = ""; + + private string m_certFilename = ""; + private string m_certPassword = ""; + private X509Certificate2 m_cert = null; + + private string m_sslCommonName = ""; + + private NSLCertificateVerify m_certVerify = new NSLCertificateVerify(); // サーバ認証用 + + + // Update Balance Messages + 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 L${0} from {1}."; + private string m_BalanceMessageGetMoney = "Got the Money L${0} from {1}."; + private string m_BalanceMessageBuyMoney = "Bought the Money L${0}."; + private string m_BalanceMessageReceiveMoney = "Received L${0} from System."; + private string m_BalanceMessageRollBack = "RollBack the Transaction: L${0} from/to {1}."; + + + const int MONEYMODULE_REQUEST_TIMEOUT = 30 * 1000; //30 seconds + private long TicksToEpoch = new DateTime(1970, 1, 1).Ticks; + + private IMoneyDBService m_moneyDBService; + private IMoneyServiceCore m_moneyCore; + + protected IConfig m_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; + + + public MoneyXmlRpcModule() + { + } + + + public void Initialise(string opensimVersion, IConfig config, IMoneyDBService moneyDBService, IMoneyServiceCore moneyCore) + { + m_opensimVersion = opensimVersion; + m_moneyDBService = moneyDBService; + m_moneyCore = moneyCore; + m_config = config; // [MoneyServer] Section + + m_defaultBalance = m_config.GetInt("DefaultBalance", 1000); + + string ftrans = m_config.GetString ("enableForceTransfer", "false"); + if (ftrans.ToLower()=="true") m_forceTransfer = true; + m_forceTransfer = m_config.GetBoolean("EnableForceTransfer", m_forceTransfer); + + string banker = m_config.GetString("BankerAvatar", ""); + m_bankerAvatar = banker.ToLower(); + + string sendmoney = m_config.GetString ("enableScriptSendMoney", "false"); + if (sendmoney.ToLower()=="true") m_scriptSendMoney = true; + m_scriptSendMoney = m_config.GetBoolean("EnableScriptSendMoney", m_scriptSendMoney); + + m_scriptAccessKey = m_config.GetString("MoneyScriptAccessKey", ""); + m_scriptIPaddress = m_config.GetString("MoneyScriptIPaddress", "127.0.0.1"); + + // クライアント証明書 + m_certFilename = m_config.GetString("ClientCertFilename", ""); + m_certPassword = m_config.GetString("ClientCertPassword", ""); + if (m_certFilename!="") { + m_cert = new X509Certificate2(m_certFilename, m_certPassword); + m_log.Info("[MONEY RPC]: Initialise: Issue Authentication of Client. Cert file is " + m_cacertFilename); + } + + // サーバ認証 + string checkcert = m_config.GetString("CheckServerCert", "false"); + if (checkcert.ToLower()=="true") m_checkServerCert = true; + + m_cacertFilename = m_config.GetString("CACertFilename", ""); + if (m_cacertFilename!="") { + m_certVerify.SetPrivateCA(m_cacertFilename); + ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(m_certVerify.ValidateServerCertificate); + m_log.Info("[MONEY RPC]: Initialise: Execute Authentication of Server. CA file is " + m_cacertFilename); + } + else { + m_checkServerCert = false; + ServicePointManager.ServerCertificateValidationCallback = null; + } + + // Update Balance Messages + m_BalanceMessageLandSale = m_config.GetString("BalanceMessageLandSale", m_BalanceMessageLandSale); + m_BalanceMessageRcvLandSale = m_config.GetString("BalanceMessageRcvLandSale", m_BalanceMessageRcvLandSale); + m_BalanceMessageSendGift = m_config.GetString("BalanceMessageSendGift", m_BalanceMessageSendGift); + m_BalanceMessageReceiveGift = m_config.GetString("BalanceMessageReceiveGift", m_BalanceMessageReceiveGift); + m_BalanceMessagePayCharge = m_config.GetString("BalanceMessagePayCharge", m_BalanceMessagePayCharge); + m_BalanceMessageBuyObject = m_config.GetString("BalanceMessageBuyObject", m_BalanceMessageBuyObject); + m_BalanceMessageGetMoney = m_config.GetString("BalanceMessageGetMoney", m_BalanceMessageGetMoney); + m_BalanceMessageBuyMoney = m_config.GetString("BalanceMessageBuyMoney", m_BalanceMessageBuyMoney); + m_BalanceMessageReceiveMoney = m_config.GetString("BalanceMessageReceiveMoney", m_BalanceMessageReceiveMoney); + m_BalanceMessageRollBack = m_config.GetString("BalanceMessageRollBack", m_BalanceMessageRollBack); + + m_sessionDic = m_moneyCore.GetSessionDic(); + m_secureSessionDic = m_moneyCore.GetSecureSessionDic(); + m_webSessionDic = m_moneyCore.GetWebSessionDic(); + RegisterHandlers(); + } + + + public void PostInitialise() + { + } + + + 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("SendMoneyBalance", handleSendMoneyBalance); // added + + // 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); + } + + + // + // + // + public string GetSSLCommonName(XmlRpcRequest request) + { + if (request.Params.Count>4) { + m_sslCommonName = (string)request.Params[4]; + } + else { + m_sslCommonName = ""; + } + return m_sslCommonName; + } + + + // + public string GetSSLCommonName() + { + return m_sslCommonName; + } + + + + + /// + /// Get the user balance when user entering a parcel. + /// + /// + /// + public XmlRpcResponse handleClientLogin(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleClientLogin:"); + + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + string clientUUID = string.Empty; + string sessionID = string.Empty; + string secureID = string.Empty; + string simIP = string.Empty; + string avatarName = string.Empty; + int balance = 0; + + response.Value = responseData; + 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("openSimServIP")) simIP = (string)requestData["openSimServIP"]; + if (requestData.ContainsKey("userName")) avatarName = (string)requestData["userName"]; + + //m_log.InfoFormat("[MONEY RPC]: handleClientLogin: client UUID is {0}", clientUUID); + + if (avatarName=="") { + responseData["success"] = false; + responseData["clientBalance"] = 0; + 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 + { + //m_log.InfoFormat("[MONEY RPC]: handleClientLogin: User {0} has logged in, getting balance...", clientUUID); + balance = m_moneyDBService.getBalance(clientUUID); + //add user if not exist + if (balance==-1) + { + if (m_moneyDBService.addUser(clientUUID, m_defaultBalance, 0)) + { + responseData["success"] = true; + responseData["description"] = "add user successfully"; + responseData["clientBalance"] = m_defaultBalance; + } + else + { + responseData["success"] = false; + responseData["description"] = "add user failed"; + responseData["clientBalance"] = 0; + } + } + else if (balance >= 0) //Success + { + responseData["success"] = true; + responseData["description"] = "get user balance successfully"; + responseData["clientBalance"] = balance; + } + UserInfo user = new UserInfo(); + user.UserID = clientUUID; + user.SimIP = simIP; + user.Avatar = avatarName; + //TODO: Add password protection here + user.PswHash = UUID.Zero.ToString(); + + if (!m_moneyDBService.TryAddUserInfo(user)) + { + m_log.ErrorFormat("[MONEY RPC]: handleClientLogin: Unable to refresh information for user \"{0}\" in DB", avatarName); + responseData["success"] = false; + responseData["description"] = "Update or add user information to db failed"; + responseData["clientBalance"] = balance; + } + return response; + + } + catch (Exception e) + { + m_log.ErrorFormat("[MONEY RPC]: handleClientLogin: Can't get balance of user {0}", clientUUID); + responseData["success"] = false; + responseData["description"] = "Exception occured" + e.ToString(); + responseData["clientBalance"] = 0; + } + return response; + + } + + + + // + // deleted using ASP.NET by Fumi.Iseki + // + /// + /// handle incoming transaction + /// + /// + /// + public XmlRpcResponse handleTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: 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 regionHandle = string.Empty; + string description = "Newly added on"; + + 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 = (Int32)requestData["amount"]; + if (requestData.ContainsKey("objectID")) objectID = (string)requestData["objectID"]; + if (requestData.ContainsKey("regionHandle")) regionHandle = (string)requestData["regionHandle"]; + if (requestData.ContainsKey("transactionType")) transactionType = (Int32)requestData["transactionType"]; + if (requestData.ContainsKey("description")) description = (string)requestData["description"]; + + if (m_sessionDic.ContainsKey(senderID) && m_secureSessionDic.ContainsKey(senderID)) + { + if (m_sessionDic[senderID]==senderSessionID && m_secureSessionDic[senderID]==senderSecureSessionID) + { + m_log.InfoFormat("[MONEY RPC]: handleTransaction: Transfering money from {0} to {1}", senderID, receiverID); + int time = (int)((DateTime.Now.Ticks - TicksToEpoch) / 10000000); + try + { + TransactionData transaction = new TransactionData(); + transaction.TransUUID = transactionUUID; + transaction.Sender = senderID; + transaction.Receiver = receiverID; + transaction.Amount = amount; + transaction.ObjectUUID = objectID; + transaction.RegionHandle = regionHandle; + transaction.Type = transactionType; + transaction.Time = time; + transaction.SecureCode = UUID.Random().ToString(); + transaction.Status = (int)Status.PENDING_STATUS; + transaction.CommonName = GetSSLCommonName(); + transaction.Description = description + " " + DateTime.Now.ToString(); + + UserInfo rcvr = m_moneyDBService.FetchUserInfo(receiverID); + if (rcvr==null) + { + m_log.ErrorFormat("[MONEY RPC]: handleTransaction: Receive User is not yet in DB {0}", receiverID); + responseData["success"] = false; + return response; + } + + bool result = m_moneyDBService.addTransaction(transaction); + if (result) + { + UserInfo user = m_moneyDBService.FetchUserInfo(senderID); + if (user!=null) + { + if (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; + } + else if (transaction.Type==(int)TransactionType.ObjectPays) { // ObjectGiveMoney + rcv_message = m_BalanceMessageGetMoney; + } + + responseData["success"] = NotifyTransfer(transactionUUID, snd_message, rcv_message); + } + else + { + responseData["success"] = true; // No messages for L$0 object. by Fumi.Iseki + } + return response; + } + } + else // add transaction failed + { + m_log.ErrorFormat("[MONEY RPC]: handleTransaction: Add transaction for user {0} failed", senderID); + } + + responseData["success"] = false; + return response; + } + catch (Exception e) + { + m_log.Error("[MONEY RPC]: handleTransaction: Exception occurred while adding transaction " + e.ToString()); + responseData["success"] = false; + return response; + } + + } + + } + + m_log.Error("[MONEY RPC]: handleTransaction: Session authentication failure for sender " + senderID); + responseData["success"] = false; + responseData["message"] = "Session check failure, please re-login later!"; + return response; + } + + + + // + // added by Fumi.Iseki + // + /// + /// handle incoming force transaction. no check senderSessionID and senderSecureSessionID + /// + /// + /// + public XmlRpcResponse handleForceTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleForceTransaction:"); + + 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 regionHandle = string.Empty; + string description = "Newly added on"; + + UUID transactionUUID = UUID.Random(); + + // + if (!m_forceTransfer) + { + m_log.Error("[MONEY RPC]: handleForceTransaction: Not allowed force transfer of Money. Set enableForceTransfer at [MoneyServer] to true in MoneyServer.ini"); + responseData["success"] = false; + 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 = (Int32)requestData["amount"]; + if (requestData.ContainsKey("objectID")) objectID = (string)requestData["objectID"]; + if (requestData.ContainsKey("regionHandle")) regionHandle = (string)requestData["regionHandle"]; + if (requestData.ContainsKey("transactionType")) transactionType = (Int32)requestData["transactionType"]; + if (requestData.ContainsKey("description")) description = (string)requestData["description"]; + + m_log.InfoFormat("[MONEY RPC]: handleForceTransaction: Force transfering money from {0} to {1}", senderID, receiverID); + int time = (int)((DateTime.Now.Ticks - TicksToEpoch) / 10000000); + + try + { + TransactionData transaction = new TransactionData(); + transaction.TransUUID = transactionUUID; + transaction.Sender = senderID; + transaction.Receiver = receiverID; + transaction.Amount = amount; + transaction.ObjectUUID = objectID; + transaction.RegionHandle = regionHandle; + transaction.Type = transactionType; + transaction.Time = time; + transaction.SecureCode = UUID.Random().ToString(); + transaction.Status = (int)Status.PENDING_STATUS; + transaction.CommonName = GetSSLCommonName(); + transaction.Description = description + " " + DateTime.Now.ToString(); + + UserInfo rcvr = m_moneyDBService.FetchUserInfo(receiverID); + if (rcvr==null) + { + m_log.ErrorFormat("[MONEY RPC]: handleForceTransaction: Force receive User is not yet in DB {0}", receiverID); + responseData["success"] = false; + return response; + } + + bool result = m_moneyDBService.addTransaction(transaction); + if (result) + { + UserInfo user = m_moneyDBService.FetchUserInfo(senderID); + if (user!=null) + { + if (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; + } + else if (transaction.Type==(int)TransactionType.ObjectPays) { // ObjectGiveMoney + rcv_message = m_BalanceMessageGetMoney; + } + + responseData["success"] = NotifyTransfer(transactionUUID, snd_message, rcv_message); + } + else + { + responseData["success"] = true; // No messages for L$0 object. by Fumi.Iseki + } + return response; + } + } + else // add transaction failed + { + m_log.ErrorFormat("[MONEY RPC]: handleForceTransaction: Add force transaction for user {0} failed", senderID); + } + + responseData["success"] = false; + return response; + } + catch (Exception e) + { + m_log.Error("[MONEY RPC]: handleForceTransaction: Exception occurred while adding force transaction " + e.ToString()); + responseData["success"] = false; + return response; + } + } + + + + // + // added by Fumi.Iseki + // + /// + /// handle adding money transaction. + /// + /// + /// + public XmlRpcResponse handleAddBankerMoney(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleAddBankerMoney:"); + + 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 description = "Add Money to Avatar on"; + + UUID transactionUUID = UUID.Random(); + + responseData["success"] = false; + + if (requestData.ContainsKey("bankerID")) bankerID = (string)requestData["bankerID"]; + if (requestData.ContainsKey("amount")) amount = (Int32)requestData["amount"]; + if (requestData.ContainsKey("regionHandle")) regionHandle = (string)requestData["regionHandle"]; + if (requestData.ContainsKey("transactionType")) transactionType = (Int32)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 RPC]: handleAddBankerMoney: Not allowed add money to avatar!!"); + m_log.Error("[MONEY RPC]: 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 RPC]: handleAddBankerMoney: Add money to avatar {0}", bankerID); + int time = (int)((DateTime.Now.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.Type = transactionType; + transaction.Time = time; + transaction.SecureCode = UUID.Random().ToString(); + transaction.Status = (int)Status.PENDING_STATUS; + transaction.CommonName = GetSSLCommonName(); + transaction.Description = description + " " + DateTime.Now.ToString(); + + UserInfo rcvr = m_moneyDBService.FetchUserInfo(bankerID); + if (rcvr==null) + { + m_log.ErrorFormat("[MONEY RPC]: handleAddBankerMoney: Avatar is not yet in DB {0}", bankerID); + return response; + } + + bool result = m_moneyDBService.addTransaction(transaction); + if (result) + { + if (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 RPC]: 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 + { + responseData["success"] = true; // No messages for L$0 add + } + return response; + } + else // add transaction failed + { + m_log.ErrorFormat("[MONEY RPC]: handleAddBankerMoney: Add force transaction for user {0} failed", senderID); + } + + return response; + } + catch (Exception e) + { + m_log.Error("[MONEY RPC]: handleAddBankerMoney: Exception occurred while adding money transaction " + e.ToString()); + return response; + } + } + + + + // + // added by Fumi.Iseki + // + /// + /// handle sending money transaction. + /// + /// + /// + public XmlRpcResponse handleSendMoneyBalance(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleSendMoneyBalance:"); + + 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 avatarID = string.Empty; + string description = "Send Money to Avatar on"; + string clientIP = remoteClient.Address.ToString(); + string secretCode = string.Empty; + + UUID transactionUUID = UUID.Random(); + + responseData["success"] = false; + + if (!m_scriptSendMoney || m_scriptAccessKey=="") + { + m_log.Error("[MONEY RPC]: handleSendMoneyBalance: Not allowed send money to avatar!!"); + m_log.Error("[MONEY RPC]: handleSendMoneyBalance: Set enableScriptSendMoney and MoneyScriptAccessKey at [MoneyServer] in MoneyServer.ini"); + responseData["message"] = "not allowed set money to avatar!"; + return response; + } + + if (requestData.ContainsKey("avatarID")) avatarID = (string)requestData["avatarID"]; + if (requestData.ContainsKey("amount")) amount = (Int32)requestData["amount"]; + if (requestData.ContainsKey("transactionType")) transactionType = (Int32)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 RPC]: handleSendMoneyBalance: Not allowed send money to avatar!!"); + m_log.Error("[MONEY RPC]: handleSendMoneyBalance: Not match Script Key"); + responseData["message"] = "not allowed send money to avatar! not match Script Key"; + return response; + } + + m_log.InfoFormat("[MONEY RPC]: handleSendMoneyBalance: Send money to avatar {0}", avatarID); + int time = (int)((DateTime.Now.Ticks - TicksToEpoch) / 10000000); + + try + { + TransactionData transaction = new TransactionData(); + transaction.TransUUID = transactionUUID; + transaction.Sender = senderID; + transaction.Receiver = avatarID; + 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.Now.ToString(); + + UserInfo rcvr = m_moneyDBService.FetchUserInfo(avatarID); + if (rcvr==null) + { + m_log.ErrorFormat("[MONEY RPC]: handleSendMoneyBalance: Avatar is not yet in DB: {0}", avatarID); + return response; + } + + bool result = m_moneyDBService.addTransaction(transaction); + if (result) + { + if (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 RPC]: handleSendMoneyBalance: Sending money finished successfully, now update balance {0}", + transactionUUID.ToString()); + string message = string.Format(m_BalanceMessageReceiveMoney, amount, "SYSTEM"); + UpdateBalance(transaction.Receiver, message); + responseData["success"] = true; + } + } + } + else + { + responseData["success"] = true; // No messages for L$0 add + } + return response; + } + else // add transaction failed + { + m_log.ErrorFormat("[MONEY RPC]: handleSendMoneyBalance: Add force transaction for user {0} failed", senderID); + } + + return response; + } + catch (Exception e) + { + m_log.Error("[MONEY RPC]: handleSendMoneyBalance: Exception occurred while adding money transaction " + e.ToString()); + return response; + } + } + + + + // + // added by Fumi.Iseki + // + /// + /// handle pay charge transaction. no check receiver information. + /// + /// + /// + public XmlRpcResponse handlePayMoneyCharge(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handlePayMoneyCharge:"); + + 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 regionHandle = string.Empty; + string description = "Pay Charge on"; + + UUID transactionUUID = UUID.Random(); + + 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 = (Int32)requestData["amount"]; + if (requestData.ContainsKey("regionHandle")) regionHandle = (string)requestData["regionHandle"]; + if (requestData.ContainsKey("transactionType")) transactionType = (Int32)requestData["transactionType"]; + if (requestData.ContainsKey("description")) description = (string)requestData["description"]; + + if (m_sessionDic.ContainsKey(senderID) && m_secureSessionDic.ContainsKey(senderID)) + { + if (m_sessionDic[senderID]==senderSessionID && m_secureSessionDic[senderID]==senderSecureSessionID) + { + m_log.InfoFormat("[MONEY RPC]: handlePayMoneyCharge: Pay from {0}", senderID); + int time = (int)((DateTime.Now.Ticks - TicksToEpoch) / 10000000); + try + { + TransactionData transaction = new TransactionData(); + transaction.TransUUID = transactionUUID; + transaction.Sender = senderID; + transaction.Receiver = receiverID; + transaction.Amount = amount; + transaction.ObjectUUID = objectID; + transaction.RegionHandle = regionHandle; + transaction.Type = transactionType; + transaction.Time = time; + transaction.SecureCode = UUID.Random().ToString(); + transaction.Status = (int)Status.PENDING_STATUS; + transaction.CommonName = GetSSLCommonName(); + transaction.Description = description + " " + DateTime.Now.ToString(); + + bool result = m_moneyDBService.addTransaction(transaction); + if (result) + { + UserInfo user = m_moneyDBService.FetchUserInfo(senderID); + if (user!=null) + { + if (amount!=0) + { + string message = string.Format(m_BalanceMessagePayCharge, amount, "SYSTEM"); + responseData["success"] = NotifyTransfer(transactionUUID, message, ""); + } + else + { + responseData["success"] = true; // No messages for L$0 object. by Fumi.Iseki + } + return response; + } + } + else // add transaction failed + { + m_log.ErrorFormat("[MONEY RPC]: handlePayMoneyCharge: Pay money transaction for user {0} failed", senderID); + } + + responseData["success"] = false; + return response; + } + catch (Exception e) + { + m_log.Error("[MONEY RPC]: handlePayMoneyCharge: Exception occurred while pay money transaction " + e.ToString()); + responseData["success"] = false; + return response; + } + + } + + } + + m_log.Error("[MONEY RPC]: handlePayMoneyCharge: Session authentication failure for sender " + senderID); + responseData["success"] = false; + responseData["message"] = "Session check failure, please re-login later!"; + return response; + } + + + + // + // added by Fumi.Iseki + // + /// + /// Continue transaction with no confirm. + /// + /// + /// + public bool NotifyTransfer(UUID transactionUUID, string msg2sender, string msg2receiver) + { + //m_log.InfoFormat("[MONEY RPC]: 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 RPC]: NotifyTransfer: Transaction Type = {0}", transaction.Type); + //m_log.InfoFormat("[MONEY RPC]: 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) return true; + 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); + 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); + UpdateBalance(transaction.Receiver, rcv_message); + } + + // Notify to sender? + if (transaction.Type==(int)TransactionType.PayObject) + { + //m_log.InfoFormat("[MONEY RPC]: 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["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 RPC]: NotifyTransfer: User {0} can't get the object, rolling back", transaction.Sender); + if (RollBackTransaction(transaction)) + { + m_log.ErrorFormat("[MONEY RPC]: NotifyTransfer: Transaction {0} failed but roll back succeeded", transactionUUID.ToString()); + } + else + { + m_log.ErrorFormat("[MONEY RPC]: NotifyTransfer: Transaction {0} failed and roll back failed as well", + transactionUUID.ToString()); + } + } + else + { + m_log.InfoFormat("[MONEY RPC]: NotifyTransfer: Transaction {0} finished successfully", transactionUUID.ToString()); + return true; + } + } + } + return false; + } + return true; + } + + } + m_log.ErrorFormat("[MONEY RPC]: NotifyTransfer: Transaction {0} failed", transactionUUID.ToString()); + } + catch (Exception e) + { + m_log.ErrorFormat("[MONEY RPC]: NotifyTransfer: exception occurred when transaction {0}: {1}", transactionUUID.ToString(), e.ToString()); + } + return false; + } + + + + /// + /// Get the user balance. + /// + /// + /// + public XmlRpcResponse handleGetBalance(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleGetBalance:"); + + 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; + + 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 RPC]: 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 + { + if (m_moneyDBService.addUser(clientUUID, m_defaultBalance, 0)) + { + responseData["success"] = true; + responseData["description"] = "add user successfully"; + responseData["clientBalance"] = m_defaultBalance; + } + else + { + responseData["success"] = false; + responseData["description"] = "add user failed"; + responseData["clientBalance"] = 0; + } + } + + else if (balance >= 0) + { + responseData["success"] = true; + responseData["clientBalance"] = balance; + } + return response; + } + catch (Exception e) + { + m_log.ErrorFormat("[MONEY RPC]: handleGetBalance: Can't get balance for user {0}, Exception {1}", clientUUID, e.ToString()); + } + + } + } + + m_log.Error("[MONEY RPC]: handleGetBalance: Session authentication failed when getting balance for user " + clientUUID); + + responseData["success"] = false; + responseData["description"] = "Session check failure, please re-login"; + return response; + } + + + + public XmlRpcResponse handleClientLogout(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleClientLogout:"); + + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + string clientUUID = string.Empty; + + response.Value = responseData; + if (requestData.ContainsKey("clientUUID")) clientUUID = (string)requestData["clientUUID"]; + + //m_log.InfoFormat("[MONEY RPC]: 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 RPC]: handleClientLogout: Failed to delete user session " + e.ToString() ); + responseData["success"] = false; + } + responseData["success"] = true; + + return response; + } + + + + /// + /// 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, string uri) + { + m_log.InfoFormat("[MONEY RPC]: genericCurrencyXMLRPCRequest: to {0}", uri); + + if (reqParams.Count<=0 || string.IsNullOrEmpty(method)) return null; + + if (m_checkServerCert) + { + if (!uri.StartsWith("https://")) + { + m_log.InfoFormat("[MONEY RPC]: 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 RPC]: genericCurrencyXMLRPCRequest: Invalid Region Server URL: {0}", uri); + return null; + } + } + + + ArrayList arrayParams = new ArrayList(); + arrayParams.Add(reqParams); + XmlRpcResponse moneyServResp = null; + try + { + //XmlRpcRequest moneyModuleReq = new XmlRpcRequest(method, arrayParams); + //moneyServResp = moneyModuleReq.Send(uri, MONEYMODULE_REQUEST_TIMEOUT); + NSLXmlRpcRequest moneyModuleReq = new NSLXmlRpcRequest(method, arrayParams); + moneyServResp = moneyModuleReq.certSend(uri, m_cert, m_checkServerCert, MONEYMODULE_REQUEST_TIMEOUT); + } + catch (Exception ex) + { + m_log.ErrorFormat("[MONEY RPC]: genericCurrencyXMLRPCRequest: Unable to connect to Region Server {0}", uri); + m_log.ErrorFormat("[MONEY RPC]: 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.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; + } + + + + /// + /// Update the client balance.We don't care about the result. + /// + /// + private void UpdateBalance(string userID, string message) + { + //m_log.InfoFormat("[MONEY RPC]: UpdateBalance: ID = {0}, Message = {1}", userID, message); + + string sessionID = string.Empty; + string secureID = string.Empty; + + if (m_sessionDic.ContainsKey(userID) && m_secureSessionDic.ContainsKey(userID)) + { + sessionID = m_sessionDic[userID]; + secureID = m_secureSessionDic[userID]; + + Hashtable requestTable = new Hashtable(); + requestTable["clientUUID"] = userID; + requestTable["clientSessionID"] = sessionID; + requestTable["clientSecureSessionID"] = secureID; + requestTable["Balance"] = m_moneyDBService.getBalance(userID); + if (message!="") requestTable["Message"] = message; + + UserInfo user = m_moneyDBService.FetchUserInfo(userID); + if (user!=null) { + genericCurrencyXMLRPCRequest(requestTable, "UpdateBalance", user.SimIP); + //m_log.InfoFormat("[MONEY RPC]: UpdateBalance: Sended UpdateBalance Request to {0}", user.SimIP.ToString()); + } + } + } + + + + /// + /// RollBack the transaction if user failed to get the object paid + /// + /// + /// + protected bool RollBackTransaction(TransactionData transaction) + { + //m_log.InfoFormat("[MONEY RPC]: RollBackTransaction:"); + + if(m_moneyDBService.withdrawMoney(transaction.TransUUID, transaction.Receiver, transaction.Amount)) + { + if(m_moneyDBService.giveMoney(transaction.TransUUID, transaction.Sender, transaction.Amount)) + { + m_log.InfoFormat("[MONEY RPC]: RollBackTransaction: Transaction {0} 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); + string rcv_message = string.Format(m_BalanceMessageRollBack, transaction.Amount, senderName); + + if (transaction.Sender!=transaction.Receiver) UpdateBalance(transaction.Sender, snd_message); + UpdateBalance(transaction.Receiver, rcv_message); + return true; + } + } + return false; + } + + + + // + public XmlRpcResponse handleCancelTransfer(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleCancelTransfer:"); + + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + + string secureCode = string.Empty; + string transactionID = string.Empty; + UUID transactionUUID = UUID.Zero; + + response.Value = responseData; + + 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)) + { + responseData["success"] = false; + m_log.Error("[MONEY RPC]: handleCancelTransfer: secureCode or transactionID can't be empty"); + return response; + } + + TransactionData transaction = m_moneyDBService.FetchTransaction(transactionUUID); + UserInfo user = m_moneyDBService.FetchUserInfo(transaction.Sender); + + try + { + m_log.InfoFormat("[MONEY RPC]: handleCancelTransfer: User {0} wanted to cancel the transaction", user.Avatar); + if (m_moneyDBService.ValidateTransfer(secureCode, transactionUUID)) + { + m_log.InfoFormat("[MONEY RPC]: 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.Now.ToString()); + responseData["success"] = true; + } + } + catch (Exception e) + { + m_log.ErrorFormat("[MONEY RPC]: handleCancelTransfer: Exception occurred when transaction {0}: {1}", transactionID, e.ToString()); + responseData["success"] = false; + } + return response; + } + + + + // + public XmlRpcResponse handleGetTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleGetTransaction:"); + + 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; + + 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["success"] = false; + responseData["description"] = "TransactionID is empty"; + m_log.Error("[MONEY RPC]: 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["success"] = false; + responseData["description"] = "Invalid Transaction UUID"; + } + + return response; + } + + catch (Exception e) + { + m_log.ErrorFormat("[MONEY RPC]: handleGetTransaction: {0}", e.ToString()); + m_log.ErrorFormat("[MONEY RPC]: handleGetTransaction: Can't get transaction information for {0}", transactionUUID.ToString()); + } + + } + } + + responseData["success"] = false; + responseData["description"] = "Session check failure, please re-login"; + + return response; + } + + + + // + public XmlRpcResponse handleWebLogin(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleWebLogin:"); + + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + string userID = string.Empty; + string webSessionID = string.Empty; + + + response.Value = responseData; + + if (requestData.ContainsKey("userID")) userID = (string)requestData["userID"]; + if (requestData.ContainsKey("sessionID")) webSessionID = (string)requestData["sessionID"]; + + if (string.IsNullOrEmpty(userID) || string.IsNullOrEmpty(webSessionID)) + { + responseData["success"] = false; + 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 RPC]: handleWebLogin: User {0} has logged in from web", userID); + responseData["success"] = true; + return response; + } + + + + // + public XmlRpcResponse handleWebLogout(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleWebLogout:"); + + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + string userID = string.Empty; + string webSessionID = string.Empty; + + response.Value = responseData; + + if (requestData.ContainsKey("userID")) userID = (string)requestData["userID"]; + if (requestData.ContainsKey("sessionID")) webSessionID = (string)requestData["sessionID"]; + + if (string.IsNullOrEmpty(userID) || string.IsNullOrEmpty(webSessionID)) + { + responseData["success"] = false; + 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 RPC]: handleWebLogout: User {0} has logged out from web", userID); + responseData["success"] = true; + return response; + + } + + + + /// + /// Get balance method for web pages. + /// + /// + /// + public XmlRpcResponse handleWebGetBalance(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleWebGetBalance:"); + + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + string userID = string.Empty; + string webSessionID = string.Empty; + int balance = 0; + + response.Value = responseData; + + if (requestData.ContainsKey("userID")) userID = (string)requestData["userID"]; + if (requestData.ContainsKey("sessionID")) webSessionID = (string)requestData["sessionID"]; + + m_log.InfoFormat("[MONEY RPC]: handleWebGetBalance: Getting balance for user {0}", userID); + + if (m_webSessionDic.ContainsKey(userID)) //perform session check + { + 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"; + } + if (balance==-1) // User not found + { + responseData["success"] = false; + 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 RPC]: handleWebGetBalance: Can't get balance for user {0}, Exception {1}", userID, e.ToString()); + responseData["success"] = false; + responseData["errorMessage"] = "Exception occurred when getting balance"; + return response; + } + + } + } + + m_log.Error("[MONEY RPC]: handleWebLogout: Session authentication failed when getting balance for user " + userID); + + responseData["success"] = false; + responseData["errorMessage"] = "Session check failure, please re-login"; + return response; + } + + + + /// + /// Get transaction for web pages + /// + /// + /// + public XmlRpcResponse handleWebGetTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleWebGetTransaction:"); + + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + string userID = string.Empty; + string webSessionID = string.Empty; + int lastIndex = -1; + int startTime = 0; + int endTime = 0; + + response.Value = responseData; + + 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 RPC]: 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["success"] = false; + responseData["errorMessage"] = string.Format("Unable to fetch transaction data with the index {0}", lastIndex + 1); + } + return response; + } + catch (Exception e) + { + m_log.ErrorFormat("[MONEY RPC]: handleWebGetTransaction: Can't get transaction for user {0}, Exception {1}", userID, e.ToString()); + responseData["success"] = false; + responseData["errorMessage"] = "Exception occurred when getting transaction"; + return response; + } + } + } + + m_log.Error("[MONEY RPC]: handleWebGetTransaction: Session authentication failed when getting transaction for user " + userID); + + responseData["success"] = false; + responseData["errorMessage"] = "Session check failure, please re-login"; + return response; + + } + + + + /// + /// Get total number of transactions for web pages. + /// + /// + /// + public XmlRpcResponse handleWebGetTransactionNum(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleWebGetTransactionNum:"); + + GetSSLCommonName(request); + + Hashtable requestData = (Hashtable)request.Params[0]; + XmlRpcResponse response = new XmlRpcResponse(); + Hashtable responseData = new Hashtable(); + string userID = string.Empty; + string webSessionID = string.Empty; + int startTime = 0; + int endTime = 0; + + response.Value = responseData; + + 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 RPC]: handleWebGetTransactionNum: Get {0} transactions for user {1}", it, userID); + responseData["success"] = true; + responseData["number"] = it; + } + else + { + responseData["success"] = false; + } + return response; + } + } + + m_log.Error("[MONEY RPC]: handleWebGetTransactionNum: Session authentication failed when getting transaction number for user " + userID); + responseData["success"] = false; + responseData["errorMessage"] = "Session check failure, please re-login"; + return response; + } + + } +} + diff --git a/release/0.8.2/src/OpenSim.Grid.MoneyServer/Program.cs b/release/0.8.2/src/OpenSim.Grid.MoneyServer/Program.cs new file mode 100644 index 0000000..3fbd12d --- /dev/null +++ b/release/0.8.2/src/OpenSim.Grid.MoneyServer/Program.cs @@ -0,0 +1,42 @@ +/* + * 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. + */ + +using log4net.Config; + +namespace OpenSim.Grid.MoneyServer +{ + class Program + { + public static void Main(string[] args) + { + XmlConfigurator.Configure(); + MoneyServerBase app = new MoneyServerBase(); + app.Startup(); + app.Work(); + } + } +} diff --git a/release/0.8.2/src/OpenSim.Grid.MoneyServer/Properties/AssemblyInfo.cs b/release/0.8.2/src/OpenSim.Grid.MoneyServer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..407ecee --- /dev/null +++ b/release/0.8.2/src/OpenSim.Grid.MoneyServer/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +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.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 +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/release/0.8.2/src/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs b/release/0.8.2/src/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs new file mode 100644 index 0000000..3175b30 --- /dev/null +++ b/release/0.8.2/src/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs @@ -0,0 +1,1940 @@ +// * 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. +// */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Reflection; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +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; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +using NSL.Certificate.Tools; +using NSL.Network.XmlRpc; + + + +[assembly: Addin("DTLNSLMoneyModule", "1.0")] +[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)] + + + +namespace OpenSim.Modules.Currency +{ + // + public enum TransactionType : int + { + // One-Time Charges + GroupCreate = 1002, + GroupJoin = 1004, + UploadCharge = 1101, + LandAuction = 1102, + ClassifiedCharge= 1103, + // Recurrent Charges + ParcelDirFee = 2003, + 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, + // Group Transactions + GroupLiability = 6003, + GroupDividend = 6004, + // Stipend Credits + StipendPayment = 10000 + } + + +/* + public enum OpenMetaverse.MoneyTransactionType : int + { + None = 0, + FailSimulatorTimeout = 1, + FailDataserverTimeout = 2, + ObjectClaim = 1000, + LandClaim = 1001, + GroupCreate = 1002, + ObjectPublicClaim = 1003, + GroupJoin = 1004, + TeleportCharge = 1100, + UploadCharge = 1101, + LandAuction = 1102, + ClassifiedCharge = 1103, + ObjectTax = 2000, + LandTax = 2001, + LightTax = 2002, + ParcelDirFee = 2003, + GroupTax = 2004, + ClassifiedRenew = 2005, + GiveInventory = 3000, + ObjectSale = 5000, + Gift = 5001, + LandSale = 5002, + ReferBonus = 5003, + InventorySale = 5004, + RefundPurchase = 5005, + LandPassSale = 5006, + DwellBonus = 5007, + PayObject = 5008, + ObjectPays = 5009, + GroupLandDeed = 6001, + GroupObjectDeed = 6002, + GroupLiability = 6003, + GroupDividend = 6004, + GroupMembershipDues = 6005, + ObjectRelease = 8000, + LandRelease = 8001, + ObjectDelete = 8002, + ObjectPublicDecay = 8003, + ObjectPublicDelete = 8004, + LindenAdjustment = 9000, + LindenGrant = 9001, + LindenPenalty = 9002, + EventFee = 9003, + EventPrize = 9004, + StipendBasic = 10000, + StipendDeveloper = 10001, + StipendAlways = 10002, + StipendDaily = 10003, + StipendRating = 10004, + StipendDelta = 10005 + } +*/ + + + + // + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "DTLNSLMoneyModule")] + public class DTLNSLMoneyModule : IMoneyModule, ISharedRegionModule + { + #region Constant numbers and members. + + // 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 X509Certificate2 m_cert = null; + + // + private bool m_use_web_settle = false; + private string m_settle_url = ""; + private string m_settle_message = ""; + private bool m_settle_user = false; + + private NSLCertificateVerify m_certVerify = new NSLCertificateVerify(); // サーバ認証用 + + + /// + /// Scene dictionary indexed by Region Handle + /// + private Dictionary m_sceneList = new Dictionary(); + + /// + /// To cache the balance data while the money server is not available. + /// + private Dictionary m_moneyServer = new Dictionary(); + + // Events + public event ObjectPaid OnObjectPaid; + + + // Price + private int ObjectCount = 0; + private int PriceEnergyUnit = 0; + private int PriceGroupCreate = 0; + private int PriceObjectClaim = 0; + private float PriceObjectRent = 0f; + private float PriceObjectScaleFactor = 0f; + private int PriceParcelClaim = 0; + private int PriceParcelRent = 0; + private float PriceParcelClaimFactor = 0f; + private int PricePublicObjectDecay = 0; + private int PricePublicObjectDelete = 0; + private int PriceRentLight = 0; + private int PriceUpload = 0; + private int TeleportMinPrice = 0; + private float TeleportPriceExponent = 0f; + private float EnergyEfficiency = 0f; + + #endregion + + + // + public void Initialise(Scene scene, IConfigSource source) + { + Initialise(source); + if (string.IsNullOrEmpty(m_moneyServURL)) m_enable_server = false; + // + AddRegion(scene); + } + + + + #region ISharedRegionModule interface + + public void Initialise(IConfigSource source) + { + //m_log.InfoFormat("[MONEY]: Initialise:"); + + // Handle the parameters errors. + if (source==null) return; + + try + { + m_config = source; + + // [Economy] section + IConfig economyConfig = m_config.Configs["Economy"]; + + if (economyConfig.GetString("EconomyModule")!=Name) + { + m_enabled = false; + m_log.InfoFormat("[MONEY]: The DTL/NSL MoneyModule is disabled"); + return; + } + else + { + m_log.InfoFormat("[MONEY]: The DTL/NSL MoneyModule is enabled"); + } + + m_sellEnabled = economyConfig.GetBoolean("SellEnabled", false); + m_moneyServURL = economyConfig.GetString("CurrencyServer"); + + // クライアント証明書 + m_certFilename = economyConfig.GetString("ClientCertFilename", ""); + m_certPassword = economyConfig.GetString("ClientCertPassword", ""); + if (m_certFilename!="") { + m_cert = new X509Certificate2(m_certFilename, m_certPassword); + m_log.InfoFormat("[MONEY]: Issue Authentication of Client. Cert File is " + m_certFilename); + } + + // サーバ認証 + string checkcert = economyConfig.GetString("CheckServerCert", "false"); + if (checkcert.ToLower()=="true") m_checkServerCert = true; + + m_cacertFilename = economyConfig.GetString("CACertFilename", ""); + if (m_cacertFilename!="") { + m_certVerify.SetPrivateCA(m_cacertFilename); + m_log.InfoFormat("[MONEY]: Execute Authentication of Server. CA Cert File is " + m_cacertFilename); + } + else { + m_checkServerCert = false; + } + ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(m_certVerify.ValidateServerCertificate); + + + // Settlement + m_use_web_settle = economyConfig.GetBoolean("SettlementByWeb", false); + m_settle_url = economyConfig.GetString ("SettlementURL", ""); + m_settle_message = economyConfig.GetString ("SettlementMessage", ""); + + // Price + PriceEnergyUnit = economyConfig.GetInt ("PriceEnergyUnit", 100); + PriceObjectClaim = economyConfig.GetInt ("PriceObjectClaim", 10); + PricePublicObjectDecay = economyConfig.GetInt ("PricePublicObjectDecay", 4); + PricePublicObjectDelete = economyConfig.GetInt ("PricePublicObjectDelete", 4); + PriceParcelClaim = economyConfig.GetInt ("PriceParcelClaim", 1); + PriceParcelClaimFactor = economyConfig.GetFloat("PriceParcelClaimFactor", 1f); + PriceUpload = economyConfig.GetInt ("PriceUpload", 0); + PriceRentLight = economyConfig.GetInt ("PriceRentLight", 5); + PriceObjectRent = economyConfig.GetFloat("PriceObjectRent", 1); + PriceObjectScaleFactor = economyConfig.GetFloat("PriceObjectScaleFactor", 10); + PriceParcelRent = economyConfig.GetInt ("PriceParcelRent", 1); + PriceGroupCreate = economyConfig.GetInt ("PriceGroupCreate", 0); + TeleportMinPrice = economyConfig.GetInt ("TeleportMinPrice", 2); + TeleportPriceExponent = economyConfig.GetFloat("TeleportPriceExponent", 2f); + EnergyEfficiency = economyConfig.GetFloat("EnergyEfficiency", 1); + + } + catch + { + m_log.ErrorFormat("[MONEY]: Initialise: Faile to read configuration file"); + } + } + + + + public void AddRegion(Scene scene) + { + //m_log.InfoFormat("[MONEY]: AddRegion:"); + + if (scene==null) return; + + scene.RegisterModuleInterface(this); // 競合するモジュールの排除 + + 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); // added + HttpServer.AddXmlRPCHandler("AddBankerMoney", AddBankerMoneyHandler); // added + HttpServer.AddXmlRPCHandler("SendMoneyBalance", SendMoneyBalanceHandler); // added + //HttpServer.AddXmlRPCHandler("UploadCovered", UploadCoveredHandler); // added for Aurora-Sim + //HttpServer.AddXmlRPCHandler("UploadCharge", UploadChargeHandler); // added for Aurora-Sim + + MainServer.Instance.AddXmlRPCHandler("OnMoneyTransfered", OnMoneyTransferedHandler); + MainServer.Instance.AddXmlRPCHandler("UpdateBalance", BalanceUpdateHandler); + MainServer.Instance.AddXmlRPCHandler("UserAlert", UserAlertHandler); + MainServer.Instance.AddXmlRPCHandler("GetBalance", GetBalanceHandler); // added + MainServer.Instance.AddXmlRPCHandler("AddBankerMoney", AddBankerMoneyHandler); // added + MainServer.Instance.AddXmlRPCHandler("SendMoneyBalance", SendMoneyBalanceHandler); // added + //MainServer.Instance.AddXmlRPCHandler("UploadCovered", UploadCoveredHandler); // added for Aurora-Sim + //MainServer.Instance.AddXmlRPCHandler("UploadCharge", UploadChargeHandler); // added for Aurora-Sim + } + } + + 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; + } + + + + public void RemoveRegion(Scene scene) + { + } + + + public void RegionLoaded(Scene scene) + { + } + + + public Type ReplaceableInterface + { + //get { return typeof(IMoneyModule); } + get { return null; } + } + + + public bool IsSharedModule + { + get { return true; } + } + + + public string Name + { + get { return "DTLNSLMoneyModule"; } + } + + + public void PostInitialise() + { + } + + + public void Close() + { + } + + + #endregion + + + + + #region IMoneyModule interface. + + // for LSL llGiveMoney() function + public bool ObjectGiveMoney(UUID objectID, UUID fromID, UUID toID, int amount) + { + //m_log.InfoFormat("[MONEY]: ObjectGiveMoney: LSL ObjectGiveMoney. UUID = {0}", objectID.ToString()); + + if (!m_sellEnabled) return false; + + string objName = string.Empty; + string avatarName = string.Empty; + + SceneObjectPart sceneObj = GetLocatePrim(objectID); + if (sceneObj!=null) + { + 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; + if (GetLocateClient(fromID)!=null) + { + ret = TransferMoney(fromID, toID, amount, (int)MoneyTransactionType.ObjectPays, objectID, regionHandle, description); + } + else + { + ret = ForceTransferMoney(fromID, toID, amount, (int)MoneyTransactionType.ObjectPays, objectID, regionHandle, description); + } + } + + return ret; + } + + + // + public int UploadCharge + { + get { return PriceUpload; } + } + + + // + public int GroupCreationCharge + { + get { return PriceGroupCreate; } + } + + + public int GetBalance(UUID agentID) + { + IClientAPI client = GetLocateClient(agentID); + return QueryBalanceFromMoneyServer(client); + } + + + public bool UploadCovered(UUID agentID, int amount) + { + IClientAPI client = GetLocateClient(agentID); + int balance = QueryBalanceFromMoneyServer(client); + if (balance= landBuyEvent.parcelPrice) + { + lock(landBuyEvent) + { + landBuyEvent.economyValidated = true; + } + } + } + } + + + + /**/ + // for LandBuy even + // For Aurora-Sim, OnParcelBuy event function is already defined + // in OpenSim/Region/CoreModules/World/Land/ParcelManagementModule.cs + private void processLandBuy(Object sender, EventManager.LandBuyArgs landBuyEvent) + { + //m_log.InfoFormat("[MONEY]: processLandBuy:"); + + if (!m_sellEnabled) return; + + lock(landBuyEvent) + { + if (landBuyEvent.economyValidated==true && landBuyEvent.transactionID==0) + { + landBuyEvent.transactionID = Util.UnixTimeSinceEpoch(); + + ulong parcelID = (ulong)landBuyEvent.parcelLocalID; + UUID regionID = UUID.Zero; + if (sender is Scene) regionID = ((Scene)sender).RegionInfo.RegionID; + + if (TransferMoney(landBuyEvent.agentId, landBuyEvent.parcelOwnerID, + landBuyEvent.parcelPrice, (int)MoneyTransactionType.LandSale, regionID, parcelID, "Land Purchase")) + { + landBuyEvent.amountDebited = landBuyEvent.parcelPrice; + } + } + } + } + + + + /**/ + // for OnObjectBuy event + // For Aurora-Sim, OnObjectBuy event function is already defined + // in OpenSim/Region/CoreModules/World/Objects/BuySell/BuySellModule.cs + public void OnObjectBuy(IClientAPI remoteClient, UUID agentID, UUID sessionID, + UUID groupID, UUID categoryID, uint localID, byte saleType, int salePrice) + { + //m_log.InfoFormat("[MONEY]: OnObjectBuy: agent = {0}, {1}", agentID, remoteClient.AgentId); + + // Handle the parameters error. + if (!m_sellEnabled) return; + if (remoteClient==null || salePrice<0) return; // for L$0 Sell + + // Get the balance from money server. + int balance = QueryBalanceFromMoneyServer(remoteClient); + if (balance(); + if (mod!=null) + { + UUID receiverId = sceneObj.OwnerID; + ulong regionHandle = sceneObj.RegionHandle; + bool ret = true; + if (salePrice>0) { + ret = TransferMoney(remoteClient.AgentId, receiverId, salePrice, + (int)MoneyTransactionType.PayObject, sceneObj.UUID, regionHandle, "Object Buy"); + } + if (ret) + { + mod.BuyObject(remoteClient, categoryID, localID, saleType, salePrice); + } + } + } + else + { + remoteClient.SendAgentAlertMessage("Unable to buy now. The object was not found", false); + 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]: 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"); + } + } + + + + private void OnRequestPayPrice(IClientAPI client, UUID objectID) + { + //m_log.InfoFormat("[MONEY]: 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) + private void OnEconomyDataRequest(IClientAPI user) + { + //m_log.InfoFormat("[MONEY]: OnEconomyDataRequest:"); + + //IClientAPI user = GetLocateClient(agentId); + + if (user!=null) + { + //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); + } + } + + + #endregion + + + + #region MoneyModule XML-RPC Handler + + // "OnMoneyTransfered" RPC from MoneyServer + public XmlRpcResponse OnMoneyTransferedHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: OnMoneyTransferedHandler:"); + + bool ret = false; + + if (request.Params.Count>0) + { + Hashtable requestParam = (Hashtable)request.Params[0]; + if (requestParam.Contains("clientUUID") && + //requestParam.Contains("receiverUUID") && + requestParam.Contains("clientSessionID") && + requestParam.Contains("clientSecureSessionID")) + { + UUID clientUUID = UUID.Zero; + UUID.TryParse((string)requestParam["clientUUID"], out clientUUID); + //UUID receiverUUID = UUID.Zero; + //UUID.TryParse((string)requestParam["receiverUUID"], out receiverUUID); + if (clientUUID!=UUID.Zero) + { + IClientAPI client = GetLocateClient(clientUUID); + if (client!=null && + client.SessionId.ToString()==(string)requestParam["clientSessionID"] && + client.SecureSessionId.ToString()==(string)requestParam["clientSecureSessionID"]) + { + if (requestParam.Contains("transactionType") && + requestParam.Contains("objectID") && + requestParam.Contains("amount")) + { + //m_log.InfoFormat("[MONEY]: OnMoneyTransferedHandler: type = {0}", requestParam["transactionType"]); + if ((int)requestParam["transactionType"]==(int)MoneyTransactionType.PayObject) // Pay for the object. + { + // 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]: OnMoneyTransferedHandler: Transaction is failed. MoneyServer will rollback"); + } + resp.Value = paramTable; + + return resp; + } + + + + // "UpdateBalance" RPC from MoneyServer or Script + public XmlRpcResponse BalanceUpdateHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: BalanceUpdateHandler:"); + + bool ret = false; + + #region Update the balance from money server. + + if (request.Params.Count>0) + { + Hashtable requestParam = (Hashtable)request.Params[0]; + if (requestParam.Contains("clientUUID") && + requestParam.Contains("clientSessionID") && // unable for Aurora-Sim + requestParam.Contains("clientSecureSessionID")) + { + UUID clientUUID = UUID.Zero; + UUID.TryParse((string)requestParam["clientUUID"], out clientUUID); + if (clientUUID!=UUID.Zero) + { + IClientAPI client = GetLocateClient(clientUUID); + if (client!=null && + client.SessionId.ToString()==(string)requestParam["clientSessionID"] && // unable for Aurora-Sim + client.SecureSessionId.ToString()==(string)requestParam["clientSecureSessionID"]) + { + 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"]); + client.SendMoneyBalance(UUID.Random(), true, Utils.StringToBytes(msg), (int)requestParam["Balance"], + 0, UUID.Zero, false, UUID.Zero, false, 0, String.Empty); + ret = true; + } + } + } + } + } + + #endregion + + // Send the response to money server. + XmlRpcResponse resp = new XmlRpcResponse(); + Hashtable paramTable = new Hashtable(); + paramTable["success"] = ret; + + if (!ret) + { + m_log.ErrorFormat("[MONEY]: BalanceUpdateHandler: Cannot update client balance from MoneyServer"); + } + resp.Value = paramTable; + + return resp; + } + + + + // "UserAlert" RPC from Script + public XmlRpcResponse UserAlertHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: UserAlertHandler:"); + + bool ret = false; + + #region confirm the request and show the notice from money server. + + if (request.Params.Count>0) + { + Hashtable requestParam = (Hashtable)request.Params[0]; + if (requestParam.Contains("clientUUID") && + requestParam.Contains("clientSessionID") && // unable for Aurora-Sim + requestParam.Contains("clientSecureSessionID")) + { + UUID clientUUID = UUID.Zero; + UUID.TryParse((string)requestParam["clientUUID"], out clientUUID); + if (clientUUID!=UUID.Zero) + { + IClientAPI client = GetLocateClient(clientUUID); + if (client!=null && + client.SessionId.ToString()==(string)requestParam["clientSessionID"] && // unable for Aurora-Sim + client.SecureSessionId.ToString()==(string)requestParam["clientSecureSessionID"]) + { + 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; + } + } + } + } + } + // + #endregion + + // 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 + public XmlRpcResponse GetBalanceHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: GetBalanceHandler:"); + + bool ret = false; + int balance = -1; + + if (request.Params.Count>0) + { + Hashtable requestParam = (Hashtable)request.Params[0]; + if (requestParam.Contains("clientUUID") && + requestParam.Contains("clientSessionID") && // unable for Aurora-Sim + requestParam.Contains("clientSecureSessionID")) + { + UUID clientUUID = UUID.Zero; + UUID.TryParse((string)requestParam["clientUUID"], out clientUUID); + if (clientUUID!=UUID.Zero) + { + IClientAPI client = GetLocateClient(clientUUID); + if (client!=null && + client.SessionId.ToString()==(string)requestParam["clientSessionID"] && // unable for Aurora-Sim + client.SecureSessionId.ToString()==(string)requestParam["clientSecureSessionID"]) + { + balance = QueryBalanceFromMoneyServer(client); + } + } + } + } + + // Send the response to caller. + if (balance<0) + { + m_log.ErrorFormat("[MONEY]: 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 + public XmlRpcResponse AddBankerMoneyHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: AddBankerMoneyHandler:"); + + bool ret = false; + + if (request.Params.Count>0) + { + Hashtable requestParam = (Hashtable)request.Params[0]; + if (requestParam.Contains("clientUUID") && + requestParam.Contains("clientSessionID") && // unable for Aurora-Sim + requestParam.Contains("clientSecureSessionID")) + { + UUID bankerUUID = UUID.Zero; + UUID.TryParse((string)requestParam["clientUUID"], out bankerUUID); + if (bankerUUID!=UUID.Zero) + { + IClientAPI client = GetLocateClient(bankerUUID); + if (client!=null && + client.SessionId.ToString()==(string)requestParam["clientSessionID"] && // unable for Aurora-Sim + client.SecureSessionId.ToString()==(string)requestParam["clientSecureSessionID"]) + { + if (requestParam.Contains("amount")) + { + Scene scene = (Scene)client.Scene; + int amount = (int)requestParam["amount"]; + ret = AddBankerMoney(bankerUUID, amount, scene.RegionInfo.RegionHandle); + + 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]: 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; + } + + + + // "SendMoneyBalance" RPC from Script + public XmlRpcResponse SendMoneyBalanceHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: SendMoneyBalanceHandler:"); + + bool ret = false; + + if (request.Params.Count>0) + { + Hashtable requestParam = (Hashtable)request.Params[0]; + if (requestParam.Contains("clientUUID") && + requestParam.Contains("secretAccessCode")) + { + UUID clientUUID = UUID.Zero; + UUID.TryParse((string)requestParam["clientUUID"], out clientUUID); + if (clientUUID!=UUID.Zero) + { + 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]: SendMoneyBalanceHandler: SecretCode: {0} + {1} = {2}", secretCode, scriptIP, hash); + ret = SendMoneyBalance(clientUUID, amount, hash); + } + } + else { + m_log.ErrorFormat("[MONEY]: SendMoneyBalanceHandler: amount is missed"); + } + } + else { + if (!requestParam.Contains("clientUUID")) { + m_log.ErrorFormat("[MONEY]: SendMoneyBalanceHandler: clientUUID is missed"); + } + if (!requestParam.Contains("secretAccessCode")) { + m_log.ErrorFormat("[MONEY]: SendMoneyBalanceHandler: secretAccessCode is missed"); + } + } + } + else { + m_log.ErrorFormat("[MONEY]: SendMoneyBalanceHandler: Count is under 0"); + } + + if (!ret) m_log.ErrorFormat("[MONEY]: SendMoneyBalanceHandler: 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; + } + + + + /* + // "UploadCovered" RPC for Aurora-Sim + public XmlRpcResponse UploadCoveredHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: UploadCoveredHandler:"); + + 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); + if (client!=null && + client.SessionId.ToString()==(string)requestParam["clientSessionID"] && + client.SecureSessionId.ToString()==(string)requestParam["clientSecureSessionID"]) + { + ret = UploadCovered(client, UploadCharge); + } + } + } + } + + // Send the response to caller. + XmlRpcResponse resp = new XmlRpcResponse(); + Hashtable paramTable = new Hashtable(); + paramTable["success"] = ret; + resp.Value = paramTable; + + return resp; + } + */ + + + + /* + // "UploadCovered" RPC for Aurora-Sim + public XmlRpcResponse UploadChargeHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: UploadChargeHandler:"); + + 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); + if (client!=null && + client.SessionId.ToString()==(string)requestParam["clientSessionID"] && + client.SecureSessionId.ToString()==(string)requestParam["clientSecureSessionID"]) + { + ApplyUploadCharge(clientUUID, UploadCharge, "Upload Asset"); + ret = true; + } + } + } + } + + // Send the response to caller. + XmlRpcResponse resp = new XmlRpcResponse(); + Hashtable paramTable = new Hashtable(); + paramTable["success"] = ret; + resp.Value = paramTable; + + return resp; + } + */ + + + #endregion + + + + + #region MoneyModule private help functions + + /// + /// 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, string description) + { + //m_log.InfoFormat("[MONEY]: TransferMoney:"); + + bool ret = false; + IClientAPI senderClient = GetLocateClient(sender); + + // Handle the illegal transaction. + if (senderClient==null) // receiverClient could be null. + { + m_log.InfoFormat("[MONEY]: TransferMoney: Client {0} not found", sender.ToString()); + return false; + } + + if (QueryBalanceFromMoneyServer(senderClient) + /// 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, string description) + { + //m_log.InfoFormat("[MONEY]: ForceTransferMoney:"); + + bool ret = false; + + #region Force send transaction request to money server and parse the resultes. + + if (m_enable_server) + { + // 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["regionHandle"] = regionHandle.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]: ForceTransferMoney: Can not money force transfer request from [{0}] to [{1}]", sender.ToString(), receiver.ToString()); + } + //else m_log.ErrorFormat("[MONEY]: ForceTransferMoney: Money Server is not available!!"); + + #endregion + + 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) + { + //m_log.InfoFormat("[MONEY]: AddBankerMoney:"); + + 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["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]: AddBankerMoney: Avatar is not Banker. Web Settlemrnt is used."); + } + else m_log.ErrorFormat("[MONEY]: AddBankerMoney: Fail Message {0}", resultTable["message"]); + } + } + else m_log.ErrorFormat("[MONEY]: AddBankerMoney: Money Server is not responce"); + } + //else m_log.ErrorFormat("[MONEY]: AddBankerMoney: Money Server is not available!!"); + + return ret; + } + + + + /// + /// Send the money to avatar. Need to notify money server to update. + /// + /// + /// The amount of money. + /// + /// + /// return true, if successfully. + /// + private bool SendMoneyBalance(UUID avatarID, int amount, string secretCode) + { + //m_log.InfoFormat("[MONEY]: SendMoneyBalance:"); + + bool ret = false; + + if (m_enable_server) + { + // Fill parameters for money transfer XML-RPC. + Hashtable paramTable = new Hashtable(); + paramTable["avatarID"] = avatarID.ToString(); + paramTable["transactionType"] = (int)MoneyTransactionType.ReferBonus; + paramTable["amount"] = amount; + paramTable["secretAccessCode"] = secretCode; + paramTable["description"] = "Bonus to Avatar"; + + // Generate the request for transfer. + Hashtable resultTable = genericCurrencyXMLRPCRequest(paramTable, "SendMoneyBalance"); + + // 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]: SendMoneyBalance: Fail Message is {0}", resultTable["message"]); + } + else m_log.ErrorFormat("[MONEY]: SendMoneyBalance: Money Server is not responce"); + } + //else m_log.ErrorFormat("[MONEY]: SendMoneyBalance: Money Server is not available!!"); + + 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, string description) + { + //m_log.InfoFormat("[MONEY]: PayMoneyCharge:"); + + bool ret = false; + IClientAPI senderClient = GetLocateClient(sender); + + // Handle the illegal transaction. + if (senderClient==null) // receiverClient could be null. + { + m_log.InfoFormat("[MONEY]: PayMoneyCharge: Client {0} is not found", sender.ToString()); + return false; + } + + if (QueryBalanceFromMoneyServer(senderClient) + /// Login the money server when the new client login. + /// + /// + /// Indicate user ID of the new client. + /// + /// + /// return true, if successfully. + /// + private bool LoginMoneyServer(IClientAPI client, out int balance) + { + //m_log.InfoFormat("[MONEY]: LoginMoneyServer:"); + + bool ret = false; + balance = 0; + + #region Send money server the client info for login. + + Scene scene = (Scene)client.Scene; + string userName = string.Empty; + + if (!string.IsNullOrEmpty(m_moneyServURL)) + { + // 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; + } + } + } + + // Login the Money Server. + Hashtable paramTable = new Hashtable(); + paramTable["openSimServIP"] = scene.RegionInfo.ServerURI.Replace(scene.RegionInfo.InternalEndPoint.Port.ToString(), + scene.RegionInfo.HttpPort.ToString()); + paramTable["userName"] = userName; + 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]: LoginMoneyServer: Client [{0}] login Money Server {1}", client.AgentId.ToString(), m_moneyServURL); + ret = true; + } + } + else m_log.ErrorFormat("[MONEY]: LoginMoneyServer: Unable to login Money Server {0} for client [{1}]", m_moneyServURL, client.AgentId.ToString()); + } + else m_log.ErrorFormat("[MONEY]: LoginMoneyServer: Money Server is not available!!"); + + #endregion + + // Viewerへ設定を通知する. + 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) + { + //m_log.InfoFormat("[MONEY]: LogoffMoneyServer:"); + + 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; + } + } + } + + return ret; + } + + + + /// + /// 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) + { + //m_log.InfoFormat("[MONEY]: genericCurrencyXMLRPCRequest:"); + + if (reqParams.Count<=0 || string.IsNullOrEmpty(method)) return null; + + if (m_checkServerCert) + { + if (!m_moneyServURL.StartsWith("https://")) + { + m_log.InfoFormat("[MONEY]: 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]: 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); + } + catch (Exception ex) + { + m_log.ErrorFormat("[MONEY]: genericCurrencyXMLRPCRequest: Unable to connect to Money Server {0}", m_moneyServURL); + m_log.ErrorFormat("[MONEY]: 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.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; + } + + + + private int QueryBalanceFromMoneyServer(IClientAPI client) + { + //m_log.InfoFormat("[MONEY]: QueryBalanceFromMoneyServer:"); + + int balance = 0; + + #region Send the request to get the balance from money server for cilent. + + 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"]; + } + } + } + else + { + if (m_moneyServer.ContainsKey(client.AgentId)) + { + balance = m_moneyServer[client.AgentId]; + } + } + } + + #endregion + + return balance; + } + + + + // + private EventManager.MoneyTransferArgs GetTransactionInfo(IClientAPI client, string transactionID) + { + //m_log.InfoFormat("[MONEY]: GetTransactionInfo:"); + + 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]: GetTransactionInfo: GetTransactionInfo: Fail to Request. {0}", (string)resultTable["description"]); + } + } + else + { + m_log.ErrorFormat("[MONEY]: GetTransactionInfo: Invalid Response"); + } + } + else + { + m_log.ErrorFormat("[MONEY]: GetTransactionInfo: Invalid Money Server URL"); + } + + return args; + } + + + + /// 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; + } + } + } + } + } + + return client; + } + + + + 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; + } + } + } + } + + return scene; + } + + + + 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; + } + + + #endregion + } + +} diff --git a/release/0.8.2/src/OpenSim.Modules.Currency/NSLCertificateTools.cs b/release/0.8.2/src/OpenSim.Modules.Currency/NSLCertificateTools.cs new file mode 100644 index 0000000..1b641e8 --- /dev/null +++ b/release/0.8.2/src/OpenSim.Modules.Currency/NSLCertificateTools.cs @@ -0,0 +1,215 @@ +/* + * Copyright (c) Contributors, http://www.nsl.tuis.ac.jp + * + */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using System.Net; +using System.Net.Security; +using System.Text; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; + +using log4net; + + +namespace NSL.Certificate.Tools +{ + // + 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 Mono.Security.X509.X509Crl m_clientcrl = null; + + + public NSLCertificateVerify() + { + m_chain = null; + m_cacert = null; + m_clientcrl = null; + } + + + public NSLCertificateVerify(string certfile) + { + SetPrivateCA(certfile); + } + + + public NSLCertificateVerify(string certfile, string crlfile) + { + SetPrivateCA (certfile); + SetPrivateCRL(crlfile); + } + + + 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; + } + } + + + 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); + } + } + + + + // + // + // + 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(<>ht)f&lV2n4`u;Q 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 literal 0 HcmV?d00001 diff --git a/release/0.9.1/bin/OpenSim.Data.MySQL.MySQLMoneyDataWrapper.dll b/release/0.9.1/bin/OpenSim.Data.MySQL.MySQLMoneyDataWrapper.dll new file mode 100644 index 0000000000000000000000000000000000000000..d0d506e8c00f1c2044826768c3315dc2787a937f GIT binary patch literal 38912 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 G0{77YS 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
0qrB& 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 X|9d%bm-;(z*PZsimj(ZuXa0WyL|KkG literal 0 HcmV?d00001 diff --git a/release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs b/release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs new file mode 100644 index 0000000..41b80d6 --- /dev/null +++ b/release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs @@ -0,0 +1,71 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +//using System.Linq; +using System.Text; +using OpenMetaverse; + +namespace OpenSim.Data.MySQL.MySQLMoneyDataWrapper +{ + public interface IMoneyManager + { + int getBalance(string userID); + + //int getBalanceStatus(string userID); + + //bool updateBalanceStatus(string userID,int status); + + bool withdrawMoney(UUID transactionID,string senderID, int amount); + + bool giveMoney(UUID transactionID,string receiverID, int amount); + + bool addTransaction(TransactionData transaction); + + bool updateTransactionStatus(UUID transactionID, int status,string description); + + TransactionData FetchTransaction(UUID transactionID); + + TransactionData[] FetchTransaction(string userID, int startTime, int endTime, uint index,uint retNum); + + int getTransactionNum(string userID, int startTime, int endTime); + + bool addUser(string userID, int balance, int status, int type); + + bool SetTransExpired(int deadTime); + + bool ValidateTransfer(string secureCode, UUID transactionID); + + bool addUserInfo(UserInfo user); + + UserInfo fetchUserInfo(string userID); + + bool updateUserInfo(UserInfo user); + + } +} diff --git a/release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs b/release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs new file mode 100644 index 0000000..3b30960 --- /dev/null +++ b/release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs @@ -0,0 +1,1622 @@ +/* + * 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. + */ + +using System; +using System.Data; +using System.Reflection; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using log4net; +using MySql.Data.MySqlClient; +using OpenMetaverse; + + +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; + private MySqlConnection dbcon; + + + public MySQLMoneyManager(string hostname,string database,string username ,string password,string cpooling, string port) + { + string s = "Server=" + hostname + ";Port=" + port + ";Database=" + database + + ";User ID=" + username + ";Password=" + password + ";Pooling=" + cpooling + ";"; + Initialise(s); + } + + + public MySQLMoneyManager(string connect) + { + Initialise(connect); + } + + + private void Initialise(string connect) + { + try { + connectString = connect; + dbcon = new MySqlConnection(connectString); + try { + dbcon.Open(); + } + catch (Exception e) { + throw new Exception("[MONEY MANAGER]: Connection error while using connection string ["+connectString+"]", e); + } + //m_log.Info("[MONEY MANAGER]: Connection established"); + } + + catch(Exception e) { + throw new Exception("[MONEY MANAGER]: Error initialising MySql Database: " + e.ToString()); + } + + try { + Dictionary tableList = new Dictionary(); + tableList = CheckTables(); + + // + // Balances Table + 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; + } + } + + // + // UserInfo Table + 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; + } + } + + // + // Transactions Table + if (!tableList.ContainsKey(Table_of_Transactions)) { + try { + CreateTransactionsTable(); + } + catch (Exception e) { + throw new Exception("[MONEY MANAGER]: Error creating transactions table: " + e.ToString()); + } + } + // check transactions table version + 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; + } + } + + // + // TotalSales Table + 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; + } + } + } + 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 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; + } + + + + /////////////////////////////////////////////////////////////////////// + // create Tables + + 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(); + } + + + 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(); + } + + + 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(); + } + + + 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(); + } + + + + /////////////////////////////////////////////////////////////////////// + // update Balances Table + + 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=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 uuid, url, name, pass + bool updatedb = true; + for (int i=0; i + /// 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() + { + //m_log.Info("[MONEY MANAGER]: Converting Transaction Table..."); + 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 + /// 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() + { + //m_log.Info("[MONEY MANAGER]: Converting Transaction Table..."); + 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 + /// 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 + + 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(); + } + + + 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(); + } + + + /////////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////////// + // + + 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; + } + } + + + /// + /// Reconnect to the database + /// + 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()); + } + } + } + + + + /////////////////////////////////////////////////////////////////////// + // + // balances + // + + /// + /// 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) { + m_log.ErrorFormat("[MoneyDB]: MySql failed to fetch balance {0}.", userID); + retValue = -2; + } + dbReader.Close(); + } + cmd.Dispose(); + + return retValue; + } + + + 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; + } + + + 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; + } + + + + /////////////////////////////////////////////////////////////////////// + // + // totalsales + // + 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 0) bRet = true; + + cmd.Dispose(); + return bRet; + } + + + 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; + + } + + + 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 0) bRet = true; + + cmd.Dispose(); + return bRet; + } + + + 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; + } + + + 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; + } + + + 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; + } + + + 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(); + } + + + 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 + // + public bool addUserInfo(UserInfo userInfo) + { + //m_log.Error("[MONEY MANAGER]: Adding UserInfo: " + userInfo.UserID); + + 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; + } + + + public UserInfo fetchUserInfo(string userID) + { + //m_log.Error("[MONEY MANAGER]: Fetching UserInfo: " + 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; + } + + + public bool updateUserInfo(UserInfo userInfo) + { + //m_log.Error("[MONEY MANAGER]: Updating UserInfo: " + userInfo.UserID); + + 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; + } + + } +} diff --git a/release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs b/release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs new file mode 100644 index 0000000..cd2bae4 --- /dev/null +++ b/release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs @@ -0,0 +1,52 @@ +/* + * 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 OpenSimulator 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. + */ +using System; +using System.Threading; + +namespace OpenSim.Data.MySQL.MySQLMoneyDataWrapper +{ + // This bit of code is from OpenSim.Data.MySQLSuperManager + public class MySQLSuperManager + { + public bool Locked; + private readonly Mutex m_lock = new Mutex(false); + public MySQLMoneyManager Manager; + public string Running; + + public void GetLock() + { + Locked = true; + m_lock.WaitOne(); + } + + public void Release() + { + m_lock.ReleaseMutex(); + Locked = false; + } + } +} diff --git a/release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/Properties/AssemblyInfo.cs b/release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f4a2a82 --- /dev/null +++ b/release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +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 +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs b/release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs new file mode 100644 index 0000000..e73b579 --- /dev/null +++ b/release/0.9.1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs @@ -0,0 +1,237 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +using System.Text; +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_objectID = "00000000-0000-0000-0000-000000000000"; + 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; + +/* + public TransactionData(string uuid, string sender, string receiver, + int amount, int time, int status, string description) + { + this.m_uuid = uuid; + this.m_sender = sender; + this.m_receiver = receiver; + this.m_amount = amount; + } +*/ + + public UUID TransUUID + { + get { return m_uuid; } + set { m_uuid = value; } + } + + public string Sender + { + get { return m_sender; } + set { m_sender = value; } + } + + public string Receiver + { + get { return m_receiver; } + set { m_receiver = value; } + } + + public int Amount + { + get { return m_amount; } + set { m_amount = value; } + } + + public int SenderBalance + { + get { return m_senderBalance; } + set { m_senderBalance = value; } + } + + public int ReceiverBalance + { + get { return m_receiverBalance; } + set { m_receiverBalance = value; } + } + + public int Type + { + get { return m_type; } + set { m_type = value; } + } + + public int Time + { + get { return m_time; } + set { m_time = value; } + } + + public int Status + { + get { return m_status; } + set { m_status = value; } + } + + public string Description + { + get { return m_description; } + set { m_description = value; } + } + + public string ObjectUUID + { + get { return m_objectID; } + set { m_objectID = value; } + } + + public string ObjectName + { + get { return m_objectName; } + set { m_objectName = value; } + } + + public string RegionHandle + { + get { return m_regionHandle; } + set { m_regionHandle = value; } + } + + public string RegionUUID + { + get { return m_regionUUID; } + set { m_regionUUID = value; } + } + + public string SecureCode + { + get { return m_secureCode; } + set { m_secureCode = value; } + } + + 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; + + public string UserID + { + get { return m_userID; } + set { m_userID = value; } + } + + public string SimIP + { + get { return m_simIP; } + set { m_simIP = value; } + } + + public string Avatar + { + get { return m_avatarName; } + set { m_avatarName = value; } + } + + public string PswHash + { + get { return m_passwordHash; } + set { m_passwordHash = value; } + } + + public int Type + { + get { return m_avatarType; } + set { m_avatarType = value; } + } + + public int Class + { + get { return m_avatarClass; } + set { m_avatarClass = value; } + } + + public string ServerURL + { + get { return m_serverURL; } + set { m_serverURL = value; } + } + } +} diff --git a/release/0.9.1/src/OpenSim.Grid.MoneyServer/IMoneyDBService.cs b/release/0.9.1/src/OpenSim.Grid.MoneyServer/IMoneyDBService.cs new file mode 100644 index 0000000..7140c0f --- /dev/null +++ b/release/0.9.1/src/OpenSim.Grid.MoneyServer/IMoneyDBService.cs @@ -0,0 +1,70 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +//using System.Linq; +using System.Text; +using OpenMetaverse; +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; + + +namespace OpenSim.Grid.MoneyServer +{ + public interface IMoneyDBService + { + int getBalance(string userID); + + bool withdrawMoney(UUID transactionID, string senderID, int amount); + + bool giveMoney(UUID transactionID, string receiverID, int amount); + + bool addTransaction(TransactionData transaction); + + bool addUser(string userID, int balance, int status, int type); + + bool updateTransactionStatus(UUID transactionID, int status, string description); + + bool SetTransExpired(int deadTime); + + bool ValidateTransfer(string secureCode, UUID transactionID); + + TransactionData FetchTransaction(UUID transactionID); + + TransactionData FetchTransaction(string userID, int startTime, int endTime, int lastIndex); + + int getTransactionNum(string userID, int startTime, int endTime); + + bool DoTransfer(UUID transactionUUID); + + bool DoAddMoney(UUID transactionUUID); // Added by Fumi.Iseki + + bool TryAddUserInfo(UserInfo user); + + UserInfo FetchUserInfo(string userID); + } +} diff --git a/release/0.9.1/src/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs b/release/0.9.1/src/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs new file mode 100644 index 0000000..d6e3721 --- /dev/null +++ b/release/0.9.1/src/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs @@ -0,0 +1,52 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +//using System.Linq; +using System.Text; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; + +using Nini.Config; + + +namespace OpenSim.Grid.MoneyServer +{ + public interface IMoneyServiceCore + { + BaseHttpServer GetHttpServer(); + Dictionary GetSessionDic(); + Dictionary GetSecureSessionDic(); + Dictionary GetWebSessionDic(); + + // + IConfig GetServerConfig(); + IConfig GetCertConfig(); + bool IsCheckClientCert(); + } +} diff --git a/release/0.9.1/src/OpenSim.Grid.MoneyServer/MoneyDBService.cs b/release/0.9.1/src/OpenSim.Grid.MoneyServer/MoneyDBService.cs new file mode 100644 index 0000000..efa72b4 --- /dev/null +++ b/release/0.9.1/src/OpenSim.Grid.MoneyServer/MoneyDBService.cs @@ -0,0 +1,570 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; +using OpenSim.Modules.Currency; +using log4net; +using System.Reflection; +using OpenMetaverse; + + +namespace OpenSim.Grid.MoneyServer +{ + class MoneyDBService: IMoneyDBService + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private string m_connect; + //private MySQLMoneyManager m_moneyManager; + private long TicksToEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; + + // DB manager pool + protected Dictionary m_dbconnections = new Dictionary(); // Lock付 + private int m_maxConnections; + + public int m_lastConnect = 0; + + + public MoneyDBService(string connect) + { + m_connect = connect; + Initialise(m_connect,10); + } + + + public MoneyDBService() + { + } + + + public void Initialise(string connectionString, int maxDBConnections) + { + m_connect = connectionString; + m_maxConnections = maxDBConnections; + if (connectionString != string.Empty) { + //m_moneyManager = new MySQLMoneyManager(connectionString); + + //m_log.Info("Creating " + m_maxConnections + " DB connections..."); + for (int i=0; im_maxConnections) { + lockedCons = 0; + System.Threading.Thread.Sleep(1000); // Wait some time before searching them again. + m_log.Debug("WARNING: All threads are in use. Probable cause: Something didnt release a mutex properly, or high volume of requests inbound."); + } + } + } + + + public int getBalance(string userID) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.getBalance(userID); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + dbm.Manager.Reconnect(); + return dbm.Manager.getBalance(userID); + } + catch(Exception e) { + m_log.Error(e.ToString()); + return 0; + } + finally { + dbm.Release(); + } + } + + + 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) { + dbm.Manager.Reconnect(); + return dbm.Manager.withdrawMoney(transactionID, senderID, amount); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool giveMoney(UUID transactionID, string receiverID, int amount) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.giveMoney(transactionID, receiverID, amount); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + dbm.Manager.Reconnect(); + return dbm.Manager.giveMoney(transactionID, receiverID, amount); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool setTotalSale(TransactionData transaction) + { + if (transaction.Receiver==transaction.Sender) return false; + if (transaction.Sender==UUID.Zero.ToString()) return false; + + MySQLSuperManager dbm = GetLockedConnection(); + + 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) { + dbm.Manager.Reconnect(); + return dbm.Manager.setTotalSale(transaction.Receiver, transaction.ObjectUUID, transaction.Type, 1, transaction.Amount, time); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool addTransaction(TransactionData transaction) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.addTransaction(transaction); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + dbm.Manager.Reconnect(); + return dbm.Manager.addTransaction(transaction); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool addUser(string userID, int balance, int status, int type) + { + 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) return false; + + // + MySQLSuperManager dbm = GetLockedConnection(); + + try { + ret = dbm.Manager.addUser(userID, 0, status, type); // make Balance Table + } + catch (MySql.Data.MySqlClient.MySqlException e) { + dbm.Manager.Reconnect(); + ret = dbm.Manager.addUser(userID, 0, status, type); // make Balance Table + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + + // + if (ret) ret = giveMoney(transaction.TransUUID, userID, balance); + return ret; + } + + + 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) { + dbm.Manager.Reconnect(); + return dbm.Manager.updateTransactionStatus(transactionID, status, description); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool SetTransExpired(int deadTime) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.SetTransExpired(deadTime); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + dbm.Manager.Reconnect(); + return dbm.Manager.SetTransExpired(deadTime); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool ValidateTransfer(string secureCode, UUID transactionID) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.ValidateTransfer(secureCode, transactionID); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + dbm.Manager.Reconnect(); + return dbm.Manager.ValidateTransfer(secureCode, transactionID); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public TransactionData FetchTransaction(UUID transactionID) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.FetchTransaction(transactionID); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + dbm.Manager.Reconnect(); + return dbm.Manager.FetchTransaction(transactionID); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return null; + } + finally { + dbm.Release(); + } + } + + + 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) { + dbm.Manager.Reconnect(); + arrTransaction = dbm.Manager.FetchTransaction(userID, startTime, endTime, index, 1); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return null; + } + finally { + dbm.Release(); + } + + // + if (arrTransaction.Length > 0) { + return arrTransaction[0]; + } + else { + return null; + } + } + + + public bool DoTransfer(UUID transactionUUID) + { + 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 DB. {0}", transaction.Receiver); + return false; + } + + if (giveMoney(transactionUUID, transaction.Receiver, transaction.Amount)) { + do_trans = true; + } + else { // give money to receiver failed. 返金処理 + 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); + } + + return do_trans; + } + + + // by Fumi.Iseki + public bool DoAddMoney(UUID transactionUUID) + { + 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 DB. {0}", transaction.Receiver); + return false; + } + // + if (giveMoney(transactionUUID, transaction.Receiver, transaction.Amount)) { + setTotalSale(transaction); + 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()); + } + + return false; + } + + + /////////////////////////////////////////////////////////////////////////////////////////////////////// + // + // userinfo + // + + public bool TryAddUserInfo(UserInfo user) + { + MySQLSuperManager dbm = GetLockedConnection(); + + UserInfo userInfo = null; + + try { + userInfo = dbm.Manager.fetchUserInfo(user.UserID); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + dbm.Manager.Reconnect(); + userInfo = dbm.Manager.fetchUserInfo(user.UserID); + } + catch (Exception e) { + m_log.Error(e.ToString()); + 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(); + } + } + + + public UserInfo FetchUserInfo(string userID) + { + UserInfo userInfo = null; + MySQLSuperManager dbm = GetLockedConnection(); + + try { + userInfo = dbm.Manager.fetchUserInfo(userID); + return userInfo; + } + catch (MySql.Data.MySqlClient.MySqlException e) { + dbm.Manager.Reconnect(); + userInfo = dbm.Manager.fetchUserInfo(userID); + return userInfo; + } + catch (Exception e) { + m_log.Error(e.ToString()); + return null; + } + finally { + dbm.Release(); + } + } + + + 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) { + dbm.Manager.Reconnect(); + return dbm.Manager.getTransactionNum(userID,startTime,endTime); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return -1; + } + finally { + dbm.Release(); + } + } + } +} diff --git a/release/0.9.1/src/OpenSim.Grid.MoneyServer/MoneyServerBase.cs b/release/0.9.1/src/OpenSim.Grid.MoneyServer/MoneyServerBase.cs new file mode 100644 index 0000000..654e4d1 --- /dev/null +++ b/release/0.9.1/src/OpenSim.Grid.MoneyServer/MoneyServerBase.cs @@ -0,0 +1,338 @@ +/* + * 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. + */ + +using System; +using System.IO; +using System.Collections.Generic; +using System.Text; +using System.Net; +using System.Net.Security; +using System.Reflection; +using System.Timers; +//using System.Security.Authentication; +//using System.Security.Cryptography; +//using System.Security.Cryptography.X509Certificates; + +using HttpServer; +using Nini.Config; +using log4net; + +using OpenSim.Framework; +using OpenSim.Framework.Console; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Data; + +using NSL.Certificate.Tools; + + + +namespace OpenSim.Grid.MoneyServer +{ + class MoneyServerBase : BaseOpenSimServer, IMoneyServiceCore + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private string connectionString = string.Empty; + private uint m_moneyServerPort = 8008; + + 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; + + private MoneyXmlRpcModule m_moneyXmlRpcModule; + private MoneyDBService m_moneyDBService; + + private NSLCertificateVerify m_certVerify = new NSLCertificateVerify(); // クライアント認証用 + + 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() + { + m_console = new LocalConsole("Money "); +// m_console = new CommandConsole("Money "); + MainConsole.Instance = m_console; + } + + + public void Work() + { + //m_console.Notice("Enter help for a list of commands\n"); + + //The timer checks the transactions table every 60 seconds + Timer checkTimer = new Timer(); + checkTimer.Interval = 60*1000; + checkTimer.Enabled = true; + checkTimer.Elapsed += new ElapsedEventHandler(CheckTransaction); + checkTimer.Start(); + + while (true) { + m_console.Prompt(); + } + } + + + /// + /// Check the transactions table, set expired transaction state to failed + /// + private void CheckTransaction(object sender, ElapsedEventArgs e) + { + 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); + } + + + 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); + if (m_checkClientCert) { + Type typeBaseHttpServer = typeof(BaseHttpServer); // BaseHttpServer.cs にパッチがあたっていない場合のため + PropertyInfo pinfo = typeBaseHttpServer.GetProperty("CertificateValidationCallback"); + + if (pinfo!=null) { + //m_httpServer.CertificateValidationCallback = (RemoteCertificateValidationCallback)m_certVerify.ValidateClientCertificate; + pinfo.SetValue(m_httpServer, (RemoteCertificateValidationCallback)m_certVerify.ValidateClientCertificate, null); + m_log.Info ("[MONEY SERVER]: Set RemoteCertificateValidationCallback"); + } + else { + m_log.Error("[MONEY SERVER]: StartupSpecific: CheckClientCert is true. But this MoneyServer does not support CheckClientCert!!"); + } + } + } + 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); + } + + //TODO : Add some console commands here + } + + + protected void ReadIniConfig() + { + MoneyServerConfigSource moneyConfig = new MoneyServerConfigSource(); + Config = moneyConfig.m_config; // for base.StartupSpecific() + + 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); + + // + // [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. Cert file is " + m_certFilename); + } + + // クライアント認証 + 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); + } + } + } + + catch (Exception) { + m_log.Error("[MONEY SERVER]: ReadIniConfig: Fail to setup configure. Please check MoneyServer.ini. Exit"); + Environment.Exit(1); + } + } + + + // 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); + + m_moneyXmlRpcModule = new MoneyXmlRpcModule(); +// m_moneyXmlRpcModule.Initialise(m_version, m_server_config, m_cert_config, m_moneyDBService, this); + m_moneyXmlRpcModule.Initialise(m_version, m_moneyDBService, this); + 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); + } + else { + //TODO: create default configuration. + //m_config = DefaultConfig(); + } + } + + + public void Save(string path) + { + m_config.Save(path); + } + + } +} diff --git a/release/0.9.1/src/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs b/release/0.9.1/src/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs new file mode 100644 index 0000000..e28e4cc --- /dev/null +++ b/release/0.9.1/src/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs @@ -0,0 +1,1740 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Reflection; +using System.Collections; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +using log4net; +using Nini.Config; +using Nwc.XmlRpc; + +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; +using OpenSim.Modules.Currency; +using OpenSim.Region.Framework.Scenes; + +using NSL.Network.XmlRpc; +using NSL.Certificate.Tools; + + +namespace OpenSim.Grid.MoneyServer +{ + class MoneyXmlRpcModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private int m_defaultBalance = 1000; + // + private bool m_forceTransfer = false; + private string m_bankerAvatar = ""; + + private bool m_scriptSendMoney = false; + private string m_scriptAccessKey = ""; + private string m_scriptIPaddress = "127.0.0.1"; + + 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 bool m_checkServerCert = false; + private string m_cacertFilename = ""; + + private string m_certFilename = ""; + private string m_certPassword = ""; + private X509Certificate2 m_clientCert = null; + + private string m_sslCommonName = ""; + + private NSLCertificateVerify m_certVerify = new NSLCertificateVerify(); // サーバ認証用 + + + // Update Balance Messages + 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; + 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; + + + public MoneyXmlRpcModule() + { + } + + + public void Initialise(string opensimVersion, IMoneyDBService moneyDBService, IMoneyServiceCore moneyCore) + { + //m_opensimVersion = opensimVersion; + m_moneyDBService = moneyDBService; + m_moneyCore = moneyCore; + m_server_config = m_moneyCore.GetServerConfig(); // [MoneyServer] Section + m_cert_config = m_moneyCore.GetCertConfig(); // [Certificate] Section + + //////////////////////////////////////////////////////////////////////// + // [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); + + // 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) + // クライアント証明書 + m_certFilename = m_cert_config.GetString("ClientCertFilename", m_certFilename); + m_certPassword = m_cert_config.GetString("ClientCertPassword", m_certPassword); + if (m_certFilename!="") { + m_clientCert = new X509Certificate2(m_certFilename, m_certPassword); + //m_clientCert = new X509Certificate2(m_certFilename, m_certPassword, X509KeyStorageFlags.MachineKeySet); + m_log.Info("[MONEY RPC]: Initialise: Issue Authentication of Client. Cert file is " + m_cacertFilename); + } + + // サーバ認証 + m_checkServerCert = m_cert_config.GetBoolean("CheckServerCert", m_checkServerCert); + + // CA + m_cacertFilename = m_cert_config.GetString("CACertFilename", m_cacertFilename); + if (m_checkServerCert && m_cacertFilename!="") { + m_certVerify.SetPrivateCA(m_cacertFilename); + ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(m_certVerify.ValidateServerCertificate); + m_log.Info("[MONEY RPC]: Initialise: Execute Authentication of Server. CA file is " + m_cacertFilename); + } + else { + m_checkServerCert = false; + ServicePointManager.ServerCertificateValidationCallback = null; + } + + m_sessionDic = m_moneyCore.GetSessionDic(); + m_secureSessionDic = m_moneyCore.GetSecureSessionDic(); + m_webSessionDic = m_moneyCore.GetWebSessionDic(); + RegisterHandlers(); + } + + + public void PostInitialise() + { + } + + + 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); // added + m_httpServer.AddXmlRPCHandler("MoveMoney", handleScriptTransaction); // added + + // 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); + } + + + // + 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; + } + + + // + public string GetSSLCommonName() + { + return m_sslCommonName; + } + + + /// + /// Get the user balance when user entering a parcel. + /// + /// + /// + public XmlRpcResponse handleClientLogin(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleClientLogin:"); + + 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 RPC]: 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 RPC]: 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; + + m_log.InfoFormat("[MONEY RPC]: handleClientLogon: Avatar {0} ({1}) is logged on.", userName, clientUUID); + m_log.InfoFormat("[MONEY RPC]: handleClientLogon: Avatar Type is {0} and Avatar Class is {1}", avatarType, avatarClass); + + // + // Check Avatar + if (String.IsNullOrEmpty(serverURL)) { + responseData["description"] = "Server URL is empty. Avatar is a NPC?"; + m_log.InfoFormat("[MONEY RPC]: handleClientLogon: {0}", responseData["description"]); + return response; + } + else 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 RPC]: 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 RPC]: handleClientLogon: {0}", responseData["description"]); + return response; + } + else if (avatarClass==(int)AvatarType.NPC_AVATAR) { + responseData["description"] = "Avatar is a NPC."; + m_log.InfoFormat("[MONEY RPC]: handleClientLogon: {0}", responseData["description"]); + return response; + } + else if (avatarClass==(int)AvatarType.FOREIGN_AVATAR) { + responseData["description"] = "Avatar is a Foreign avatar."; + m_log.InfoFormat("[MONEY RPC]: handleClientLogon: {0}", responseData["description"]); + return response; + } + else if (avatarClass==(int)AvatarType.UNKNOWN_AVATAR) { + responseData["description"] = "Avatar is a Unknown avatar."; + m_log.InfoFormat("[MONEY RPC]: 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 RPC]: 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 RPC]: 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 RPC]: 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) + { + //m_log.InfoFormat("[MONEY RPC]: handleClientLogout:"); + + 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 RPC]: 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 RPC]: handleClientLogout: Failed to delete user session: " + e.ToString() ); + responseData["success"] = false; + return response; + } + + responseData["success"] = true; + return response; + } + + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // + /// + /// handle incoming transaction + /// + /// + /// + public XmlRpcResponse handleTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: 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"]; + + if (m_sessionDic.ContainsKey(senderID) && m_secureSessionDic.ContainsKey(senderID)) { + if (m_sessionDic[senderID]==senderSessionID && m_secureSessionDic[senderID]==senderSecureSessionID) { + m_log.InfoFormat("[MONEY RPC]: 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 RPC]: 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 RPC]: handleTransaction: Add transaction for user {0} failed.", senderID); + } + return response; + } + catch (Exception e) { + m_log.Error("[MONEY RPC]: handleTransaction: Exception occurred while adding transaction: " + e.ToString()); + } + return response; + } + } + + m_log.Error("[MONEY RPC]: handleTransaction: Session authentication failure for sender " + senderID); + responseData["message"] = "Session check failure, please re-login later!"; + return response; + } + + + // + // added by Fumi.Iseki + // + /// + /// handle incoming force transaction. no check senderSessionID and senderSecureSessionID + /// + /// + /// + public XmlRpcResponse handleForceTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleForceTransaction:"); + + 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 RPC]: handleForceTransaction: Not allowed force transfer of Money."); + m_log.Error("[MONEY RPC]: 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 RPC]: handleForceTransaction: Force 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 RPC]: 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 RPC]: handleForceTransaction: Add force transaction for user {0} failed.", senderID); + } + return response; + } + catch (Exception e) { + m_log.Error("[MONEY RPC]: handleForceTransaction: Exception occurred while adding force transaction: " + e.ToString()); + } + return response; + } + + + // + // added by Fumi.Iseki + // + /// + /// handle scripted sending money transaction. + /// + /// + /// + public XmlRpcResponse handleScriptTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: 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 RPC]: handleScriptTransaction: Not allowed send money to avatar!!"); + m_log.Error("[MONEY RPC]: 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 RPC]: handleScriptTransaction: Not allowed send money to avatar!!"); + m_log.Error("[MONEY RPC]: handleScriptTransaction: Not match Script Access Key."); + responseData["message"] = "not allowed send money to avatar! not match Script Key"; + return response; + } + + m_log.InfoFormat("[MONEY RPC]: 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 RPC]: 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 RPC]: 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 RPC]: 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 RPC]: 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 RPC]: handleScriptTransaction: Add force transaction for user {0} failed.", transaction.Sender); + } + return response; + } + catch (Exception e) { + m_log.Error("[MONEY RPC]: handleScriptTransaction: Exception occurred while adding money transaction: " + e.ToString()); + } + return response; + } + + + // + // added by Fumi.Iseki + // + /// + /// handle adding money transaction. + /// + /// + /// + public XmlRpcResponse handleAddBankerMoney(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleAddBankerMoney:"); + + 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 RPC]: handleAddBankerMoney: Not allowed add money to avatar!!"); + m_log.Error("[MONEY RPC]: 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 RPC]: 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 RPC]: 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 RPC]: 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 RPC]: handleAddBankerMoney: Add force transaction for user {0} failed.", senderID); + } + return response; + } + catch (Exception e) { + m_log.Error("[MONEY RPC]: handleAddBankerMoney: Exception occurred while adding money transaction: " + e.ToString()); + } + return response; + } + + + // + // added by Fumi.Iseki + // + /// + /// handle pay charge transaction. no check receiver information. + /// + /// + /// + public XmlRpcResponse handlePayMoneyCharge(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handlePayMoneyCharge:"); + + 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(); + + 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 (m_sessionDic.ContainsKey(senderID) && m_secureSessionDic.ContainsKey(senderID)) { + if (m_sessionDic[senderID]==senderSessionID && m_secureSessionDic[senderID]==senderSecureSessionID) { + m_log.InfoFormat("[MONEY RPC]: handlePayMoneyCharge: Pay from {0}", senderID); + 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(); + + 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 message = string.Format(m_BalanceMessagePayCharge, amount, "SYSTEM", ""); + responseData["success"] = NotifyTransfer(transactionUUID, message, "", ""); + } + 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 RPC]: handlePayMoneyCharge: Pay money transaction for user {0} failed.", senderID); + } + return response; + } + catch (Exception e) { + m_log.Error("[MONEY RPC]: handlePayMoneyCharge: Exception occurred while pay money transaction: " + e.ToString()); + } + return response; + } + } + + m_log.Error("[MONEY RPC]: handlePayMoneyCharge: Session authentication failure for sender " + senderID); + responseData["message"] = "Session check failure, please re-login later!"; + return response; + } + + + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // + // added by Fumi.Iseki + // + /// + /// Continue transaction with no confirm. + /// + /// + /// + public bool NotifyTransfer(UUID transactionUUID, string msg2sender, string msg2receiver, string objectName) + { + //m_log.InfoFormat("[MONEY RPC]: 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 RPC]: NotifyTransfer: Transaction Type = {0}", transaction.Type); + m_log.InfoFormat("[MONEY RPC]: 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) return true; + 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 RPC]: 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 RPC]: NotifyTransfer: User {0} can't get the object, rolling back.", transaction.Sender); + if (RollBackTransaction(transaction)) { + m_log.ErrorFormat("[MONEY RPC]: NotifyTransfer: Transaction {0} failed but roll back succeeded.", transactionUUID.ToString()); + } + else { + m_log.ErrorFormat("[MONEY RPC]: NotifyTransfer: Transaction {0} failed and roll back failed as well.", + transactionUUID.ToString()); + } + } + else { + m_log.InfoFormat("[MONEY RPC]: NotifyTransfer: Transaction {0} finished successfully.", transactionUUID.ToString()); + return true; + } + } + } + return false; + } + return true; + } + } + m_log.ErrorFormat("[MONEY RPC]: NotifyTransfer: Transaction {0} failed.", transactionUUID.ToString()); + } + catch (Exception e) { + m_log.ErrorFormat("[MONEY RPC]: NotifyTransfer: exception occurred when transaction {0}: {1}", transactionUUID.ToString(), e.ToString()); + } + + return false; + } + + + + /// + /// Get the user balance. + /// + /// + /// + public XmlRpcResponse handleGetBalance(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleGetBalance:"); + + 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 RPC]: 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 RPC]: handleGetBalance: Can't get balance for user {0}, Exception {1}", clientUUID, e.ToString()); + } + return response; + } + } + + m_log.Error("[MONEY RPC]: handleGetBalance: Session authentication failed when getting balance for user " + clientUUID); + responseData["description"] = "Session check failure, please re-login"; + return response; + } + + + /// + /// 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, string uri) + { + //m_log.InfoFormat("[MONEY RPC]: genericCurrencyXMLRPCRequest: to {0}", uri); + + if (reqParams.Count<=0 || string.IsNullOrEmpty(method)) return null; + + if (m_checkServerCert) { + if (!uri.StartsWith("https://")) { + m_log.InfoFormat("[MONEY RPC]: 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 RPC]: genericCurrencyXMLRPCRequest: Invalid Region Server URL: {0}", uri); + return null; + } + } + + ArrayList arrayParams = new ArrayList(); + arrayParams.Add(reqParams); + XmlRpcResponse moneyServResp = null; + try { + //XmlRpcRequest moneyModuleReq = new XmlRpcRequest(method, arrayParams); + //moneyServResp = moneyModuleReq.Send(uri, MONEYMODULE_REQUEST_TIMEOUT); + NSLXmlRpcRequest moneyModuleReq = new NSLXmlRpcRequest(method, arrayParams); + moneyServResp = moneyModuleReq.certSend(uri, m_clientCert, m_checkServerCert, MONEYMODULE_REQUEST_TIMEOUT); + } + catch (Exception ex) { + m_log.ErrorFormat("[MONEY RPC]: genericCurrencyXMLRPCRequest: Unable to connect to Region Server {0}", uri); + m_log.ErrorFormat("[MONEY RPC]: 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; + } + + + /// + /// Update the client balance.We don't care about the result. + /// + /// + private void UpdateBalance(string userID, string message) + { + //m_log.InfoFormat("[MONEY RPC]: UpdateBalance: ID = {0}, Message = {1}", userID, message); + + string sessionID = string.Empty; + string secureID = string.Empty; + + if (m_sessionDic.ContainsKey(userID) && m_secureSessionDic.ContainsKey(userID)) { + sessionID = m_sessionDic[userID]; + secureID = m_secureSessionDic[userID]; + + Hashtable requestTable = new Hashtable(); + requestTable["clientUUID"] = userID; + requestTable["clientSessionID"] = sessionID; + requestTable["clientSecureSessionID"] = secureID; + requestTable["Balance"] = m_moneyDBService.getBalance(userID); + if (message!="") requestTable["Message"] = message; + + UserInfo user = m_moneyDBService.FetchUserInfo(userID); + if (user!=null) { + genericCurrencyXMLRPCRequest(requestTable, "UpdateBalance", user.SimIP); + m_log.InfoFormat("[MONEY RPC]: UpdateBalance: Sended UpdateBalance Request to {0}", user.SimIP.ToString()); + } + } + } + + + /// + /// RollBack the transaction if user failed to get the object paid + /// + /// + /// + protected bool RollBackTransaction(TransactionData transaction) + { + //m_log.InfoFormat("[MONEY RPC]: RollBackTransaction:"); + + if(m_moneyDBService.withdrawMoney(transaction.TransUUID, transaction.Receiver, transaction.Amount)) { + if(m_moneyDBService.giveMoney(transaction.TransUUID, transaction.Sender, transaction.Amount)) { + m_log.InfoFormat("[MONEY RPC]: 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; + } + + + // + public XmlRpcResponse handleCancelTransfer(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleCancelTransfer:"); + + 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 RPC]: 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 RPC]: handleCancelTransfer: User {0} wanted to cancel the transaction.", user.Avatar); + if (m_moneyDBService.ValidateTransfer(secureCode, transactionUUID)) { + m_log.InfoFormat("[MONEY RPC]: 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 RPC]: handleCancelTransfer: Exception occurred when transaction {0}: {1}", transactionID, e.ToString()); + } + return response; + } + + + // + public XmlRpcResponse handleGetTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleGetTransaction:"); + + 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 RPC]: 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 RPC]: handleGetTransaction: {0}", e.ToString()); + m_log.ErrorFormat("[MONEY RPC]: 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; + } + + + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + // In development + // + + public XmlRpcResponse handleWebLogin(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleWebLogin:"); + + 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 RPC]: handleWebLogin: User {0} has logged in from web.", userID); + responseData["success"] = true; + return response; + } + + + // + public XmlRpcResponse handleWebLogout(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleWebLogout:"); + + 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 RPC]: handleWebLogout: User {0} has logged out from web.", userID); + responseData["success"] = true; + return response; + } + + + /// + /// Get balance method for web pages. + /// + /// + /// + public XmlRpcResponse handleWebGetBalance(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleWebGetBalance:"); + + 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 RPC]: 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 RPC]: 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 RPC]: handleWebLogout: Session authentication failed when getting balance for user " + userID); + responseData["errorMessage"] = "Session check failure, please re-login"; + return response; + } + + + /// + /// Get transaction for web pages + /// + /// + /// + public XmlRpcResponse handleWebGetTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleWebGetTransaction:"); + + 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 RPC]: 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 RPC]: 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 RPC]: handleWebGetTransaction: Session authentication failed when getting transaction for user " + userID); + responseData["errorMessage"] = "Session check failure, please re-login"; + return response; + } + + + /// + /// Get total number of transactions for web pages. + /// + /// + /// + public XmlRpcResponse handleWebGetTransactionNum(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleWebGetTransactionNum:"); + + 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 RPC]: handleWebGetTransactionNum: Get {0} transactions for user {1}", it, userID); + responseData["success"] = true; + responseData["number"] = it; + } + return response; + } + } + + m_log.Error("[MONEY RPC]: handleWebGetTransactionNum: Session authentication failed when getting transaction number for user " + userID); + responseData["errorMessage"] = "Session check failure, please re-login"; + return response; + } + } + +} + diff --git a/release/0.9.1/src/OpenSim.Grid.MoneyServer/Program.cs b/release/0.9.1/src/OpenSim.Grid.MoneyServer/Program.cs new file mode 100644 index 0000000..3fbd12d --- /dev/null +++ b/release/0.9.1/src/OpenSim.Grid.MoneyServer/Program.cs @@ -0,0 +1,42 @@ +/* + * 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. + */ + +using log4net.Config; + +namespace OpenSim.Grid.MoneyServer +{ + class Program + { + public static void Main(string[] args) + { + XmlConfigurator.Configure(); + MoneyServerBase app = new MoneyServerBase(); + app.Startup(); + app.Work(); + } + } +} diff --git a/release/0.9.1/src/OpenSim.Grid.MoneyServer/Properties/AssemblyInfo.cs b/release/0.9.1/src/OpenSim.Grid.MoneyServer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..407ecee --- /dev/null +++ b/release/0.9.1/src/OpenSim.Grid.MoneyServer/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +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.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 +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/release/0.9.1/src/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs b/release/0.9.1/src/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs new file mode 100644 index 0000000..d39af60 --- /dev/null +++ b/release/0.9.1/src/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs @@ -0,0 +1,1903 @@ +// * 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. +// */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Reflection; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +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; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; +using NSL.Certificate.Tools; +using NSL.Network.XmlRpc; + + + +[assembly: Addin("DTLNSLMoneyModule", "1.0")] +[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)] + + + +namespace OpenSim.Modules.Currency +{ + // + 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 + } + + +/* + // Refer to OpenMetaverse + public enum OpenMetaverse.MoneyTransactionType : int + { + None = 0, + FailSimulatorTimeout = 1, + FailDataserverTimeout = 2, + ObjectClaim = 1000, + LandClaim = 1001, + GroupCreate = 1002, + ObjectPublicClaim = 1003, + GroupJoin = 1004, + TeleportCharge = 1100, + UploadCharge = 1101, + LandAuction = 1102, + ClassifiedCharge = 1103, + ObjectTax = 2000, + LandTax = 2001, + LightTax = 2002, + ParcelDirFee = 2003, + GroupTax = 2004, + ClassifiedRenew = 2005, + GiveInventory = 3000, + ObjectSale = 5000, + Gift = 5001, + LandSale = 5002, + ReferBonus = 5003, + InventorySale = 5004, + RefundPurchase = 5005, + LandPassSale = 5006, + DwellBonus = 5007, + PayObject = 5008, + ObjectPays = 5009, + GroupLandDeed = 6001, + GroupObjectDeed = 6002, + GroupLiability = 6003, + GroupDividend = 6004, + GroupMembershipDues = 6005, + ObjectRelease = 8000, + LandRelease = 8001, + ObjectDelete = 8002, + ObjectPublicDecay = 8003, + ObjectPublicDelete = 8004, + LindenAdjustment = 9000, + LindenGrant = 9001, + LindenPenalty = 9002, + EventFee = 9003, + EventPrize = 9004, + StipendBasic = 10000, + StipendDeveloper = 10001, + StipendAlways = 10002, + StipendDaily = 10003, + StipendRating = 10004, + StipendDelta = 10005 + } +*/ + + + // + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "DTLNSLMoneyModule")] + public class DTLNSLMoneyModule : IMoneyModule, ISharedRegionModule + { + #region Constant numbers and members. + + // 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 X509Certificate2 m_cert = null; + + 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(); // サーバ認証用 + + + /// + /// Scene dictionary indexed by Region Handle + /// + private Dictionary m_sceneList = new Dictionary(); + + /// + /// To cache the balance data while the money server is not available. + /// + 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; + + #endregion + + + // + public void Initialise(Scene scene, IConfigSource source) + { + Initialise(source); + if (string.IsNullOrEmpty(m_moneyServURL)) m_enable_server = false; + // + AddRegion(scene); + } + + + #region ISharedRegionModule interface + + public void Initialise(IConfigSource source) + { + //m_log.InfoFormat("[MONEY]: Initialise:"); + + // Handle the parameters errors. + if (source==null) return; + + try { + m_config = source; + + // [Economy] section + IConfig economyConfig = m_config.Configs["Economy"]; + + if (economyConfig.GetString("EconomyModule")!=Name) { + //m_enabled = false; + m_log.InfoFormat("[MONEY]: The DTL/NSL MoneyModule is disabled"); + return; + } + else { + m_log.InfoFormat("[MONEY]: The DTL/NSL MoneyModule is enabled"); + } + + m_sellEnabled = economyConfig.GetBoolean("SellEnabled", m_sellEnabled); + m_moneyServURL = economyConfig.GetString("CurrencyServer", m_moneyServURL); + + // クライアント証明書 + m_certFilename = economyConfig.GetString("ClientCertFilename", m_certFilename); + m_certPassword = economyConfig.GetString("ClientCertPassword", m_certPassword); + if (m_certFilename!="") { + m_cert = new X509Certificate2(m_certFilename, m_certPassword); + //m_cert = new X509Certificate2(m_certFilename, m_certPassword, X509KeyStorageFlags.MachineKeySet); + m_log.InfoFormat("[MONEY]: Issue Authentication of Client. Cert File is " + m_certFilename); + } + + // サーバ認証 + m_checkServerCert = economyConfig.GetBoolean("CheckServerCert", m_checkServerCert); + m_cacertFilename = economyConfig.GetString("CACertFilename", m_cacertFilename); + if (m_cacertFilename!="") { + m_certVerify.SetPrivateCA(m_cacertFilename); + m_log.InfoFormat("[MONEY]: Execute Authentication of Server. CA Cert File is " + m_cacertFilename); + } + else { + m_checkServerCert = false; + } + ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(m_certVerify.ValidateServerCertificate); + + + // Settlement + m_use_web_settle = economyConfig.GetBoolean("SettlementByWeb", m_use_web_settle); + m_settle_url = economyConfig.GetString ("SettlementURL", m_settle_url); + m_settle_message = economyConfig.GetString ("SettlementMessage", m_settle_message); + + // Price + 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); + PriceGroupCreate = economyConfig.GetInt ("PriceGroupCreate", PriceGroupCreate); + TeleportMinPrice = economyConfig.GetInt ("TeleportMinPrice", TeleportMinPrice); + TeleportPriceExponent = economyConfig.GetFloat("TeleportPriceExponent", TeleportPriceExponent); + EnergyEfficiency = economyConfig.GetFloat("EnergyEfficiency", EnergyEfficiency); + + // for HG Avatar + string avatar_class = economyConfig.GetString("HGAvatarAs", "HGAvatar").ToLower(); + if (avatar_class=="localavatar") m_hg_avatarClass = (int)AvatarType.LOCAL_AVATAR; + else if (avatar_class=="guestavatar") m_hg_avatarClass = (int)AvatarType.GUEST_AVATAR; + else if (avatar_class=="hgavatar") m_hg_avatarClass = (int)AvatarType.HG_AVATAR; + else if (avatar_class=="foreignavatar") m_hg_avatarClass = (int)AvatarType.FOREIGN_AVATAR; + else m_hg_avatarClass = (int)AvatarType.UNKNOWN_AVATAR; + + } + catch { + m_log.ErrorFormat("[MONEY]: Initialise: Faile to read configuration file"); + } + } + + + public void AddRegion(Scene scene) + { + //m_log.InfoFormat("[MONEY]: AddRegion:"); + + if (scene==null) return; + + scene.RegisterModuleInterface(this); // 競合するモジュールの排除 + + 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); // added + HttpServer.AddXmlRPCHandler("AddBankerMoney", AddBankerMoneyHandler); // added + HttpServer.AddXmlRPCHandler("SendMoney", SendMoneyHandler); // added + HttpServer.AddXmlRPCHandler("MoveMoney", MoveMoneyHandler); // added + + MainServer.Instance.AddXmlRPCHandler("OnMoneyTransfered", OnMoneyTransferedHandler); + MainServer.Instance.AddXmlRPCHandler("UpdateBalance", BalanceUpdateHandler); + MainServer.Instance.AddXmlRPCHandler("UserAlert", UserAlertHandler); + MainServer.Instance.AddXmlRPCHandler("GetBalance", GetBalanceHandler); // added + MainServer.Instance.AddXmlRPCHandler("AddBankerMoney", AddBankerMoneyHandler); // added + MainServer.Instance.AddXmlRPCHandler("SendMoney", SendMoneyHandler); // added + MainServer.Instance.AddXmlRPCHandler("MoveMoney", MoveMoneyHandler); // added + } + } + + 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; + } + + + 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; + } + } + + + public void RegionLoaded(Scene scene) + { + //m_log.InfoFormat("[MONEY]: RegionLoaded:"); + } + + + public Type ReplaceableInterface + { + //get { return typeof(IMoneyModule); } + get { return null; } + } + + + public bool IsSharedModule + { + get { return true; } + } + + + public string Name + { + get { return "DTLNSLMoneyModule"; } + } + + + public void PostInitialise() + { + //m_log.InfoFormat("[MONEY]: PostInitialise:"); + } + + + public void Close() + { + //m_log.InfoFormat("[MONEY]: Close:"); + } + + #endregion + + + #region IMoneyModule interface. + + // for LSL llGiveMoney() function + public bool ObjectGiveMoney(UUID objectID, UUID fromID, UUID toID, int amount, UUID txn, out string result) + { + //m_log.InfoFormat("[MONEY]: ObjectGiveMoney: LSL ObjectGiveMoney. UUID = {0}", objectID.ToString()); + + 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"; + return ret; + } + + + // + public int UploadCharge + { + get { return PriceUpload; } + } + + + // + public int GroupCreationCharge + { + get { return PriceGroupCreate; } + } + + + public int GetBalance(UUID agentID) + { + IClientAPI client = GetLocateClient(agentID); + return QueryBalanceFromMoneyServer(client); + } + + + 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; + } + return false; + } + + + 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; + } + return false; + } + + + 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); + } + + + public void ApplyCharge(UUID agentID, int amount, MoneyTransactionType type) + { + ApplyCharge(agentID, amount, type, string.Empty); + } + + + 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); + } + + + 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); + } + + + 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 + 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); + } + + // for 0.9.1 over + 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); + return ret; + } + + #endregion + + + #region MoneyModule event handlers + + // + private void OnNewClient(IClientAPI client) + { + m_log.InfoFormat("[MONEY]: OnNewClient"); + + client.OnEconomyDataRequest += OnEconomyDataRequest; + client.OnLogout += ClientClosed; + + client.OnMoneyBalanceRequest += OnMoneyBalanceRequest; + client.OnRequestPayPrice += OnRequestPayPrice; + client.OnObjectBuy += OnObjectBuy; + } + + + public void OnMakeRootAgent(ScenePresence agent) + { + m_log.InfoFormat("[MONEY]: OnMakeRootAgent:"); + + 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); + + //client.OnMoneyBalanceRequest += OnMoneyBalanceRequest; + //client.OnRequestPayPrice += OnRequestPayPrice; + //client.OnObjectBuy += OnObjectBuy; + } + + + // for OnClientClosed event + private void ClientClosed(IClientAPI client) + { + //m_log.InfoFormat("[MONEY]: ClientClosed:"); + + if (m_enable_server && client!=null) { + LogoffMoneyServer(client); + } + } + + + // for OnMakeChildAgent event + private void MakeChildAgent(ScenePresence avatar) + { + //m_log.InfoFormat("[MONEY]: MakeChildAgent:"); + } + + + // for OnMoneyTransfer event + private void MoneyTransferAction(Object sender, EventManager.MoneyTransferArgs moneyEvent) + { + //m_log.InfoFormat("[MONEY]: MoneyTransferAction: type = {0}", moneyEvent.transactiontype); + + 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 + private void ValidateLandBuy(Object sender, EventManager.LandBuyArgs landBuyEvent) + { + //m_log.InfoFormat("[MONEY]: ValidateLandBuy:"); + + 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 + private void processLandBuy(Object sender, EventManager.LandBuyArgs landBuyEvent) + { + //m_log.InfoFormat("[MONEY]: processLandBuy:"); + + 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 + public void OnObjectBuy(IClientAPI remoteClient, UUID agentID, UUID sessionID, + UUID groupID, UUID categoryID, uint localID, byte saleType, int salePrice) + { + m_log.InfoFormat("[MONEY]: 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(); + 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); + 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]: 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"); + } + } + + + private void OnRequestPayPrice(IClientAPI client, UUID objectID) + { + m_log.InfoFormat("[MONEY]: 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) + private void OnEconomyDataRequest(IClientAPI user) + { + //m_log.InfoFormat("[MONEY]: OnEconomyDataRequest:"); + //IClientAPI user = GetLocateClient(agentId); + + 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); + } + } + } + + #endregion + + + #region MoneyModule XML-RPC Handler + + // "OnMoneyTransfered" RPC from MoneyServer + public XmlRpcResponse OnMoneyTransferedHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + m_log.InfoFormat("[MONEY]: 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]: 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]: OnMoneyTransferedHandler: Transaction is failed. MoneyServer will rollback"); + } + resp.Value = paramTable; + + return resp; + } + + + // "UpdateBalance" RPC from MoneyServer or Script + public XmlRpcResponse BalanceUpdateHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: BalanceUpdateHandler:"); + + bool ret = false; + + #region Update the balance from money server. + + 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; + } + } + } + } + } + + #endregion + + // Send the response to money server. + XmlRpcResponse resp = new XmlRpcResponse(); + Hashtable paramTable = new Hashtable(); + paramTable["success"] = ret; + + if (!ret) { + m_log.ErrorFormat("[MONEY]: BalanceUpdateHandler: Cannot update client balance from MoneyServer"); + } + resp.Value = paramTable; + + return resp; + } + + + // "UserAlert" RPC from Script + public XmlRpcResponse UserAlertHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: UserAlertHandler:"); + + bool ret = false; + + #region confirm the request and show the notice from money server. + + 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; + } + } + } + } + } + // + #endregion + + // 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 + public XmlRpcResponse GetBalanceHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: GetBalanceHandler:"); + + 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]: 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 + public XmlRpcResponse AddBankerMoneyHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: 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]: 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 + public XmlRpcResponse SendMoneyHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: SendMoneyHandler:"); + + 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]: SendMoneyHandler: SecretCode: {0} + {1} = {2}", secretCode, scriptIP, hash); + ret = SendMoneyTo(agentUUID, amount, type, hash); + } + } + else { + m_log.ErrorFormat("[MONEY]: SendMoneyHandler: amount is missed"); + } + } + else { + if (!requestParam.Contains("agentUUID")) { + m_log.ErrorFormat("[MONEY]: SendMoneyHandler: agentUUID is missed"); + } + if (!requestParam.Contains("secretAccessCode")) { + m_log.ErrorFormat("[MONEY]: SendMoneyHandler: secretAccessCode is missed"); + } + } + } + else { + m_log.ErrorFormat("[MONEY]: SendMoneyHandler: Params count is under 0"); + } + + if (!ret) m_log.ErrorFormat("[MONEY]: 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 + public XmlRpcResponse MoveMoneyHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: MoveMoneyHandler:"); + + 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]: MoveMoneyHandler: SecretCode: {0} + {1} = {2}", secretCode, scriptIP, hash); + ret = MoveMoneyFromTo(fromUUID, toUUID, amount, hash); + } + else { + m_log.ErrorFormat("[MONEY]: MoveMoneyHandler: amount is missed"); + } + } + else { + if (!requestParam.Contains("fromUUID") && !requestParam.Contains("toUUID")) { + m_log.ErrorFormat("[MONEY]: MoveMoneyHandler: fromUUID and toUUID are missed"); + } + if (!requestParam.Contains("secretAccessCode")) { + m_log.ErrorFormat("[MONEY]: MoveMoneyHandler: secretAccessCode is missed"); + } + } + } + else { + m_log.ErrorFormat("[MONEY]: MoveMoneyHandler: Params count is under 0"); + } + + if (!ret) m_log.ErrorFormat("[MONEY]: 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; + } + + #endregion + + + #region MoneyModule private help functions + + /// + /// 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) + { + //m_log.InfoFormat("[MONEY]: TransferMoney:"); + + bool ret = false; + IClientAPI senderClient = GetLocateClient(sender); + + // Handle the illegal transaction. + // receiverClient could be null. + if (senderClient==null) { + m_log.InfoFormat("[MONEY]: TransferMoney: Client {0} not found", sender.ToString()); + return false; + } + + if (QueryBalanceFromMoneyServer(senderClient) + /// 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) + { + //m_log.InfoFormat("[MONEY]: ForceTransferMoney:"); + + bool ret = false; + + #region Force send transaction request to money server and parse the resultes. + + 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]: ForceTransferMoney: Can not money force transfer request from [{0}] to [{1}]", sender.ToString(), receiver.ToString()); + } + //else m_log.ErrorFormat("[MONEY]: ForceTransferMoney: Money Server is not available!!"); + + #endregion + + 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) + { + //m_log.InfoFormat("[MONEY]: SendMoneyTo:"); + + 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]: SendMoneyTo: Fail Message is {0}", resultTable["message"]); + } + else m_log.ErrorFormat("[MONEY]: SendMoneyTo: Money Server is not responce"); + } + //else m_log.ErrorFormat("[MONEY]: SendMoneyTo: Money Server is not available!!"); + + 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) + { + //m_log.InfoFormat("[MONEY]: MoveMoneyFromTo:"); + + 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]: MoveMoneyFromTo: Fail Message is {0}", resultTable["message"]); + } + else m_log.ErrorFormat("[MONEY]: MoveMoneyFromTo: Money Server is not responce"); + } + //else m_log.ErrorFormat("[MONEY]: MoveMoneyFromTo: Money Server is not available!!"); + + 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) + { + //m_log.InfoFormat("[MONEY]: AddBankerMoney:"); + + 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]: AddBankerMoney: Avatar is not Banker. Web Settlemrnt is used."); + } + else m_log.ErrorFormat("[MONEY]: AddBankerMoney: Fail Message {0}", resultTable["message"]); + } + } + else m_log.ErrorFormat("[MONEY]: AddBankerMoney: Money Server is not responce"); + } + //else m_log.ErrorFormat("[MONEY]: AddBankerMoney: Money Server is not available!!"); + + 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) + { + //m_log.InfoFormat("[MONEY]: PayMoneyCharge:"); + + bool ret = false; + IClientAPI senderClient = GetLocateClient(sender); + + // Handle the illegal transaction. + // receiverClient could be null. + if (senderClient==null) { + m_log.InfoFormat("[MONEY]: PayMoneyCharge: Client {0} is not found", sender.ToString()); + return false; + } + + if (QueryBalanceFromMoneyServer(senderClient) + /// 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) + { + //m_log.InfoFormat("[MONEY]: LoginMoneyServer:"); + + balance = 0; + bool ret = false; + bool isNpc = avatar.IsNPC; + + IClientAPI client = avatar.ControllingClient; + + #region Send money server the client info for login. + + 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; + } + } + } + + ////////////////////////////////////////////////////////////// + // 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; + } + + // + avatarClass = avatarType; + if (avatarType==(int)AvatarType.NPC_AVATAR) return false; + if (avatarType==(int)AvatarType.HG_AVATAR) avatarClass = m_hg_avatarClass; + + // + // Lognn 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]: LoginMoneyServer: Client [{0}] login Money Server {1}", client.AgentId.ToString(), m_moneyServURL); + ret = true; + } + } + else m_log.ErrorFormat("[MONEY]: LoginMoneyServer: Unable to login Money Server {0} for client [{1}]", m_moneyServURL, client.AgentId.ToString()); + } + else m_log.ErrorFormat("[MONEY]: LoginMoneyServer: Money Server is not available!!"); + + #endregion + + // Viewerへ設定を通知する. + 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) + { + //m_log.InfoFormat("[MONEY]: LogoffMoneyServer:"); + + 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; + } + } + } + + return ret; + } + + + // + private EventManager.MoneyTransferArgs GetTransactionInfo(IClientAPI client, string transactionID) + { + //m_log.InfoFormat("[MONEY]: GetTransactionInfo:"); + + 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]: GetTransactionInfo: GetTransactionInfo: Fail to Request. {0}", (string)resultTable["description"]); + } + } + else { + m_log.ErrorFormat("[MONEY]: GetTransactionInfo: Invalid Response"); + } + } + else { + m_log.ErrorFormat("[MONEY]: 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) + { + //m_log.InfoFormat("[MONEY]: genericCurrencyXMLRPCRequest:"); + + if (reqParams.Count<=0 || string.IsNullOrEmpty(method)) return null; + + if (m_checkServerCert) { + if (!m_moneyServURL.StartsWith("https://")) { + m_log.InfoFormat("[MONEY]: 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]: 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); + } + catch (Exception ex) { + m_log.ErrorFormat("[MONEY]: genericCurrencyXMLRPCRequest: Unable to connect to Money Server {0}", m_moneyServURL); + m_log.ErrorFormat("[MONEY]: 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; + } + } + } + } + } + + return client; + } + + + 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; + } + } + } + } + + return scene; + } + + + 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; + } + + #endregion + } + +} diff --git a/release/0.9.1/src/OpenSim.Modules.Currency/NSLCertificateTools.cs b/release/0.9.1/src/OpenSim.Modules.Currency/NSLCertificateTools.cs new file mode 100644 index 0000000..db0008c --- /dev/null +++ b/release/0.9.1/src/OpenSim.Modules.Currency/NSLCertificateTools.cs @@ -0,0 +1,226 @@ +/* + * Copyright (c) Contributors, http://www.nsl.tuis.ac.jp + * + */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using System.Net; +using System.Net.Security; +using System.Text; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; + +using log4net; + + +namespace NSL.Certificate.Tools +{ + // + 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 Mono.Security.X509.X509Crl m_clientcrl = null; + + + public NSLCertificateVerify() + { + m_chain = null; + m_cacert = null; + m_clientcrl = null; + } + + + public NSLCertificateVerify(string certfile) + { + SetPrivateCA(certfile); + } + + + public NSLCertificateVerify(string certfile, string crlfile) + { + SetPrivateCA (certfile); + SetPrivateCRL(crlfile); + } + + + 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; + } + } + + + 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); + } + } + + + // + // + // + 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; iNp_U@6-&1B#2HItc@jxS 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

DzLxVYa}nxW>LN}T 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 L;s2bcp9B97j=nnI literal 0 HcmV?d00001 diff --git a/release/0.9.1_1/bin/OpenSim.Data.MySQL.MySQLMoneyDataWrapper.dll b/release/0.9.1_1/bin/OpenSim.Data.MySQL.MySQLMoneyDataWrapper.dll new file mode 100644 index 0000000000000000000000000000000000000000..b76a314fa44695c12638cef8e887fb222b609f02 GIT binary patch literal 38912 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 tableList = new Dictionary(); + tableList = CheckTables(); + + // + // Balances Table + 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; + } + } + + // + // UserInfo Table + 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; + } + } + + // + // Transactions Table + if (!tableList.ContainsKey(Table_of_Transactions)) { + try { + CreateTransactionsTable(); + } + catch (Exception e) { + throw new Exception("[MONEY MANAGER]: Error creating transactions table: " + e.ToString()); + } + } + // check transactions table version + 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; + } + } + + // + // TotalSales Table + 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; + } + } + } + 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 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; + } + + + + /////////////////////////////////////////////////////////////////////// + // create Tables + + 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(); + } + + + 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(); + } + + + 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(); + } + + + 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(); + } + + + + /////////////////////////////////////////////////////////////////////// + // update Balances Table + + 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=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 uuid, url, name, pass + bool updatedb = true; + for (int i=0; i + /// 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() + { + //m_log.Info("[MONEY MANAGER]: Converting Transaction Table..."); + 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 + /// 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() + { + //m_log.Info("[MONEY MANAGER]: Converting Transaction Table..."); + 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 + /// 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 + + 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(); + } + + + 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(); + } + + + /////////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////////// + // + + 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; + } + } + + + /// + /// Reconnect to the database + /// + 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()); + } + } + } + + + + /////////////////////////////////////////////////////////////////////// + // + // balances + // + + /// + /// 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) { + m_log.ErrorFormat("[MoneyDB]: MySql failed to fetch balance {0}.", userID); + retValue = -2; + } + dbReader.Close(); + } + cmd.Dispose(); + + return retValue; + } + + + 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; + } + + + 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; + } + + + + /////////////////////////////////////////////////////////////////////// + // + // totalsales + // + 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 0) bRet = true; + + cmd.Dispose(); + return bRet; + } + + + 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; + + } + + + 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 0) bRet = true; + + cmd.Dispose(); + return bRet; + } + + + 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; + } + + + 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; + } + + + 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; + } + + + 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(); + } + + + 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 + // + public bool addUserInfo(UserInfo userInfo) + { + //m_log.Error("[MONEY MANAGER]: Adding UserInfo: " + userInfo.UserID); + + 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; + } + + + public UserInfo fetchUserInfo(string userID) + { + //m_log.Error("[MONEY MANAGER]: Fetching UserInfo: " + 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; + } + + + public bool updateUserInfo(UserInfo userInfo) + { + //m_log.Error("[MONEY MANAGER]: Updating UserInfo: " + userInfo.UserID); + + 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; + } + + } +} diff --git a/release/0.9.1_1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs b/release/0.9.1_1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs new file mode 100644 index 0000000..cd2bae4 --- /dev/null +++ b/release/0.9.1_1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs @@ -0,0 +1,52 @@ +/* + * 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 OpenSimulator 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. + */ +using System; +using System.Threading; + +namespace OpenSim.Data.MySQL.MySQLMoneyDataWrapper +{ + // This bit of code is from OpenSim.Data.MySQLSuperManager + public class MySQLSuperManager + { + public bool Locked; + private readonly Mutex m_lock = new Mutex(false); + public MySQLMoneyManager Manager; + public string Running; + + public void GetLock() + { + Locked = true; + m_lock.WaitOne(); + } + + public void Release() + { + m_lock.ReleaseMutex(); + Locked = false; + } + } +} diff --git a/release/0.9.1_1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs b/release/0.9.1_1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs new file mode 100644 index 0000000..e73b579 --- /dev/null +++ b/release/0.9.1_1/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs @@ -0,0 +1,237 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +using System.Text; +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_objectID = "00000000-0000-0000-0000-000000000000"; + 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; + +/* + public TransactionData(string uuid, string sender, string receiver, + int amount, int time, int status, string description) + { + this.m_uuid = uuid; + this.m_sender = sender; + this.m_receiver = receiver; + this.m_amount = amount; + } +*/ + + public UUID TransUUID + { + get { return m_uuid; } + set { m_uuid = value; } + } + + public string Sender + { + get { return m_sender; } + set { m_sender = value; } + } + + public string Receiver + { + get { return m_receiver; } + set { m_receiver = value; } + } + + public int Amount + { + get { return m_amount; } + set { m_amount = value; } + } + + public int SenderBalance + { + get { return m_senderBalance; } + set { m_senderBalance = value; } + } + + public int ReceiverBalance + { + get { return m_receiverBalance; } + set { m_receiverBalance = value; } + } + + public int Type + { + get { return m_type; } + set { m_type = value; } + } + + public int Time + { + get { return m_time; } + set { m_time = value; } + } + + public int Status + { + get { return m_status; } + set { m_status = value; } + } + + public string Description + { + get { return m_description; } + set { m_description = value; } + } + + public string ObjectUUID + { + get { return m_objectID; } + set { m_objectID = value; } + } + + public string ObjectName + { + get { return m_objectName; } + set { m_objectName = value; } + } + + public string RegionHandle + { + get { return m_regionHandle; } + set { m_regionHandle = value; } + } + + public string RegionUUID + { + get { return m_regionUUID; } + set { m_regionUUID = value; } + } + + public string SecureCode + { + get { return m_secureCode; } + set { m_secureCode = value; } + } + + 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; + + public string UserID + { + get { return m_userID; } + set { m_userID = value; } + } + + public string SimIP + { + get { return m_simIP; } + set { m_simIP = value; } + } + + public string Avatar + { + get { return m_avatarName; } + set { m_avatarName = value; } + } + + public string PswHash + { + get { return m_passwordHash; } + set { m_passwordHash = value; } + } + + public int Type + { + get { return m_avatarType; } + set { m_avatarType = value; } + } + + public int Class + { + get { return m_avatarClass; } + set { m_avatarClass = value; } + } + + public string ServerURL + { + get { return m_serverURL; } + set { m_serverURL = value; } + } + } +} diff --git a/release/0.9.1_1/src/OpenSim.Grid.MoneyServer/IMoneyDBService.cs b/release/0.9.1_1/src/OpenSim.Grid.MoneyServer/IMoneyDBService.cs new file mode 100644 index 0000000..7140c0f --- /dev/null +++ b/release/0.9.1_1/src/OpenSim.Grid.MoneyServer/IMoneyDBService.cs @@ -0,0 +1,70 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +//using System.Linq; +using System.Text; +using OpenMetaverse; +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; + + +namespace OpenSim.Grid.MoneyServer +{ + public interface IMoneyDBService + { + int getBalance(string userID); + + bool withdrawMoney(UUID transactionID, string senderID, int amount); + + bool giveMoney(UUID transactionID, string receiverID, int amount); + + bool addTransaction(TransactionData transaction); + + bool addUser(string userID, int balance, int status, int type); + + bool updateTransactionStatus(UUID transactionID, int status, string description); + + bool SetTransExpired(int deadTime); + + bool ValidateTransfer(string secureCode, UUID transactionID); + + TransactionData FetchTransaction(UUID transactionID); + + TransactionData FetchTransaction(string userID, int startTime, int endTime, int lastIndex); + + int getTransactionNum(string userID, int startTime, int endTime); + + bool DoTransfer(UUID transactionUUID); + + bool DoAddMoney(UUID transactionUUID); // Added by Fumi.Iseki + + bool TryAddUserInfo(UserInfo user); + + UserInfo FetchUserInfo(string userID); + } +} diff --git a/release/0.9.1_1/src/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs b/release/0.9.1_1/src/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs new file mode 100644 index 0000000..d6e3721 --- /dev/null +++ b/release/0.9.1_1/src/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs @@ -0,0 +1,52 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +//using System.Linq; +using System.Text; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; + +using Nini.Config; + + +namespace OpenSim.Grid.MoneyServer +{ + public interface IMoneyServiceCore + { + BaseHttpServer GetHttpServer(); + Dictionary GetSessionDic(); + Dictionary GetSecureSessionDic(); + Dictionary GetWebSessionDic(); + + // + IConfig GetServerConfig(); + IConfig GetCertConfig(); + bool IsCheckClientCert(); + } +} diff --git a/release/0.9.1_1/src/OpenSim.Grid.MoneyServer/MoneyDBService.cs b/release/0.9.1_1/src/OpenSim.Grid.MoneyServer/MoneyDBService.cs new file mode 100644 index 0000000..efa72b4 --- /dev/null +++ b/release/0.9.1_1/src/OpenSim.Grid.MoneyServer/MoneyDBService.cs @@ -0,0 +1,570 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; +using OpenSim.Modules.Currency; +using log4net; +using System.Reflection; +using OpenMetaverse; + + +namespace OpenSim.Grid.MoneyServer +{ + class MoneyDBService: IMoneyDBService + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private string m_connect; + //private MySQLMoneyManager m_moneyManager; + private long TicksToEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; + + // DB manager pool + protected Dictionary m_dbconnections = new Dictionary(); // Lock付 + private int m_maxConnections; + + public int m_lastConnect = 0; + + + public MoneyDBService(string connect) + { + m_connect = connect; + Initialise(m_connect,10); + } + + + public MoneyDBService() + { + } + + + public void Initialise(string connectionString, int maxDBConnections) + { + m_connect = connectionString; + m_maxConnections = maxDBConnections; + if (connectionString != string.Empty) { + //m_moneyManager = new MySQLMoneyManager(connectionString); + + //m_log.Info("Creating " + m_maxConnections + " DB connections..."); + for (int i=0; im_maxConnections) { + lockedCons = 0; + System.Threading.Thread.Sleep(1000); // Wait some time before searching them again. + m_log.Debug("WARNING: All threads are in use. Probable cause: Something didnt release a mutex properly, or high volume of requests inbound."); + } + } + } + + + public int getBalance(string userID) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.getBalance(userID); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + dbm.Manager.Reconnect(); + return dbm.Manager.getBalance(userID); + } + catch(Exception e) { + m_log.Error(e.ToString()); + return 0; + } + finally { + dbm.Release(); + } + } + + + 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) { + dbm.Manager.Reconnect(); + return dbm.Manager.withdrawMoney(transactionID, senderID, amount); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool giveMoney(UUID transactionID, string receiverID, int amount) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.giveMoney(transactionID, receiverID, amount); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + dbm.Manager.Reconnect(); + return dbm.Manager.giveMoney(transactionID, receiverID, amount); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool setTotalSale(TransactionData transaction) + { + if (transaction.Receiver==transaction.Sender) return false; + if (transaction.Sender==UUID.Zero.ToString()) return false; + + MySQLSuperManager dbm = GetLockedConnection(); + + 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) { + dbm.Manager.Reconnect(); + return dbm.Manager.setTotalSale(transaction.Receiver, transaction.ObjectUUID, transaction.Type, 1, transaction.Amount, time); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool addTransaction(TransactionData transaction) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.addTransaction(transaction); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + dbm.Manager.Reconnect(); + return dbm.Manager.addTransaction(transaction); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool addUser(string userID, int balance, int status, int type) + { + 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) return false; + + // + MySQLSuperManager dbm = GetLockedConnection(); + + try { + ret = dbm.Manager.addUser(userID, 0, status, type); // make Balance Table + } + catch (MySql.Data.MySqlClient.MySqlException e) { + dbm.Manager.Reconnect(); + ret = dbm.Manager.addUser(userID, 0, status, type); // make Balance Table + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + + // + if (ret) ret = giveMoney(transaction.TransUUID, userID, balance); + return ret; + } + + + 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) { + dbm.Manager.Reconnect(); + return dbm.Manager.updateTransactionStatus(transactionID, status, description); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool SetTransExpired(int deadTime) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.SetTransExpired(deadTime); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + dbm.Manager.Reconnect(); + return dbm.Manager.SetTransExpired(deadTime); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool ValidateTransfer(string secureCode, UUID transactionID) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.ValidateTransfer(secureCode, transactionID); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + dbm.Manager.Reconnect(); + return dbm.Manager.ValidateTransfer(secureCode, transactionID); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public TransactionData FetchTransaction(UUID transactionID) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.FetchTransaction(transactionID); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + dbm.Manager.Reconnect(); + return dbm.Manager.FetchTransaction(transactionID); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return null; + } + finally { + dbm.Release(); + } + } + + + 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) { + dbm.Manager.Reconnect(); + arrTransaction = dbm.Manager.FetchTransaction(userID, startTime, endTime, index, 1); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return null; + } + finally { + dbm.Release(); + } + + // + if (arrTransaction.Length > 0) { + return arrTransaction[0]; + } + else { + return null; + } + } + + + public bool DoTransfer(UUID transactionUUID) + { + 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 DB. {0}", transaction.Receiver); + return false; + } + + if (giveMoney(transactionUUID, transaction.Receiver, transaction.Amount)) { + do_trans = true; + } + else { // give money to receiver failed. 返金処理 + 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); + } + + return do_trans; + } + + + // by Fumi.Iseki + public bool DoAddMoney(UUID transactionUUID) + { + 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 DB. {0}", transaction.Receiver); + return false; + } + // + if (giveMoney(transactionUUID, transaction.Receiver, transaction.Amount)) { + setTotalSale(transaction); + 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()); + } + + return false; + } + + + /////////////////////////////////////////////////////////////////////////////////////////////////////// + // + // userinfo + // + + public bool TryAddUserInfo(UserInfo user) + { + MySQLSuperManager dbm = GetLockedConnection(); + + UserInfo userInfo = null; + + try { + userInfo = dbm.Manager.fetchUserInfo(user.UserID); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + dbm.Manager.Reconnect(); + userInfo = dbm.Manager.fetchUserInfo(user.UserID); + } + catch (Exception e) { + m_log.Error(e.ToString()); + 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(); + } + } + + + public UserInfo FetchUserInfo(string userID) + { + UserInfo userInfo = null; + MySQLSuperManager dbm = GetLockedConnection(); + + try { + userInfo = dbm.Manager.fetchUserInfo(userID); + return userInfo; + } + catch (MySql.Data.MySqlClient.MySqlException e) { + dbm.Manager.Reconnect(); + userInfo = dbm.Manager.fetchUserInfo(userID); + return userInfo; + } + catch (Exception e) { + m_log.Error(e.ToString()); + return null; + } + finally { + dbm.Release(); + } + } + + + 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) { + dbm.Manager.Reconnect(); + return dbm.Manager.getTransactionNum(userID,startTime,endTime); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return -1; + } + finally { + dbm.Release(); + } + } + } +} diff --git a/release/0.9.1_1/src/OpenSim.Grid.MoneyServer/MoneyServerBase.cs b/release/0.9.1_1/src/OpenSim.Grid.MoneyServer/MoneyServerBase.cs new file mode 100644 index 0000000..9023c6e --- /dev/null +++ b/release/0.9.1_1/src/OpenSim.Grid.MoneyServer/MoneyServerBase.cs @@ -0,0 +1,339 @@ +/* + * 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. + */ + +using System; +using System.IO; +using System.Collections.Generic; +using System.Text; +using System.Net; +using System.Net.Security; +using System.Reflection; +using System.Timers; +//using System.Security.Authentication; +//using System.Security.Cryptography; +//using System.Security.Cryptography.X509Certificates; + +using HttpServer; +using Nini.Config; +using log4net; + +using OpenSim.Framework; +using OpenSim.Framework.Console; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Data; + +using NSL.Certificate.Tools; + + + +namespace OpenSim.Grid.MoneyServer +{ + class MoneyServerBase : BaseOpenSimServer, IMoneyServiceCore + { + 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 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; + + private MoneyXmlRpcModule m_moneyXmlRpcModule; + private MoneyDBService m_moneyDBService; + + private NSLCertificateVerify m_certVerify = new NSLCertificateVerify(); // クライアント認証用 + + 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() + { + m_console = new LocalConsole("Money "); +// m_console = new CommandConsole("Money "); + MainConsole.Instance = m_console; + } + + + public void Work() + { + //m_console.Notice("Enter help for a list of commands\n"); + + //The timer checks the transactions table every 60 seconds + Timer checkTimer = new Timer(); + checkTimer.Interval = 60*1000; + checkTimer.Enabled = true; + checkTimer.Elapsed += new ElapsedEventHandler(CheckTransaction); + checkTimer.Start(); + + while (true) { + m_console.Prompt(); + } + } + + + /// + /// Check the transactions table, set expired transaction state to failed + /// + private void CheckTransaction(object sender, ElapsedEventArgs e) + { + 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); + } + + + 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); + if (m_checkClientCert) { + Type typeBaseHttpServer = typeof(BaseHttpServer); // BaseHttpServer.cs にパッチがあたっていない場合のため + PropertyInfo pinfo = typeBaseHttpServer.GetProperty("CertificateValidationCallback"); + + if (pinfo!=null) { + //m_httpServer.CertificateValidationCallback = (RemoteCertificateValidationCallback)m_certVerify.ValidateClientCertificate; + pinfo.SetValue(m_httpServer, (RemoteCertificateValidationCallback)m_certVerify.ValidateClientCertificate, null); + m_log.Info ("[MONEY SERVER]: Set RemoteCertificateValidationCallback"); + } + else { + m_log.Error("[MONEY SERVER]: StartupSpecific: CheckClientCert is true. But this MoneyServer does not support CheckClientCert!!"); + } + } + } + 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); + } + + //TODO : Add some console commands here + } + + + protected void ReadIniConfig() + { + MoneyServerConfigSource moneyConfig = new MoneyServerConfigSource(); + Config = moneyConfig.m_config; // for base.StartupSpecific() + + 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); + + // + // [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. Cert file is " + m_certFilename); + } + + // クライアント認証 + 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); + } + } + } + + catch (Exception) { + m_log.Error("[MONEY SERVER]: ReadIniConfig: Fail to setup configure. Please check MoneyServer.ini. Exit"); + Environment.Exit(1); + } + } + + + // 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); + + m_moneyXmlRpcModule = new MoneyXmlRpcModule(); +// m_moneyXmlRpcModule.Initialise(m_version, m_server_config, m_cert_config, m_moneyDBService, this); + m_moneyXmlRpcModule.Initialise(m_version, m_moneyDBService, this); + 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); + } + else { + //TODO: create default configuration. + //m_config = DefaultConfig(); + } + } + + + public void Save(string path) + { + m_config.Save(path); + } + + } +} diff --git a/release/0.9.1_1/src/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs b/release/0.9.1_1/src/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs new file mode 100644 index 0000000..675179c --- /dev/null +++ b/release/0.9.1_1/src/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs @@ -0,0 +1,1758 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Reflection; +using System.Collections; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +using log4net; +using Nini.Config; +using Nwc.XmlRpc; + +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; +using OpenSim.Modules.Currency; +using OpenSim.Region.Framework.Scenes; + +using NSL.Network.XmlRpc; +using NSL.Certificate.Tools; + + +namespace OpenSim.Grid.MoneyServer +{ + class MoneyXmlRpcModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private int m_defaultBalance = 1000; + // + private bool m_forceTransfer = false; + private string m_bankerAvatar = ""; + + private bool m_scriptSendMoney = false; + private string m_scriptAccessKey = ""; + private string m_scriptIPaddress = "127.0.0.1"; + + 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 bool m_checkServerCert = false; + private string m_cacertFilename = ""; + + private string m_certFilename = ""; + private string m_certPassword = ""; + private X509Certificate2 m_clientCert = null; + + private string m_sslCommonName = ""; + + private NSLCertificateVerify m_certVerify = new NSLCertificateVerify(); // サーバ認証用 + + + // Update Balance Messages + 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; + 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; + + + public MoneyXmlRpcModule() + { + } + + + public void Initialise(string opensimVersion, IMoneyDBService moneyDBService, IMoneyServiceCore moneyCore) + { + //m_opensimVersion = opensimVersion; + m_moneyDBService = moneyDBService; + m_moneyCore = moneyCore; + m_server_config = m_moneyCore.GetServerConfig(); // [MoneyServer] Section + m_cert_config = m_moneyCore.GetCertConfig(); // [Certificate] Section + + //////////////////////////////////////////////////////////////////////// + // [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); + + // 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) + // クライアント証明書 + m_certFilename = m_cert_config.GetString("ClientCertFilename", m_certFilename); + m_certPassword = m_cert_config.GetString("ClientCertPassword", m_certPassword); + if (m_certFilename!="") { + m_clientCert = new X509Certificate2(m_certFilename, m_certPassword); + //m_clientCert = new X509Certificate2(m_certFilename, m_certPassword, X509KeyStorageFlags.MachineKeySet); + m_log.Info("[MONEY RPC]: Initialise: Issue Authentication of Client. Cert file is " + m_cacertFilename); + } + + // サーバ認証 + m_checkServerCert = m_cert_config.GetBoolean("CheckServerCert", m_checkServerCert); + + // CA + m_cacertFilename = m_cert_config.GetString("CACertFilename", m_cacertFilename); + if (m_checkServerCert && m_cacertFilename!="") { + m_certVerify.SetPrivateCA(m_cacertFilename); + ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(m_certVerify.ValidateServerCertificate); + m_log.Info("[MONEY RPC]: Initialise: Execute Authentication of Server. CA file is " + m_cacertFilename); + } + else { + m_checkServerCert = false; + ServicePointManager.ServerCertificateValidationCallback = null; + } + + m_sessionDic = m_moneyCore.GetSessionDic(); + m_secureSessionDic = m_moneyCore.GetSecureSessionDic(); + m_webSessionDic = m_moneyCore.GetWebSessionDic(); + RegisterHandlers(); + } + + + public void PostInitialise() + { + } + + + 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); // added + m_httpServer.AddXmlRPCHandler("MoveMoney", handleScriptTransaction); // added + + // 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); + } + + + // + 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; + } + + + // + public string GetSSLCommonName() + { + return m_sslCommonName; + } + + + /// + /// Get the user balance when user entering a parcel. + /// + /// + /// + public XmlRpcResponse handleClientLogin(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleClientLogin:"); + + 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 RPC]: 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 RPC]: 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 RPC]: handleClientLogon: Avatar {0} ({1}) is logged on.", userName, clientUUID); + m_log.InfoFormat("[MONEY RPC]: handleClientLogon: Avatar Type is {0} and Avatar Class is {1}", avatarType, avatarClass); + + //if (String.IsNullOrEmpty(serverURL)) { + // responseData["description"] = "Server URL is empty. Avatar is a NPC?"; + // m_log.InfoFormat("[MONEY RPC]: handleClientLogon: {0}", responseData["description"]); + // return response; + //} + + // + // 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 RPC]: 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 RPC]: handleClientLogon: {0}", responseData["description"]); + return response; + } + else if (avatarClass==(int)AvatarType.FOREIGN_AVATAR) { + responseData["description"] = "Avatar is a Foreign avatar."; + m_log.InfoFormat("[MONEY RPC]: handleClientLogon: {0}", responseData["description"]); + return response; + } + else if (avatarClass==(int)AvatarType.UNKNOWN_AVATAR) { + responseData["description"] = "Avatar is a Unknown avatar."; + m_log.InfoFormat("[MONEY RPC]: 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 RPC]: 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 RPC]: 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 RPC]: 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 RPC]: 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) + { + //m_log.InfoFormat("[MONEY RPC]: handleClientLogout:"); + + 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 RPC]: 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 RPC]: handleClientLogout: Failed to delete user session: " + e.ToString() ); + responseData["success"] = false; + return response; + } + + responseData["success"] = true; + return response; + } + + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // + /// + /// handle incoming transaction + /// + /// + /// + public XmlRpcResponse handleTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: 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 RPC]: handleTransaction: Transfering money from {0} to {1}, Amount = {2}", senderID, receiverID, amount); + m_log.InfoFormat("[MONEY RPC]: 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 RPC]: 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 RPC]: 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 RPC]: handleTransaction: Add transaction for user {0} failed.", senderID); + } + return response; + } + catch (Exception e) { + m_log.Error("[MONEY RPC]: handleTransaction: Exception occurred while adding transaction: " + e.ToString()); + } + return response; + } + } + + m_log.Error("[MONEY RPC]: handleTransaction: Session authentication failure for sender " + senderID); + responseData["message"] = "Session check failure, please re-login later!"; + return response; + } + + + // + // added by Fumi.Iseki + // + /// + /// handle incoming force transaction. no check senderSessionID and senderSecureSessionID + /// + /// + /// + public XmlRpcResponse handleForceTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleForceTransaction:"); + + 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 RPC]: handleForceTransaction: Not allowed force transfer of Money."); + m_log.Error("[MONEY RPC]: 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 RPC]: handleForceTransaction: Force transfering money from {0} to {1}, Amount = {2}", senderID, receiverID, amount); + m_log.InfoFormat("[MONEY RPC]: 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 RPC]: 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 RPC]: handleForceTransaction: Add force transaction for user {0} failed.", senderID); + } + return response; + } + catch (Exception e) { + m_log.Error("[MONEY RPC]: handleForceTransaction: Exception occurred while adding force transaction: " + e.ToString()); + } + return response; + } + + + // + // added by Fumi.Iseki + // + /// + /// handle scripted sending money transaction. + /// + /// + /// + public XmlRpcResponse handleScriptTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: 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 RPC]: handleScriptTransaction: Not allowed send money to avatar!!"); + m_log.Error("[MONEY RPC]: 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 RPC]: handleScriptTransaction: Not allowed send money to avatar!!"); + m_log.Error("[MONEY RPC]: handleScriptTransaction: Not match Script Access Key."); + responseData["message"] = "not allowed send money to avatar! not match Script Key"; + return response; + } + + m_log.InfoFormat("[MONEY RPC]: 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 RPC]: 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 RPC]: 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 RPC]: 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 RPC]: 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 RPC]: handleScriptTransaction: Add force transaction for user {0} failed.", transaction.Sender); + } + return response; + } + catch (Exception e) { + m_log.Error("[MONEY RPC]: handleScriptTransaction: Exception occurred while adding money transaction: " + e.ToString()); + } + return response; + } + + + // + // added by Fumi.Iseki + // + /// + /// handle adding money transaction. + /// + /// + /// + public XmlRpcResponse handleAddBankerMoney(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleAddBankerMoney:"); + + 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 RPC]: handleAddBankerMoney: Not allowed add money to avatar!!"); + m_log.Error("[MONEY RPC]: 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 RPC]: 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 RPC]: 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 RPC]: 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 RPC]: handleAddBankerMoney: Add force transaction for user {0} failed.", senderID); + } + return response; + } + catch (Exception e) { + m_log.Error("[MONEY RPC]: handleAddBankerMoney: Exception occurred while adding money transaction: " + e.ToString()); + } + return response; + } + + + // + // added by Fumi.Iseki + // + /// + /// handle pay charge transaction. no check receiver information. + /// + /// + /// + public XmlRpcResponse handlePayMoneyCharge(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handlePayMoneyCharge:"); + + 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(); + + 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 RPC]: handlePayMoneyCharge: Transfering money from {0} to {1}, Amount = {2}", senderID, receiverID, amount); + m_log.InfoFormat("[MONEY RPC]: handlePayMoneyCharge: 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 RPC]: handlePayMoneyCharge: Pay from {0}", senderID); + 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(); + + 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 message = string.Format(m_BalanceMessagePayCharge, amount, "SYSTEM", ""); + responseData["success"] = NotifyTransfer(transactionUUID, message, "", ""); + } + 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 RPC]: handlePayMoneyCharge: Pay money transaction for user {0} failed.", senderID); + } + return response; + } + catch (Exception e) { + m_log.Error("[MONEY RPC]: handlePayMoneyCharge: Exception occurred while pay money transaction: " + e.ToString()); + } + return response; + } + } + + m_log.Error("[MONEY RPC]: handlePayMoneyCharge: Session authentication failure for sender " + senderID); + responseData["message"] = "Session check failure, please re-login later!"; + return response; + } + + + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // + // added by Fumi.Iseki + // + /// + /// Continue transaction with no confirm. + /// + /// + /// + public bool NotifyTransfer(UUID transactionUUID, string msg2sender, string msg2receiver, string objectName) + { + //m_log.InfoFormat("[MONEY RPC]: 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 RPC]: NotifyTransfer: Transaction Type = {0}", transaction.Type); + m_log.InfoFormat("[MONEY RPC]: 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) return true; + 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 RPC]: 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 RPC]: NotifyTransfer: User {0} can't get the object, rolling back.", transaction.Sender); + if (RollBackTransaction(transaction)) { + m_log.ErrorFormat("[MONEY RPC]: NotifyTransfer: Transaction {0} failed but roll back succeeded.", transactionUUID.ToString()); + } + else { + m_log.ErrorFormat("[MONEY RPC]: NotifyTransfer: Transaction {0} failed and roll back failed as well.", + transactionUUID.ToString()); + } + } + else { + m_log.InfoFormat("[MONEY RPC]: NotifyTransfer: Transaction {0} finished successfully.", transactionUUID.ToString()); + return true; + } + } + } + return false; + } + return true; + } + } + m_log.ErrorFormat("[MONEY RPC]: NotifyTransfer: Transaction {0} failed.", transactionUUID.ToString()); + } + catch (Exception e) { + m_log.ErrorFormat("[MONEY RPC]: NotifyTransfer: exception occurred when transaction {0}: {1}", transactionUUID.ToString(), e.ToString()); + } + + return false; + } + + + + /// + /// Get the user balance. + /// + /// + /// + public XmlRpcResponse handleGetBalance(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleGetBalance:"); + + 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 RPC]: 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 RPC]: handleGetBalance: Can't get balance for user {0}, Exception {1}", clientUUID, e.ToString()); + } + return response; + } + } + + m_log.Error("[MONEY RPC]: handleGetBalance: Session authentication failed when getting balance for user " + clientUUID); + responseData["description"] = "Session check failure, please re-login"; + return response; + } + + + /// + /// 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, string uri) + { + //m_log.InfoFormat("[MONEY RPC]: genericCurrencyXMLRPCRequest: to {0}", uri); + + if (reqParams.Count<=0 || string.IsNullOrEmpty(method)) return null; + + if (m_checkServerCert) { + if (!uri.StartsWith("https://")) { + m_log.InfoFormat("[MONEY RPC]: 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 RPC]: genericCurrencyXMLRPCRequest: Invalid Region Server URL: {0}", uri); + return null; + } + } + + ArrayList arrayParams = new ArrayList(); + arrayParams.Add(reqParams); + XmlRpcResponse moneyServResp = null; + try { + //XmlRpcRequest moneyModuleReq = new XmlRpcRequest(method, arrayParams); + //moneyServResp = moneyModuleReq.Send(uri, MONEYMODULE_REQUEST_TIMEOUT); + NSLXmlRpcRequest moneyModuleReq = new NSLXmlRpcRequest(method, arrayParams); + moneyServResp = moneyModuleReq.certSend(uri, m_clientCert, m_checkServerCert, MONEYMODULE_REQUEST_TIMEOUT); + } + catch (Exception ex) { + m_log.ErrorFormat("[MONEY RPC]: genericCurrencyXMLRPCRequest: Unable to connect to Region Server {0}", uri); + m_log.ErrorFormat("[MONEY RPC]: 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; + } + + + /// + /// Update the client balance.We don't care about the result. + /// + /// + private void UpdateBalance(string userID, string message) + { + //m_log.InfoFormat("[MONEY RPC]: UpdateBalance: ID = {0}, Message = {1}", userID, message); + + string sessionID = string.Empty; + string secureID = string.Empty; + + if (m_sessionDic.ContainsKey(userID) && m_secureSessionDic.ContainsKey(userID)) { + sessionID = m_sessionDic[userID]; + secureID = m_secureSessionDic[userID]; + + Hashtable requestTable = new Hashtable(); + requestTable["clientUUID"] = userID; + requestTable["clientSessionID"] = sessionID; + requestTable["clientSecureSessionID"] = secureID; + requestTable["Balance"] = m_moneyDBService.getBalance(userID); + if (message!="") requestTable["Message"] = message; + + UserInfo user = m_moneyDBService.FetchUserInfo(userID); + if (user!=null) { + genericCurrencyXMLRPCRequest(requestTable, "UpdateBalance", user.SimIP); + m_log.InfoFormat("[MONEY RPC]: UpdateBalance: Sended UpdateBalance Request to {0}", user.SimIP.ToString()); + } + } + } + + + /// + /// RollBack the transaction if user failed to get the object paid + /// + /// + /// + protected bool RollBackTransaction(TransactionData transaction) + { + //m_log.InfoFormat("[MONEY RPC]: RollBackTransaction:"); + + if(m_moneyDBService.withdrawMoney(transaction.TransUUID, transaction.Receiver, transaction.Amount)) { + if(m_moneyDBService.giveMoney(transaction.TransUUID, transaction.Sender, transaction.Amount)) { + m_log.InfoFormat("[MONEY RPC]: 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; + } + + + // + public XmlRpcResponse handleCancelTransfer(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleCancelTransfer:"); + + 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 RPC]: 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 RPC]: handleCancelTransfer: User {0} wanted to cancel the transaction.", user.Avatar); + if (m_moneyDBService.ValidateTransfer(secureCode, transactionUUID)) { + m_log.InfoFormat("[MONEY RPC]: 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 RPC]: handleCancelTransfer: Exception occurred when transaction {0}: {1}", transactionID, e.ToString()); + } + return response; + } + + + // + public XmlRpcResponse handleGetTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleGetTransaction:"); + + 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 RPC]: 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 RPC]: handleGetTransaction: {0}", e.ToString()); + m_log.ErrorFormat("[MONEY RPC]: 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; + } + + + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + // In development + // + + public XmlRpcResponse handleWebLogin(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleWebLogin:"); + + 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 RPC]: handleWebLogin: User {0} has logged in from web.", userID); + responseData["success"] = true; + return response; + } + + + // + public XmlRpcResponse handleWebLogout(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleWebLogout:"); + + 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 RPC]: handleWebLogout: User {0} has logged out from web.", userID); + responseData["success"] = true; + return response; + } + + + /// + /// Get balance method for web pages. + /// + /// + /// + public XmlRpcResponse handleWebGetBalance(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleWebGetBalance:"); + + 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 RPC]: 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 RPC]: 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 RPC]: handleWebLogout: Session authentication failed when getting balance for user " + userID); + responseData["errorMessage"] = "Session check failure, please re-login"; + return response; + } + + + /// + /// Get transaction for web pages + /// + /// + /// + public XmlRpcResponse handleWebGetTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleWebGetTransaction:"); + + 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 RPC]: 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 RPC]: 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 RPC]: handleWebGetTransaction: Session authentication failed when getting transaction for user " + userID); + responseData["errorMessage"] = "Session check failure, please re-login"; + return response; + } + + + /// + /// Get total number of transactions for web pages. + /// + /// + /// + public XmlRpcResponse handleWebGetTransactionNum(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY RPC]: handleWebGetTransactionNum:"); + + 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 RPC]: handleWebGetTransactionNum: Get {0} transactions for user {1}", it, userID); + responseData["success"] = true; + responseData["number"] = it; + } + return response; + } + } + + m_log.Error("[MONEY RPC]: handleWebGetTransactionNum: Session authentication failed when getting transaction number for user " + userID); + responseData["errorMessage"] = "Session check failure, please re-login"; + return response; + } + } + +} + diff --git a/release/0.9.1_1/src/OpenSim.Grid.MoneyServer/Program.cs b/release/0.9.1_1/src/OpenSim.Grid.MoneyServer/Program.cs new file mode 100644 index 0000000..3fbd12d --- /dev/null +++ b/release/0.9.1_1/src/OpenSim.Grid.MoneyServer/Program.cs @@ -0,0 +1,42 @@ +/* + * 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. + */ + +using log4net.Config; + +namespace OpenSim.Grid.MoneyServer +{ + class Program + { + public static void Main(string[] args) + { + XmlConfigurator.Configure(); + MoneyServerBase app = new MoneyServerBase(); + app.Startup(); + app.Work(); + } + } +} diff --git a/release/0.9.1_1/src/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs b/release/0.9.1_1/src/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs new file mode 100644 index 0000000..72f808b --- /dev/null +++ b/release/0.9.1_1/src/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs @@ -0,0 +1,1903 @@ +// * 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. +// */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Reflection; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +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; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; +using NSL.Certificate.Tools; +using NSL.Network.XmlRpc; + + + +[assembly: Addin("DTLNSLMoneyModule", "1.0")] +[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)] + + + +namespace OpenSim.Modules.Currency +{ + // + 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 + } + + +/* + // Refer to OpenMetaverse + public enum OpenMetaverse.MoneyTransactionType : int + { + None = 0, + FailSimulatorTimeout = 1, + FailDataserverTimeout = 2, + ObjectClaim = 1000, + LandClaim = 1001, + GroupCreate = 1002, + ObjectPublicClaim = 1003, + GroupJoin = 1004, + TeleportCharge = 1100, + UploadCharge = 1101, + LandAuction = 1102, + ClassifiedCharge = 1103, + ObjectTax = 2000, + LandTax = 2001, + LightTax = 2002, + ParcelDirFee = 2003, + GroupTax = 2004, + ClassifiedRenew = 2005, + GiveInventory = 3000, + ObjectSale = 5000, + Gift = 5001, + LandSale = 5002, + ReferBonus = 5003, + InventorySale = 5004, + RefundPurchase = 5005, + LandPassSale = 5006, + DwellBonus = 5007, + PayObject = 5008, + ObjectPays = 5009, + GroupLandDeed = 6001, + GroupObjectDeed = 6002, + GroupLiability = 6003, + GroupDividend = 6004, + GroupMembershipDues = 6005, + ObjectRelease = 8000, + LandRelease = 8001, + ObjectDelete = 8002, + ObjectPublicDecay = 8003, + ObjectPublicDelete = 8004, + LindenAdjustment = 9000, + LindenGrant = 9001, + LindenPenalty = 9002, + EventFee = 9003, + EventPrize = 9004, + StipendBasic = 10000, + StipendDeveloper = 10001, + StipendAlways = 10002, + StipendDaily = 10003, + StipendRating = 10004, + StipendDelta = 10005 + } +*/ + + + // + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "DTLNSLMoneyModule")] + public class DTLNSLMoneyModule : IMoneyModule, ISharedRegionModule + { + #region Constant numbers and members. + + // 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 X509Certificate2 m_cert = null; + + 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(); // サーバ認証用 + + + /// + /// Scene dictionary indexed by Region Handle + /// + private Dictionary m_sceneList = new Dictionary(); + + /// + /// To cache the balance data while the money server is not available. + /// + 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; + + #endregion + + + // + public void Initialise(Scene scene, IConfigSource source) + { + Initialise(source); + if (string.IsNullOrEmpty(m_moneyServURL)) m_enable_server = false; + // + AddRegion(scene); + } + + + #region ISharedRegionModule interface + + public void Initialise(IConfigSource source) + { + //m_log.InfoFormat("[MONEY]: Initialise:"); + + // Handle the parameters errors. + if (source==null) return; + + try { + m_config = source; + + // [Economy] section + IConfig economyConfig = m_config.Configs["Economy"]; + + if (economyConfig.GetString("EconomyModule")!=Name) { + //m_enabled = false; + m_log.InfoFormat("[MONEY]: The DTL/NSL MoneyModule is disabled"); + return; + } + else { + m_log.InfoFormat("[MONEY]: The DTL/NSL MoneyModule is enabled"); + } + + m_sellEnabled = economyConfig.GetBoolean("SellEnabled", m_sellEnabled); + m_moneyServURL = economyConfig.GetString("CurrencyServer", m_moneyServURL); + + // クライアント証明書 + m_certFilename = economyConfig.GetString("ClientCertFilename", m_certFilename); + m_certPassword = economyConfig.GetString("ClientCertPassword", m_certPassword); + if (m_certFilename!="") { + m_cert = new X509Certificate2(m_certFilename, m_certPassword); + //m_cert = new X509Certificate2(m_certFilename, m_certPassword, X509KeyStorageFlags.MachineKeySet); + m_log.InfoFormat("[MONEY]: Issue Authentication of Client. Cert File is " + m_certFilename); + } + + // サーバ認証 + m_checkServerCert = economyConfig.GetBoolean("CheckServerCert", m_checkServerCert); + m_cacertFilename = economyConfig.GetString("CACertFilename", m_cacertFilename); + if (m_cacertFilename!="") { + m_certVerify.SetPrivateCA(m_cacertFilename); + m_log.InfoFormat("[MONEY]: Execute Authentication of Server. CA Cert File is " + m_cacertFilename); + } + else { + m_checkServerCert = false; + } + ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(m_certVerify.ValidateServerCertificate); + + + // Settlement + m_use_web_settle = economyConfig.GetBoolean("SettlementByWeb", m_use_web_settle); + m_settle_url = economyConfig.GetString ("SettlementURL", m_settle_url); + m_settle_message = economyConfig.GetString ("SettlementMessage", m_settle_message); + + // Price + 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); + PriceGroupCreate = economyConfig.GetInt ("PriceGroupCreate", PriceGroupCreate); + TeleportMinPrice = economyConfig.GetInt ("TeleportMinPrice", TeleportMinPrice); + TeleportPriceExponent = economyConfig.GetFloat("TeleportPriceExponent", TeleportPriceExponent); + EnergyEfficiency = economyConfig.GetFloat("EnergyEfficiency", EnergyEfficiency); + + // for HG Avatar + string avatar_class = economyConfig.GetString("HGAvatarAs", "HGAvatar").ToLower(); + if (avatar_class=="localavatar") m_hg_avatarClass = (int)AvatarType.LOCAL_AVATAR; + else if (avatar_class=="guestavatar") m_hg_avatarClass = (int)AvatarType.GUEST_AVATAR; + else if (avatar_class=="hgavatar") m_hg_avatarClass = (int)AvatarType.HG_AVATAR; + else if (avatar_class=="foreignavatar") m_hg_avatarClass = (int)AvatarType.FOREIGN_AVATAR; + else m_hg_avatarClass = (int)AvatarType.UNKNOWN_AVATAR; + + } + catch { + m_log.ErrorFormat("[MONEY]: Initialise: Faile to read configuration file"); + } + } + + + public void AddRegion(Scene scene) + { + //m_log.InfoFormat("[MONEY]: AddRegion:"); + + if (scene==null) return; + + scene.RegisterModuleInterface(this); // 競合するモジュールの排除 + + 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); // added + HttpServer.AddXmlRPCHandler("AddBankerMoney", AddBankerMoneyHandler); // added + HttpServer.AddXmlRPCHandler("SendMoney", SendMoneyHandler); // added + HttpServer.AddXmlRPCHandler("MoveMoney", MoveMoneyHandler); // added + + MainServer.Instance.AddXmlRPCHandler("OnMoneyTransfered", OnMoneyTransferedHandler); + MainServer.Instance.AddXmlRPCHandler("UpdateBalance", BalanceUpdateHandler); + MainServer.Instance.AddXmlRPCHandler("UserAlert", UserAlertHandler); + MainServer.Instance.AddXmlRPCHandler("GetBalance", GetBalanceHandler); // added + MainServer.Instance.AddXmlRPCHandler("AddBankerMoney", AddBankerMoneyHandler); // added + MainServer.Instance.AddXmlRPCHandler("SendMoney", SendMoneyHandler); // added + MainServer.Instance.AddXmlRPCHandler("MoveMoney", MoveMoneyHandler); // added + } + } + + 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; + } + + + 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; + } + } + + + public void RegionLoaded(Scene scene) + { + //m_log.InfoFormat("[MONEY]: RegionLoaded:"); + } + + + public Type ReplaceableInterface + { + //get { return typeof(IMoneyModule); } + get { return null; } + } + + + public bool IsSharedModule + { + get { return true; } + } + + + public string Name + { + get { return "DTLNSLMoneyModule"; } + } + + + public void PostInitialise() + { + //m_log.InfoFormat("[MONEY]: PostInitialise:"); + } + + + public void Close() + { + //m_log.InfoFormat("[MONEY]: Close:"); + } + + #endregion + + + #region IMoneyModule interface. + + // for LSL llGiveMoney() function + public bool ObjectGiveMoney(UUID objectID, UUID fromID, UUID toID, int amount, UUID txn, out string result) + { + //m_log.InfoFormat("[MONEY]: ObjectGiveMoney: LSL ObjectGiveMoney. UUID = {0}", objectID.ToString()); + + 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"; + return ret; + } + + + // + public int UploadCharge + { + get { return PriceUpload; } + } + + + // + public int GroupCreationCharge + { + get { return PriceGroupCreate; } + } + + + public int GetBalance(UUID agentID) + { + IClientAPI client = GetLocateClient(agentID); + return QueryBalanceFromMoneyServer(client); + } + + + 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; + } + return false; + } + + + 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; + } + return false; + } + + + 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); + } + + + public void ApplyCharge(UUID agentID, int amount, MoneyTransactionType type) + { + ApplyCharge(agentID, amount, type, string.Empty); + } + + + 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); + } + + + 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); + } + + + 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 + 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); + } + + // for 0.9.1 over + 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); + return ret; + } + + #endregion + + + #region MoneyModule event handlers + + // + private void OnNewClient(IClientAPI client) + { + m_log.InfoFormat("[MONEY]: OnNewClient"); + + client.OnEconomyDataRequest += OnEconomyDataRequest; + client.OnLogout += ClientClosed; + + client.OnMoneyBalanceRequest += OnMoneyBalanceRequest; + client.OnRequestPayPrice += OnRequestPayPrice; + client.OnObjectBuy += OnObjectBuy; + } + + + public void OnMakeRootAgent(ScenePresence agent) + { + m_log.InfoFormat("[MONEY]: OnMakeRootAgent:"); + + 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); + + //client.OnMoneyBalanceRequest += OnMoneyBalanceRequest; + //client.OnRequestPayPrice += OnRequestPayPrice; + //client.OnObjectBuy += OnObjectBuy; + } + + + // for OnClientClosed event + private void ClientClosed(IClientAPI client) + { + //m_log.InfoFormat("[MONEY]: ClientClosed:"); + + if (m_enable_server && client!=null) { + LogoffMoneyServer(client); + } + } + + + // for OnMakeChildAgent event + private void MakeChildAgent(ScenePresence avatar) + { + //m_log.InfoFormat("[MONEY]: MakeChildAgent:"); + } + + + // for OnMoneyTransfer event + private void MoneyTransferAction(Object sender, EventManager.MoneyTransferArgs moneyEvent) + { + //m_log.InfoFormat("[MONEY]: MoneyTransferAction: type = {0}", moneyEvent.transactiontype); + + 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 + private void ValidateLandBuy(Object sender, EventManager.LandBuyArgs landBuyEvent) + { + //m_log.InfoFormat("[MONEY]: ValidateLandBuy:"); + + 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 + private void processLandBuy(Object sender, EventManager.LandBuyArgs landBuyEvent) + { + //m_log.InfoFormat("[MONEY]: processLandBuy:"); + + 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 + public void OnObjectBuy(IClientAPI remoteClient, UUID agentID, UUID sessionID, + UUID groupID, UUID categoryID, uint localID, byte saleType, int salePrice) + { + m_log.InfoFormat("[MONEY]: 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(); + 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); + 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]: 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"); + } + } + + + private void OnRequestPayPrice(IClientAPI client, UUID objectID) + { + m_log.InfoFormat("[MONEY]: 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) + private void OnEconomyDataRequest(IClientAPI user) + { + //m_log.InfoFormat("[MONEY]: OnEconomyDataRequest:"); + //IClientAPI user = GetLocateClient(agentId); + + 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); + } + } + } + + #endregion + + + #region MoneyModule XML-RPC Handler + + // "OnMoneyTransfered" RPC from MoneyServer + public XmlRpcResponse OnMoneyTransferedHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + m_log.InfoFormat("[MONEY]: 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]: 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]: OnMoneyTransferedHandler: Transaction is failed. MoneyServer will rollback"); + } + resp.Value = paramTable; + + return resp; + } + + + // "UpdateBalance" RPC from MoneyServer or Script + public XmlRpcResponse BalanceUpdateHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: BalanceUpdateHandler:"); + + bool ret = false; + + #region Update the balance from money server. + + 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; + } + } + } + } + } + + #endregion + + // Send the response to money server. + XmlRpcResponse resp = new XmlRpcResponse(); + Hashtable paramTable = new Hashtable(); + paramTable["success"] = ret; + + if (!ret) { + m_log.ErrorFormat("[MONEY]: BalanceUpdateHandler: Cannot update client balance from MoneyServer"); + } + resp.Value = paramTable; + + return resp; + } + + + // "UserAlert" RPC from Script + public XmlRpcResponse UserAlertHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: UserAlertHandler:"); + + bool ret = false; + + #region confirm the request and show the notice from money server. + + 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; + } + } + } + } + } + // + #endregion + + // 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 + public XmlRpcResponse GetBalanceHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: GetBalanceHandler:"); + + 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]: 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 + public XmlRpcResponse AddBankerMoneyHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: 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]: 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 + public XmlRpcResponse SendMoneyHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: SendMoneyHandler:"); + + 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]: SendMoneyHandler: SecretCode: {0} + {1} = {2}", secretCode, scriptIP, hash); + ret = SendMoneyTo(agentUUID, amount, type, hash); + } + } + else { + m_log.ErrorFormat("[MONEY]: SendMoneyHandler: amount is missed"); + } + } + else { + if (!requestParam.Contains("agentUUID")) { + m_log.ErrorFormat("[MONEY]: SendMoneyHandler: agentUUID is missed"); + } + if (!requestParam.Contains("secretAccessCode")) { + m_log.ErrorFormat("[MONEY]: SendMoneyHandler: secretAccessCode is missed"); + } + } + } + else { + m_log.ErrorFormat("[MONEY]: SendMoneyHandler: Params count is under 0"); + } + + if (!ret) m_log.ErrorFormat("[MONEY]: 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 + public XmlRpcResponse MoveMoneyHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY]: MoveMoneyHandler:"); + + 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]: MoveMoneyHandler: SecretCode: {0} + {1} = {2}", secretCode, scriptIP, hash); + ret = MoveMoneyFromTo(fromUUID, toUUID, amount, hash); + } + else { + m_log.ErrorFormat("[MONEY]: MoveMoneyHandler: amount is missed"); + } + } + else { + if (!requestParam.Contains("fromUUID") && !requestParam.Contains("toUUID")) { + m_log.ErrorFormat("[MONEY]: MoveMoneyHandler: fromUUID and toUUID are missed"); + } + if (!requestParam.Contains("secretAccessCode")) { + m_log.ErrorFormat("[MONEY]: MoveMoneyHandler: secretAccessCode is missed"); + } + } + } + else { + m_log.ErrorFormat("[MONEY]: MoveMoneyHandler: Params count is under 0"); + } + + if (!ret) m_log.ErrorFormat("[MONEY]: 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; + } + + #endregion + + + #region MoneyModule private help functions + + /// + /// 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) + { + //m_log.InfoFormat("[MONEY]: TransferMoney:"); + + bool ret = false; + IClientAPI senderClient = GetLocateClient(sender); + + // Handle the illegal transaction. + // receiverClient could be null. + if (senderClient==null) { + m_log.InfoFormat("[MONEY]: TransferMoney: Client {0} not found", sender.ToString()); + return false; + } + + if (QueryBalanceFromMoneyServer(senderClient) + /// 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) + { + //m_log.InfoFormat("[MONEY]: ForceTransferMoney:"); + + bool ret = false; + + #region Force send transaction request to money server and parse the resultes. + + 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]: ForceTransferMoney: Can not money force transfer request from [{0}] to [{1}]", sender.ToString(), receiver.ToString()); + } + //else m_log.ErrorFormat("[MONEY]: ForceTransferMoney: Money Server is not available!!"); + + #endregion + + 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) + { + //m_log.InfoFormat("[MONEY]: SendMoneyTo:"); + + 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]: SendMoneyTo: Fail Message is {0}", resultTable["message"]); + } + else m_log.ErrorFormat("[MONEY]: SendMoneyTo: Money Server is not responce"); + } + //else m_log.ErrorFormat("[MONEY]: SendMoneyTo: Money Server is not available!!"); + + 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) + { + //m_log.InfoFormat("[MONEY]: MoveMoneyFromTo:"); + + 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]: MoveMoneyFromTo: Fail Message is {0}", resultTable["message"]); + } + else m_log.ErrorFormat("[MONEY]: MoveMoneyFromTo: Money Server is not responce"); + } + //else m_log.ErrorFormat("[MONEY]: MoveMoneyFromTo: Money Server is not available!!"); + + 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) + { + //m_log.InfoFormat("[MONEY]: AddBankerMoney:"); + + 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]: AddBankerMoney: Avatar is not Banker. Web Settlemrnt is used."); + } + else m_log.ErrorFormat("[MONEY]: AddBankerMoney: Fail Message {0}", resultTable["message"]); + } + } + else m_log.ErrorFormat("[MONEY]: AddBankerMoney: Money Server is not responce"); + } + //else m_log.ErrorFormat("[MONEY]: AddBankerMoney: Money Server is not available!!"); + + 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) + { + //m_log.InfoFormat("[MONEY]: PayMoneyCharge:"); + + bool ret = false; + IClientAPI senderClient = GetLocateClient(sender); + + // Handle the illegal transaction. + // receiverClient could be null. + if (senderClient==null) { + m_log.InfoFormat("[MONEY]: PayMoneyCharge: Client {0} is not found", sender.ToString()); + return false; + } + + if (QueryBalanceFromMoneyServer(senderClient) + /// 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) + { + //m_log.InfoFormat("[MONEY]: LoginMoneyServer:"); + + balance = 0; + bool ret = false; + bool isNpc = avatar.IsNPC; + + IClientAPI client = avatar.ControllingClient; + + #region Send money server the client info for login. + + 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; + } + } + } + + ////////////////////////////////////////////////////////////// + // 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; + } + + // + 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]: LoginMoneyServer: Client [{0}] login Money Server {1}", client.AgentId.ToString(), m_moneyServURL); + ret = true; + } + } + else m_log.ErrorFormat("[MONEY]: LoginMoneyServer: Unable to login Money Server {0} for client [{1}]", m_moneyServURL, client.AgentId.ToString()); + } + else m_log.ErrorFormat("[MONEY]: LoginMoneyServer: Money Server is not available!!"); + + #endregion + + // Viewerへ設定を通知する. + 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) + { + //m_log.InfoFormat("[MONEY]: LogoffMoneyServer:"); + + 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; + } + } + } + + return ret; + } + + + // + private EventManager.MoneyTransferArgs GetTransactionInfo(IClientAPI client, string transactionID) + { + //m_log.InfoFormat("[MONEY]: GetTransactionInfo:"); + + 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]: GetTransactionInfo: GetTransactionInfo: Fail to Request. {0}", (string)resultTable["description"]); + } + } + else { + m_log.ErrorFormat("[MONEY]: GetTransactionInfo: Invalid Response"); + } + } + else { + m_log.ErrorFormat("[MONEY]: 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) + { + //m_log.InfoFormat("[MONEY]: genericCurrencyXMLRPCRequest:"); + + if (reqParams.Count<=0 || string.IsNullOrEmpty(method)) return null; + + if (m_checkServerCert) { + if (!m_moneyServURL.StartsWith("https://")) { + m_log.InfoFormat("[MONEY]: 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]: 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); + } + catch (Exception ex) { + m_log.ErrorFormat("[MONEY]: genericCurrencyXMLRPCRequest: Unable to connect to Money Server {0}", m_moneyServURL); + m_log.ErrorFormat("[MONEY]: 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; + } + } + } + } + } + + return client; + } + + + 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; + } + } + } + } + + return scene; + } + + + 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; + } + + #endregion + } + +} diff --git a/release/0.9.1_1/src/OpenSim.Modules.Currency/NSLCertificateTools.cs b/release/0.9.1_1/src/OpenSim.Modules.Currency/NSLCertificateTools.cs new file mode 100644 index 0000000..6365c8f --- /dev/null +++ b/release/0.9.1_1/src/OpenSim.Modules.Currency/NSLCertificateTools.cs @@ -0,0 +1,226 @@ +/* + * Copyright (c) Contributors, http://www.nsl.tuis.ac.jp + * + */ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using System.Net; +using System.Net.Security; +using System.Text; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; + +using log4net; + + +namespace NSL.Certificate.Tools +{ + // + 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 Mono.Security.X509.X509Crl m_clientcrl = null; + + + public NSLCertificateVerify() + { + m_chain = null; + m_cacert = null; + m_clientcrl = null; + } + + + public NSLCertificateVerify(string certfile) + { + SetPrivateCA(certfile); + } + + + public NSLCertificateVerify(string certfile, string crlfile) + { + SetPrivateCA (certfile); + SetPrivateCRL(crlfile); + } + + + 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; + } + } + + + 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); + } + } + + + // + // + // + 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; iz};2!ujDpzMS|TS%dy`L}FO+lSJRn5B@C?+>Lw|840K{=eTj@16IZ z(P)dEKK-7rO8m~f=bU@*x#ymH&biAwv-^%Wo1if!gnz&OwK1Q?C;x7g=gf~upp(l! zmo%S>e|7n11G~Pu{J@EUQg)v#ov4tT1(f^~U@Vkc2{C>I96*BAvx2xntT8vp^A8iczn|=9?(fsLA zBB&0_iB`2MZt+Ucs}-;O{Mkeu8dm zB?JX2SNxGT$AnF646=NqF@$b6rj|rVKJ8y-eav7;Sw7`tKM^&iwY8r)BoCy})_?N5i zLkB}eJVpwOfGoZisnSJAy`20yrVVX^{}d{|$gQTZ80ZPsTUdgJzrJUL&r>d+EeXf~ zk`J{WIl~XlWPknZU%zrw)M;^HA~+#artXsZ>!K4&QTf_fv~V##qpN+*Z1`YHBKu=- z)wT>M?ZMaBgDkRZUT+qI@1x)WHK&|5bTpEP2*1I?a!}Nr+gUG#VucmR7B0a<@!FpX z(j^T-8dp+^sHN^)G*Y+}sqaKrn*ar5qNcIMtbyDiNTj>W0?0n#Aiehx9{5|lK4g0F z32v%*3WWx$4dAc4%5%-XUDg$!06*TzQ&ZmxCr( z+h!MT5~gmRi^+CrXXDDw`>1JZHSN}BH*NE5_H9Qh=RHQh%9j*&&RteYI+d&D7wF$2 z<~Ja#xW`jVeKORtF_8?l-3+c;kJKC2+J?fh)}Iv!jvrm=kS-#za=R@o~t!7hSBwIH>P%M*)_@Lw<%gtic+~7AXh3g^4_wVF&T7&m8@o5>Mr8K}~x(o4-5Mb^whR4&u>v2#-lfmBAm^ zi1kS0f2=#X$T@@A9CC`6#>~hWSSXu>(jmEuP{`Xt5g*wk7KAp50klbsA6Be~(IQud zfL_fSur9O&&;}$=6N+UZk{@IeOFm10H6M~cBor$&B(IDkiA5Tc_c4h@8Iqr4GAqgd z&E#@PZciYIRTpWQgvAd{zr0>WtgNZAt_+zd1~EywgwDVMgq-l1Hhwr0{3zTBJ96f7 zAK)bfK%}G~y!bJ+=uA+Yg(DPz2-d}YO>nRdp~CING3BEENl+Ah;SQi+Mc7tC2g9Xl zs$IjcCCy%VGmBjE1zI9zH#MWy6~P($Gp_eDwf<8t+FHv%YI92iPz`b;CclV>>5Oe!=(#OO%VxmuvI5SktJueNEV}fke1P zB-0n_7&)^>c@Y4Zi=1iq0WkR@XVwzX1~rPTF2@Xq93y|WDpFR+3< zQM=-AfS~weJfxxMYE*Jji|uSiE4dzkRH`t58rGx~^ivNc8$S*5#4;1cErCE;K6Fng z6V+U-B@g_5qRG2K+@5wb_aGzVlLmW+pvM2cT2 zqf>6CjBbb&zgxzlX~~qanOLOwb3a-#9y%H+HZ1gUCK;mJ2H9_HeO=;5OU6UjsUYh0 z!xdRlbB*z~Aylz86;G$r$#i|XA>G(E49KcfOfcX9C(BN`<(q z70EQEn%Xy{nhGPrNGh>5*pjJB)h&x}iUWEtp>?(@LW)vH3d6A}l&Z@lQb}zlxG+CA%k0{`v=EHH@^h5Iy7 zS9ql+l7&eo8db-keyPR+iCqZkn#H z*I2q(k)9zH_v%8A&Rv~Zq{4WTeGa7-)fR?=Jz<&Z3T0m?~jRl~Bc0sVdB=;1$+fjl$

UDZt@0dr-s;}5< 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 literal 0 HcmV?d00001 diff --git a/release/0.9.2/bin/OpenSim.Modules.Currency.dll b/release/0.9.2/bin/OpenSim.Modules.Currency.dll new file mode 100644 index 0000000000000000000000000000000000000000..0eaf7aeb2621f0dbe3265ac2c51651c7c4ed6f3e GIT binary patch literal 45056 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 A+W-In literal 0 HcmV?d00001 diff --git a/release/0.9.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs b/release/0.9.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs new file mode 100644 index 0000000..f807ecb --- /dev/null +++ b/release/0.9.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/IMoneyManager.cs @@ -0,0 +1,71 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +//using System.Linq; +using System.Text; +using OpenMetaverse; + +namespace OpenSim.Data.MySQL.MySQLMoneyDataWrapper +{ + public interface IMoneyManager + { + int getBalance(string userID); + + //int getBalanceStatus(string userID); + + //bool updateBalanceStatus(string userID,int status); + + bool withdrawMoney(UUID transactionID, string senderID, int amount); + + bool giveMoney(UUID transactionID, string receiverID, int amount); + + bool addTransaction(TransactionData transaction); + + bool updateTransactionStatus(UUID transactionID, int status, string description); + + TransactionData FetchTransaction(UUID transactionID); + + TransactionData[] FetchTransaction(string userID, int startTime, int endTime, uint index, uint retNum); + + int getTransactionNum(string userID, int startTime, int endTime); + + bool addUser(string userID, int balance, int status, int type); + + bool SetTransExpired(int deadTime); + + bool ValidateTransfer(string secureCode, UUID transactionID); + + bool addUserInfo(UserInfo user); + + UserInfo fetchUserInfo(string userID); + + bool updateUserInfo(UserInfo user); + + } +} diff --git a/release/0.9.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs b/release/0.9.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs new file mode 100644 index 0000000..d260035 --- /dev/null +++ b/release/0.9.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLMoneyManager.cs @@ -0,0 +1,1627 @@ +/* + * 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. + */ + +using System; +using System.Data; +using System.Reflection; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using log4net; +using MySql.Data.MySqlClient; +using OpenMetaverse; + + +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; + private MySqlConnection dbcon; + + + public MySQLMoneyManager(string hostname,string database,string username ,string password,string cpooling, string port) + { + string s = "Server=" + hostname + ";Port=" + port + ";Database=" + database + + ";User ID=" + username + ";Password=" + password + ";Pooling=" + cpooling + ";"; + Initialise(s); + } + + + public MySQLMoneyManager(string connect) + { + Initialise(connect); + } + + + private void Initialise(string connect) + { + try { + connectString = connect; + dbcon = new MySqlConnection(connectString); + try { + dbcon.Open(); + } + catch (Exception e) { + throw new Exception("[MONEY MANAGER]: Connection error while using connection string ["+connectString+"]", e); + } + //m_log.Info("[MONEY MANAGER]: Connection established"); + } + + catch(Exception e) { + throw new Exception("[MONEY MANAGER]: Error initialising MySql Database: " + e.ToString()); + } + + try { + Dictionary tableList = new Dictionary(); + tableList = CheckTables(); + + // + // Balances Table + 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; + } + } + + // + // UserInfo Table + 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; + } + } + + // + // Transactions Table + if (!tableList.ContainsKey(Table_of_Transactions)) { + try { + CreateTransactionsTable(); + } + catch (Exception e) { + throw new Exception("[MONEY MANAGER]: Error creating transactions table: " + e.ToString()); + } + } + // check transactions table version + 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; + } + } + + // + // TotalSales Table + 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; + } + } + } + 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 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; + } + + + + /////////////////////////////////////////////////////////////////////// + // create Tables + + 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(); + } + + + 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(); + } + + + 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(); + } + + + 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(); + } + + + + /////////////////////////////////////////////////////////////////////// + // update Balances Table + + 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=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 uuid, url, name, pass + bool updatedb = true; + for (int i=0; i + /// 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() + { + //m_log.Info("[MONEY MANAGER]: Converting Transaction Table..."); + 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 + /// 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() + { + //m_log.Info("[MONEY MANAGER]: Converting Transaction Table..."); + 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 + /// 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 + + 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(); + } + + + 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(); + } + + + /////////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////////// + // + + 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; + } + } + + + /// + /// Reconnect to the database + /// + 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()); + } + } + } + + + + /////////////////////////////////////////////////////////////////////// + // + // balances + // + + /// + /// 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"]); + } + } +#pragma warning disable CS0168 // The variable 'e' is declared but never +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception e) { + m_log.ErrorFormat("[MoneyDB]: MySql failed to fetch balance {0}.", userID); + retValue = -2; + } +#pragma warning restore CA1031 // Do not catch general exception types +#pragma warning restore CS0168 // The variable 'e' is declared but never + + dbReader.Close(); + } + cmd.Dispose(); + + return retValue; + } + + + 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; + } + + + 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; + } + + + + /////////////////////////////////////////////////////////////////////// + // + // totalsales + // + 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 0) bRet = true; + + cmd.Dispose(); + return bRet; + } + + + 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; + + } + + + 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 0) bRet = true; + + cmd.Dispose(); + return bRet; + } + + + 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; + } + + + 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; + } + + + 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; + } + + + 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(); + } + + + 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 + // + public bool addUserInfo(UserInfo userInfo) + { + //m_log.Error("[MONEY MANAGER]: Adding UserInfo: " + userInfo.UserID); + + 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; + } + + + public UserInfo fetchUserInfo(string userID) + { + //m_log.Error("[MONEY MANAGER]: Fetching UserInfo: " + 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; + } + + + public bool updateUserInfo(UserInfo userInfo) + { + //m_log.Error("[MONEY MANAGER]: Updating UserInfo: " + userInfo.UserID); + + 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; + } + + } +} diff --git a/release/0.9.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs b/release/0.9.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs new file mode 100644 index 0000000..26974c8 --- /dev/null +++ b/release/0.9.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs @@ -0,0 +1,53 @@ +/* + * 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 OpenSimulator 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. + */ + +using System; +using System.Threading; + +namespace OpenSim.Data.MySQL.MySQLMoneyDataWrapper +{ + // This bit of code is from OpenSim.Data.MySQLSuperManager + public class MySQLSuperManager + { + public bool Locked; + private readonly Mutex m_lock = new Mutex(false); + public MySQLMoneyManager Manager; + public string Running; + + public void GetLock() + { + Locked = true; + m_lock.WaitOne(); + } + + public void Release() + { + m_lock.ReleaseMutex(); + Locked = false; + } + } +} diff --git a/release/0.9.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs b/release/0.9.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs new file mode 100644 index 0000000..abb0fd1 --- /dev/null +++ b/release/0.9.2/src/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs @@ -0,0 +1,237 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +using System.Text; +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_objectID = "00000000-0000-0000-0000-000000000000"; + 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; + +/* + public TransactionData(string uuid, string sender, string receiver, + int amount, int time, int status, string description) + { + this.m_uuid = uuid; + this.m_sender = sender; + this.m_receiver = receiver; + this.m_amount = amount; + } +*/ + + public UUID TransUUID + { + get { return m_uuid; } + set { m_uuid = value; } + } + + public string Sender + { + get { return m_sender; } + set { m_sender = value; } + } + + public string Receiver + { + get { return m_receiver; } + set { m_receiver = value; } + } + + public int Amount + { + get { return m_amount; } + set { m_amount = value; } + } + + public int SenderBalance + { + get { return m_senderBalance; } + set { m_senderBalance = value; } + } + + public int ReceiverBalance + { + get { return m_receiverBalance; } + set { m_receiverBalance = value; } + } + + public int Type + { + get { return m_type; } + set { m_type = value; } + } + + public int Time + { + get { return m_time; } + set { m_time = value; } + } + + public int Status + { + get { return m_status; } + set { m_status = value; } + } + + public string Description + { + get { return m_description; } + set { m_description = value; } + } + + public string ObjectUUID + { + get { return m_objectID; } + set { m_objectID = value; } + } + + public string ObjectName + { + get { return m_objectName; } + set { m_objectName = value; } + } + + public string RegionHandle + { + get { return m_regionHandle; } + set { m_regionHandle = value; } + } + + public string RegionUUID + { + get { return m_regionUUID; } + set { m_regionUUID = value; } + } + + public string SecureCode + { + get { return m_secureCode; } + set { m_secureCode = value; } + } + + 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; + + public string UserID + { + get { return m_userID; } + set { m_userID = value; } + } + + public string SimIP + { + get { return m_simIP; } + set { m_simIP = value; } + } + + public string Avatar + { + get { return m_avatarName; } + set { m_avatarName = value; } + } + + public string PswHash + { + get { return m_passwordHash; } + set { m_passwordHash = value; } + } + + public int Type + { + get { return m_avatarType; } + set { m_avatarType = value; } + } + + public int Class + { + get { return m_avatarClass; } + set { m_avatarClass = value; } + } + + public string ServerURL + { + get { return m_serverURL; } + set { m_serverURL = value; } + } + } +} diff --git a/release/0.9.2/src/OpenSim.Grid.MoneyServer/IMoneyDBService.cs b/release/0.9.2/src/OpenSim.Grid.MoneyServer/IMoneyDBService.cs new file mode 100644 index 0000000..2043719 --- /dev/null +++ b/release/0.9.2/src/OpenSim.Grid.MoneyServer/IMoneyDBService.cs @@ -0,0 +1,77 @@ +/* + * 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. + */ + +#pragma warning disable IDE1006 + +#pragma warning disable S1128 // Unused "using" should be removed +using System; +using System.Collections.Generic; +using System.Text; +using OpenMetaverse; +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; +#pragma warning restore S1128 // Unused "using" should be removed + + +namespace OpenSim.Grid.MoneyServer +{ + /// + /// IMoney DB Service + /// + public interface IMoneyDBService + { + int getBalance(string userID); + + bool withdrawMoney(UUID transactionID, string senderID, int amount); + + bool giveMoney(UUID transactionID, string receiverID, int amount); + + bool addTransaction(TransactionData transaction); + + bool addUser(string userID, int balance, int status, int type); + + bool updateTransactionStatus(UUID transactionID, int status, string description); + + bool SetTransExpired(int deadTime); + + bool ValidateTransfer(string secureCode, UUID transactionID); + + TransactionData FetchTransaction(UUID transactionID); + + TransactionData FetchTransaction(string userID, int startTime, int endTime, int lastIndex); + + int getTransactionNum(string userID, int startTime, int endTime); + + bool DoTransfer(UUID transactionUUID); + + bool DoAddMoney(UUID transactionUUID); // Added by Fumi.Iseki + + bool TryAddUserInfo(UserInfo user); + + UserInfo FetchUserInfo(string userID); + + } +} diff --git a/release/0.9.2/src/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs b/release/0.9.2/src/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs new file mode 100644 index 0000000..7f84903 --- /dev/null +++ b/release/0.9.2/src/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs @@ -0,0 +1,52 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +//using System.Linq; +using System.Text; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; + +using Nini.Config; + + +namespace OpenSim.Grid.MoneyServer +{ + public interface IMoneyServiceCore + { + BaseHttpServer GetHttpServer(); + Dictionary GetSessionDic(); + Dictionary GetSecureSessionDic(); + Dictionary GetWebSessionDic(); + + // + IConfig GetServerConfig(); + IConfig GetCertConfig(); + bool IsCheckClientCert(); + } +} diff --git a/release/0.9.2/src/OpenSim.Grid.MoneyServer/MoneyDBService.cs b/release/0.9.2/src/OpenSim.Grid.MoneyServer/MoneyDBService.cs new file mode 100644 index 0000000..07fa789 --- /dev/null +++ b/release/0.9.2/src/OpenSim.Grid.MoneyServer/MoneyDBService.cs @@ -0,0 +1,584 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; +using OpenSim.Modules.Currency; +using log4net; +using System.Reflection; +using OpenMetaverse; + + +namespace OpenSim.Grid.MoneyServer +{ + class MoneyDBService: IMoneyDBService + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private string m_connect; + //private MySQLMoneyManager m_moneyManager; + 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 void Initialise(string connectionString, int maxDBConnections) + { + m_connect = connectionString; + m_maxConnections = maxDBConnections; + if (connectionString != string.Empty) { + //m_moneyManager = new MySQLMoneyManager(connectionString); + + //m_log.Info("Creating " + m_maxConnections + " DB connections..."); + for (int i=0; im_maxConnections) { + lockedCons = 0; + System.Threading.Thread.Sleep(1000); // Wait some time before searching them again. + m_log.Debug("WARNING: All threads are in use. Probable cause: Something didnt release a mutex properly, or high volume of requests inbound."); + } + } + } + + + public int getBalance(string userID) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.getBalance(userID); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + return dbm.Manager.getBalance(userID); + } + catch(Exception e) { + m_log.Error(e.ToString()); + return 0; + } + finally { + dbm.Release(); + } + } + + + 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) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + return dbm.Manager.withdrawMoney(transactionID, senderID, amount); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool giveMoney(UUID transactionID, string receiverID, int amount) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.giveMoney(transactionID, receiverID, amount); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + return dbm.Manager.giveMoney(transactionID, receiverID, amount); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool setTotalSale(TransactionData transaction) + { + if (transaction.Receiver==transaction.Sender) return false; + if (transaction.Sender==UUID.Zero.ToString()) return false; + + MySQLSuperManager dbm = GetLockedConnection(); + + 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) { + m_log.Info(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.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool addTransaction(TransactionData transaction) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.addTransaction(transaction); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + return dbm.Manager.addTransaction(transaction); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool addUser(string userID, int balance, int status, int type) + { + 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) return false; + + // + MySQLSuperManager dbm = GetLockedConnection(); + + try { + ret = dbm.Manager.addUser(userID, 0, status, type); // make Balance Table + } + catch (MySql.Data.MySqlClient.MySqlException e) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + ret = dbm.Manager.addUser(userID, 0, status, type); // make Balance Table + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + + // + if (ret) ret = giveMoney(transaction.TransUUID, userID, balance); + return ret; + } + + + 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) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + return dbm.Manager.updateTransactionStatus(transactionID, status, description); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool SetTransExpired(int deadTime) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.SetTransExpired(deadTime); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + return dbm.Manager.SetTransExpired(deadTime); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public bool ValidateTransfer(string secureCode, UUID transactionID) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.ValidateTransfer(secureCode, transactionID); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + return dbm.Manager.ValidateTransfer(secureCode, transactionID); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return false; + } + finally { + dbm.Release(); + } + } + + + public TransactionData FetchTransaction(UUID transactionID) + { + MySQLSuperManager dbm = GetLockedConnection(); + + try { + return dbm.Manager.FetchTransaction(transactionID); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + return dbm.Manager.FetchTransaction(transactionID); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return null; + } + finally { + dbm.Release(); + } + } + + + 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) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + arrTransaction = dbm.Manager.FetchTransaction(userID, startTime, endTime, index, 1); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return null; + } + finally { + dbm.Release(); + } + + // + if (arrTransaction.Length > 0) { + return arrTransaction[0]; + } + else { + return null; + } + } + + + public bool DoTransfer(UUID transactionUUID) + { + 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 DB. {0}", transaction.Receiver); + 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); + } + + return do_trans; + } + + + // by Fumi.Iseki + public bool DoAddMoney(UUID transactionUUID) + { + 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 DB. {0}", transaction.Receiver); + return false; + } + // + if (giveMoney(transactionUUID, transaction.Receiver, transaction.Amount)) { + setTotalSale(transaction); + 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()); + } + + return false; + } + + + /////////////////////////////////////////////////////////////////////////////////////////////////////// + // + // userinfo + // + + public bool TryAddUserInfo(UserInfo user) + { + MySQLSuperManager dbm = GetLockedConnection(); + + UserInfo userInfo = null; + + try { + userInfo = dbm.Manager.fetchUserInfo(user.UserID); + } + catch (MySql.Data.MySqlClient.MySqlException e) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + userInfo = dbm.Manager.fetchUserInfo(user.UserID); + } + catch (Exception e) { + m_log.Error(e.ToString()); + 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(); + } + } + + + public UserInfo FetchUserInfo(string userID) + { + UserInfo userInfo = null; + MySQLSuperManager dbm = GetLockedConnection(); + + try { + userInfo = dbm.Manager.fetchUserInfo(userID); + return userInfo; + } + catch (MySql.Data.MySqlClient.MySqlException e) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + userInfo = dbm.Manager.fetchUserInfo(userID); + return userInfo; + } + catch (Exception e) { + m_log.Error(e.ToString()); + return null; + } + finally { + dbm.Release(); + } + } + + + 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) { + m_log.Info(e.ToString()); + dbm.Manager.Reconnect(); + return dbm.Manager.getTransactionNum(userID,startTime,endTime); + } + catch (Exception e) { + m_log.Error(e.ToString()); + return -1; + } + finally { + dbm.Release(); + } + } + } +} diff --git a/release/0.9.2/src/OpenSim.Grid.MoneyServer/MoneyServerBase.cs b/release/0.9.2/src/OpenSim.Grid.MoneyServer/MoneyServerBase.cs new file mode 100644 index 0000000..deddd76 --- /dev/null +++ b/release/0.9.2/src/OpenSim.Grid.MoneyServer/MoneyServerBase.cs @@ -0,0 +1,407 @@ +/* + * 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. + */ + +#pragma warning disable S1128 // Unused "using" should be removed +using System; +using System.IO; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using System.Net; +using System.Net.Security; +using System.Reflection; +using System.Timers; +using OpenSim.Framework.Servers.HttpServer; +using Nini.Config; +using log4net; +using OpenSim.Framework; +using OpenSim.Framework.Console; +using OpenSim.Framework.Servers; +using OpenSim.Data; +using NSL.Certificate.Tools; + +using System.Threading; +using System.Security.Cryptography.X509Certificates; +using log4net.Appender; +using log4net.Core; +using log4net.Repository; +using OpenMetaverse; +using Timer = System.Timers.Timer; +#pragma warning restore S1128 // Unused "using" should be removed + + +/// +/// OpenSim Grid MoneyServer +/// +namespace OpenSim.Grid.MoneyServer +{ + /// + /// class MoneyServerBase : BaseOpenSimServer, IMoneyServiceCore + /// Manni internal class + /// + internal class MoneyServerBase : BaseOpenSimServer, IMoneyServiceCore + { + 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 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; + +#pragma warning disable S1450 // Private fields only used as local variables in methods should become local variables + private MoneyXmlRpcModule m_moneyXmlRpcModule; +#pragma warning restore S1450 // Private fields only used as local variables in methods should become local variables + private MoneyDBService m_moneyDBService; + + private NSLCertificateVerify m_certVerify = new NSLCertificateVerify(); // Client Certificate + +#pragma warning disable S2933 // Fields that are only assigned in the constructor should be "readonly" + private Dictionary m_sessionDic = new Dictionary(); + private Dictionary m_secureSessionDic = new Dictionary(); + private Dictionary m_webSessionDic = new Dictionary(); +#pragma warning restore S2933 // Fields that are only assigned in the constructor should be "readonly" + + IConfig m_server_config; + IConfig m_cert_config; + + + /// + /// Money Server Base + /// + public MoneyServerBase() + { + m_console = new LocalConsole("MoneyServer "); + MainConsole.Instance = m_console; + } + + + /// + /// Work + /// + public void Work() + { + //The timer checks the transactions table every 60 seconds + Timer checkTimer = new Timer + { + Interval = 60 * 1000, + Enabled = true + }; + checkTimer.Elapsed += new ElapsedEventHandler(CheckTransaction); + checkTimer.Start(); + + while (true) { + m_console.Prompt(); + } + } + + + /// + /// Check the transactions table, set expired transaction state to failed + /// + private void CheckTransaction(object sender, ElapsedEventArgs e) + { + 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); + } + + + /// + /// 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"); + + /* + Type typeBaseHttpServer = typeof(BaseHttpServer); // BaseHttpServer.cs にパッチがあたっていない場合のため + PropertyInfo pinfo = typeBaseHttpServer.GetProperty("CertificateValidationCallback"); + + if (pinfo!=null) { + pinfo.SetValue(m_httpServer, (RemoteCertificateValidationCallback)m_certVerify.ValidateClientCertificate, null); + m_log.Info ("[MONEY SERVER]: Set RemoteCertificateValidationCallback"); + } + else { + m_log.Error("[MONEY SERVER]: StartupSpecific: CheckClientCert is true. But this MoneyServer does not support CheckClientCert!!"); + } + */ + } + } + 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); + } + } + + + /// + /// Read Ini Config + /// + protected 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); + + // + // [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); + } + } + } + + catch (Exception) + { + m_log.Error("[MONEY SERVER]: ReadIniConfig: Fail to setup configure. Please check MoneyServer.ini. Exit"); + 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; + } +#pragma warning disable S2486 // Generic exceptions should not be ignored +#pragma warning disable CA1031 // Do not catch general exception types +#pragma warning disable S108 // Nested blocks of code should not be left empty + catch (Exception) { } +#pragma warning restore S108 // Nested blocks of code should not be left empty +#pragma warning restore CA1031 // Do not catch general exception types +#pragma warning restore S2486 // Generic exceptions should not be ignored + } + + + /// + /// Setup Money Services + /// + 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); + + m_moneyXmlRpcModule = new MoneyXmlRpcModule(); + m_moneyXmlRpcModule.Initialise(m_version, m_moneyDBService, this); + m_moneyXmlRpcModule.PostInitialise(); + } + + + /// + /// Is Check Client Cert + /// + public bool IsCheckClientCert() + { + return m_checkClientCert; + } + + + /// + /// Get Server Config + /// + public IConfig GetServerConfig() + { + return m_server_config; + } + + + /// + /// Get Cert Config + /// + public IConfig GetCertConfig() + { + return m_cert_config; + } + + + /// + /// Get Http Server + /// + public BaseHttpServer GetHttpServer() + { + return m_httpServer; + } + + + /// + /// Get Session Dic + /// + public Dictionary GetSessionDic() + { + return m_sessionDic; + } + + /// + /// Get Secure Session Dic + /// + public Dictionary GetSecureSessionDic() + { + return m_secureSessionDic; + } + + + /// + /// Get Web Session Dic + /// + public Dictionary GetWebSessionDic() + { + return m_webSessionDic; + } + + } + + + + /// + /// class Money Server Config Source + /// + class MoneyServerConfigSource + { + /// + /// Ini Config Source + /// + public IniConfigSource m_config; + + /// + /// Money Server Config Source + /// + public MoneyServerConfigSource() + { + string configPath = Path.Combine(Directory.GetCurrentDirectory(), "MoneyServer.ini"); + if (File.Exists(configPath)) + { + m_config = new IniConfigSource(configPath); + } +#pragma warning disable S108 // Nested blocks of code should not be left empty + else { } +#pragma warning restore S108 // Nested blocks of code should not be left empty + } + + + /// + /// Save config + /// + public void Save(string path) + { + m_config.Save(path); + } + + } +} diff --git a/release/0.9.2/src/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs b/release/0.9.2/src/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs new file mode 100644 index 0000000..bb957cb --- /dev/null +++ b/release/0.9.2/src/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs @@ -0,0 +1,1807 @@ +/* + * 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. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Reflection; +using System.Collections; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +using log4net; +using Nini.Config; +using Nwc.XmlRpc; + +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; +using OpenSim.Modules.Currency; +using OpenSim.Region.Framework.Scenes; + +using NSL.Network.XmlRpc; +using NSL.Certificate.Tools; + + +namespace OpenSim.Grid.MoneyServer +{ + class MoneyXmlRpcModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private int m_defaultBalance = 1000; + // + private bool m_forceTransfer = false; + private string m_bankerAvatar = ""; + + private bool m_scriptSendMoney = false; + private string m_scriptAccessKey = ""; + private string m_scriptIPaddress = "127.0.0.1"; + + 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 bool m_checkServerCert = false; + private string m_cacertFilename = ""; + + private string m_certFilename = ""; + private string m_certPassword = ""; + //private X509Certificate2 m_clientCert = null; + + private string m_sslCommonName = ""; + + /// + /// For server authentication + /// + private NSLCertificateVerify m_certVerify = new NSLCertificateVerify(); + + /// + /// Update Balance Messages + /// + 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; + 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; + + + public MoneyXmlRpcModule() + { + } + + + public void Initialise(string opensimVersion, IMoneyDBService moneyDBService, IMoneyServiceCore moneyCore) + { + //m_opensimVersion = opensimVersion; + m_moneyDBService = moneyDBService; + m_moneyCore = moneyCore; + m_server_config = m_moneyCore.GetServerConfig(); // [MoneyServer] Section + m_cert_config = m_moneyCore.GetCertConfig(); // [Certificate] Section + + //////////////////////////////////////////////////////////////////////// + // [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); + + // 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_clientCert = new X509Certificate2(m_certFilename, m_certPassword); + //m_clientCert = new X509Certificate2(m_certFilename, m_certPassword, X509KeyStorageFlags.MachineKeySet); + m_log.Info("[MONEY XMLRPC]: Initialise: Issue Authentication of Client. Cert file is " + m_certFilename); + } + + // Server Authentication + // CA : MoneyServer が XMLRPC のWebサーバのサーバ証明書をチェックするための設定 + 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]: Initialise: No check XMLRPC Server or CACertFilename is empty. CheckServerCert is false."); + } + + //ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(m_certVerify.ValidateServerCertificate); + //ServicePointManager.UseNagleAlgorithm = false; + //ServicePointManager.Expect100Continue = false; + + m_sessionDic = m_moneyCore.GetSessionDic(); + m_secureSessionDic = m_moneyCore.GetSecureSessionDic(); + m_webSessionDic = m_moneyCore.GetWebSessionDic(); + RegisterHandlers(); + } + + + public void PostInitialise() + { + } + + + 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); // added + m_httpServer.AddXmlRPCHandler("MoveMoney", handleScriptTransaction); // added + + // 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_func); + m_httpServer.AddXmlRPCHandler("buyLandPrep", landBuy_func); + + // Currency Buy Test + m_httpServer.AddXmlRPCHandler("getCurrencyQuote", quote_func); + m_httpServer.AddXmlRPCHandler("buyCurrency", buy_func); + } + + + private XmlRpcResponse buy_func(XmlRpcRequest request, IPEndPoint client) + { + m_log.InfoFormat("[MONEY XMLRPC]: handleClient buyCurrency."); + throw new NotImplementedException(); + } + + + private XmlRpcResponse quote_func(XmlRpcRequest request, IPEndPoint client) + { + m_log.InfoFormat("[MONEY XMLRPC]: handleClient getCurrencyQuote."); + throw new NotImplementedException(); + } + + + private XmlRpcResponse landBuy_func(XmlRpcRequest request, IPEndPoint client) + { + m_log.InfoFormat("[MONEY XMLRPC]: handleClient buyLandPrep."); + throw new NotImplementedException(); + } + + + private XmlRpcResponse preflightBuyLandPrep_func(XmlRpcRequest request, IPEndPoint client) + { + m_log.InfoFormat("[MONEY XMLRPC]: handleClient preflightBuyLandPrep."); + throw new NotImplementedException(); + } + + + // + 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; + } + + + // + public string GetSSLCommonName() + { + return m_sslCommonName; + } + + + /// + /// Get the user balance when user entering a parcel. + /// + /// + /// + 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 { + if (commonName=="SSLCN:unknownName") { + m_log.InfoFormat("[MONEY XMLRPC]: handleClientLogin: SSL Common Name is \"{0}\", but this name is ignored.", commonName); + } + 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); + + //if (String.IsNullOrEmpty(serverURL)) { + // responseData["description"] = "Server URL is empty. Avatar is a NPC?"; + // m_log.InfoFormat("[MONEY XMLRPC]: handleClientLogon: {0}", responseData["description"]); + // return response; + //} + + // + // 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) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleClientLogout:"); + + //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; + } + + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // + /// + /// handle incoming transaction + /// + /// + /// + 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; + } + + + // + // added by Fumi.Iseki + // + /// + /// handle incoming force transaction. no check senderSessionID and senderSecureSessionID + /// + /// + /// + public XmlRpcResponse handleForceTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleForceTransaction:"); + + 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; + } + + + // + // added by Fumi.Iseki + // + /// + /// handle scripted sending money transaction. + /// + /// + /// + 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; + } + + + // + // added by Fumi.Iseki + // + /// + /// handle adding money transaction. + /// + /// + /// + public XmlRpcResponse handleAddBankerMoney(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleAddBankerMoney:"); + + 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; + } + + + // + // added by Fumi.Iseki + // + /// + /// handle pay charge transaction. no check receiver information. + /// + /// + /// + public XmlRpcResponse handlePayMoneyCharge(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handlePayMoneyCharge:"); + + 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(); + + 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); + m_log.InfoFormat("[MONEY XMLRPC]: handlePayMoneyCharge: 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]: handlePayMoneyCharge: Pay from {0}", senderID); + 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(); + + 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 message = string.Format(m_BalanceMessagePayCharge, amount, "SYSTEM", ""); + responseData["success"] = NotifyTransfer(transactionUUID, message, "", ""); + } + 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]: handlePayMoneyCharge: Pay money transaction for user {0} failed.", senderID); + } + return response; + } + catch (Exception e) { + m_log.Error("[MONEY XMLRPC]: handlePayMoneyCharge: Exception occurred while pay money transaction: " + e.ToString()); + } + return response; + } + } + + m_log.Error("[MONEY XMLRPC]: handlePayMoneyCharge: Session authentication failure for sender " + senderID); + responseData["message"] = "Session check failure, please re-login later!"; + return response; + } + + + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // + // added by Fumi.Iseki + // + /// + /// Continue transaction with no confirm. + /// + /// + /// + 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) return true; + 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; + } + + + /// + /// Get the user balance. + /// + /// + /// + public XmlRpcResponse handleGetBalance(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleGetBalance:"); + + 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; + } + + + /// + /// 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, 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_clientCert, m_checkServerCert, MONEYMODULE_REQUEST_TIMEOUT); + 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; + } + + + /// + /// Update the client balance.We don't care about the result. + /// + /// + private void UpdateBalance(string userID, string message) + { + //m_log.InfoFormat("[MONEY XMLRPC]: UpdateBalance: ID = {0}, Message = {1}", userID, message); + + string sessionID = string.Empty; + string secureID = string.Empty; + + if (m_sessionDic.ContainsKey(userID) && m_secureSessionDic.ContainsKey(userID)) { + sessionID = m_sessionDic[userID]; + secureID = m_secureSessionDic[userID]; + + Hashtable requestTable = new Hashtable(); + requestTable["clientUUID"] = userID; + requestTable["clientSessionID"] = sessionID; + requestTable["clientSecureSessionID"] = secureID; + requestTable["Balance"] = m_moneyDBService.getBalance(userID); + 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: Sended UpdateBalance Request to {0}", user.SimIP.ToString()); + } + } + } + + + /// + /// RollBack the transaction if user failed to get the object paid + /// + /// + /// + protected bool RollBackTransaction(TransactionData transaction) + { + //m_log.InfoFormat("[MONEY XMLRPC]: RollBackTransaction:"); + + 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; + } + + + // + public XmlRpcResponse handleCancelTransfer(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleCancelTransfer:"); + + 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) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleGetTransaction:"); + + 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; + } + + + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + // In development + // + + public XmlRpcResponse handleWebLogin(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleWebLogin: Start."); + + 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) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleWebLogout:"); + + 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; + } + + + /// + /// Get balance method for web pages. + /// + /// + /// + public XmlRpcResponse handleWebGetBalance(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleWebGetBalance:"); + + 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; + } + + + /// + /// Get transaction for web pages + /// + /// + /// + public XmlRpcResponse handleWebGetTransaction(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleWebGetTransaction:"); + + 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; + } + + + /// + /// Get total number of transactions for web pages. + /// + /// + /// + public XmlRpcResponse handleWebGetTransactionNum(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY XMLRPC]: handleWebGetTransactionNum:"); + + 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; + } + } + +} + diff --git a/release/0.9.2/src/OpenSim.Grid.MoneyServer/Program.cs b/release/0.9.2/src/OpenSim.Grid.MoneyServer/Program.cs new file mode 100644 index 0000000..3fbd12d --- /dev/null +++ b/release/0.9.2/src/OpenSim.Grid.MoneyServer/Program.cs @@ -0,0 +1,42 @@ +/* + * 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. + */ + +using log4net.Config; + +namespace OpenSim.Grid.MoneyServer +{ + class Program + { + public static void Main(string[] args) + { + XmlConfigurator.Configure(); + MoneyServerBase app = new MoneyServerBase(); + app.Startup(); + app.Work(); + } + } +} diff --git a/release/0.9.2/src/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs b/release/0.9.2/src/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs new file mode 100644 index 0000000..f86ff16 --- /dev/null +++ b/release/0.9.2/src/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs @@ -0,0 +1,1920 @@ +// * 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. +// */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Reflection; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +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; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +using OpenSim.Data.MySQL.MySQLMoneyDataWrapper; +using NSL.Certificate.Tools; +using NSL.Network.XmlRpc; + + + +[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 + } + + +/* + // Refer to OpenMetaverse + public enum OpenMetaverse.MoneyTransactionType : int + { + None = 0, + FailSimulatorTimeout = 1, + FailDataserverTimeout = 2, + ObjectClaim = 1000, + LandClaim = 1001, + GroupCreate = 1002, + ObjectPublicClaim = 1003, + GroupJoin = 1004, + TeleportCharge = 1100, + UploadCharge = 1101, + LandAuction = 1102, + ClassifiedCharge = 1103, + ObjectTax = 2000, + LandTax = 2001, + LightTax = 2002, + ParcelDirFee = 2003, + GroupTax = 2004, + ClassifiedRenew = 2005, + GiveInventory = 3000, + ObjectSale = 5000, + Gift = 5001, + LandSale = 5002, + ReferBonus = 5003, + InventorySale = 5004, + RefundPurchase = 5005, + LandPassSale = 5006, + DwellBonus = 5007, + PayObject = 5008, + ObjectPays = 5009, + GroupLandDeed = 6001, + GroupObjectDeed = 6002, + GroupLiability = 6003, + GroupDividend = 6004, + GroupMembershipDues = 6005, + ObjectRelease = 8000, + LandRelease = 8001, + ObjectDelete = 8002, + ObjectPublicDecay = 8003, + ObjectPublicDelete = 8004, + LindenAdjustment = 9000, + LindenGrant = 9001, + LindenPenalty = 9002, + EventFee = 9003, + EventPrize = 9004, + StipendBasic = 10000, + StipendDeveloper = 10001, + StipendAlways = 10002, + StipendDaily = 10003, + StipendRating = 10004, + StipendDelta = 10005 + } +*/ + + + + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "DTLNSLMoneyModule")] + public class DTLNSLMoneyModule : IMoneyModule, ISharedRegionModule + { + #region Constant numbers and members. + + // 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 X509Certificate2 m_cert = null; + + 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 + + + /// + /// Scene dictionary indexed by Region Handle + /// + private Dictionary m_sceneList = new Dictionary(); + + /// + /// To cache the balance data while the money server is not available. + /// + 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; + + #endregion + + + /// + /// Initialise + /// + /// + /// + public void Initialise(Scene scene, IConfigSource source) + { + Initialise(source); + if (string.IsNullOrEmpty(m_moneyServURL)) m_enable_server = false; + // + AddRegion(scene); + } + + + #region ISharedRegionModule interface + + public void Initialise(IConfigSource source) + { + //m_log.InfoFormat("[MONEY MODULE]: Initialise:"); + + // Handle the parameters errors. + if (source==null) return; + + try { + m_config = source; + + // [Economy] section + IConfig economyConfig = m_config.Configs["Economy"]; + + if (economyConfig.GetString("EconomyModule")!=Name && economyConfig.GetString("economymodule")!=Name) { + //m_enabled = false; + m_log.InfoFormat("[MONEY MODULE]: Initialise: The DTL/NSL MoneyModule is disabled"); + return; + } + else { + m_log.InfoFormat("[MONEY MODULE]: Initialise: The DTL/NSL MoneyModule is enabled"); + } + + m_sellEnabled = economyConfig.GetBoolean("SellEnabled", m_sellEnabled); + m_moneyServURL = economyConfig.GetString("CurrencyServer", m_moneyServURL); + + // Client Certification // クライアント証明書 + m_certFilename = economyConfig.GetString("ClientCertFilename", m_certFilename); + m_certPassword = economyConfig.GetString("ClientCertPassword", m_certPassword); + if (m_certFilename!="") { + m_certVerify.SetPrivateCert(m_certFilename, m_certPassword); + //m_cert = new X509Certificate2(m_certFilename, m_certPassword); + //m_cert = new X509Certificate2(m_certFilename, m_certPassword, X509KeyStorageFlags.MachineKeySet); + m_log.Info("[MONEY MODULE]: Initialise: Issue Authentication of Client. Cert File is " + m_certFilename); + } + + // Server Authentication // MoneyServer のサーバ証明書のチェック + m_checkServerCert = economyConfig.GetBoolean("CheckServerCert", m_checkServerCert); + m_cacertFilename = economyConfig.GetString ("CACertFilename", m_cacertFilename); + + if (m_cacertFilename != "") { + m_certVerify.SetPrivateCA(m_cacertFilename); + } + else { + m_checkServerCert = false; + } + + if (m_checkServerCert) { + m_log.Info("[MONEY MODULE]: Initialise: Execute Authentication of Server. CA Cert File is " + m_cacertFilename); + } + else { + m_log.Info("[MONEY MODULE]: Initialise: No check Money Server or CACertFilename is empty. CheckServerCert is false."); + } + + //ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(m_certVerify.ValidateServerCertificate); + //ServicePointManager.UseNagleAlgorithm = false; + //ServicePointManager.Expect100Continue = false; + + // Settlement + m_use_web_settle = economyConfig.GetBoolean("SettlementByWeb", m_use_web_settle); + m_settle_url = economyConfig.GetString ("SettlementURL", m_settle_url); + m_settle_message = economyConfig.GetString ("SettlementMessage", m_settle_message); + + // Price + 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); + PriceGroupCreate = economyConfig.GetInt ("PriceGroupCreate", PriceGroupCreate); + TeleportMinPrice = economyConfig.GetInt ("TeleportMinPrice", TeleportMinPrice); + TeleportPriceExponent = economyConfig.GetFloat("TeleportPriceExponent", TeleportPriceExponent); + EnergyEfficiency = economyConfig.GetFloat("EnergyEfficiency", EnergyEfficiency); + + // for HG Avatar + string avatar_class = economyConfig.GetString("HGAvatarAs", "HGAvatar").ToLower(); + if (avatar_class=="localavatar") m_hg_avatarClass = (int)AvatarType.LOCAL_AVATAR; + else if (avatar_class=="guestavatar") m_hg_avatarClass = (int)AvatarType.GUEST_AVATAR; + else if (avatar_class=="hgavatar") m_hg_avatarClass = (int)AvatarType.HG_AVATAR; + else if (avatar_class=="foreignavatar") m_hg_avatarClass = (int)AvatarType.FOREIGN_AVATAR; + else m_hg_avatarClass = (int)AvatarType.UNKNOWN_AVATAR; + + } + catch { + m_log.ErrorFormat("[MONEY MODULE]: Initialise: Faile to read configuration file"); + } + } + + + public void AddRegion(Scene scene) + { + //m_log.InfoFormat("[MONEY MODULE]: AddRegion:"); + + if (scene==null) return; + + scene.RegisterModuleInterface(this); // 競合するモジュールの排除 + + 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); // added + HttpServer.AddXmlRPCHandler("AddBankerMoney", AddBankerMoneyHandler); // added + HttpServer.AddXmlRPCHandler("SendMoney", SendMoneyHandler); // added + HttpServer.AddXmlRPCHandler("MoveMoney", MoveMoneyHandler); // added + + MainServer.Instance.AddXmlRPCHandler("OnMoneyTransfered", OnMoneyTransferedHandler); + MainServer.Instance.AddXmlRPCHandler("UpdateBalance", BalanceUpdateHandler); + MainServer.Instance.AddXmlRPCHandler("UserAlert", UserAlertHandler); + MainServer.Instance.AddXmlRPCHandler("GetBalance", GetBalanceHandler); // added + MainServer.Instance.AddXmlRPCHandler("AddBankerMoney", AddBankerMoneyHandler); // added + MainServer.Instance.AddXmlRPCHandler("SendMoney", SendMoneyHandler); // added + MainServer.Instance.AddXmlRPCHandler("MoveMoney", MoveMoneyHandler); // added + } + } + + 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; + } + + + 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; + } + } + + + public void RegionLoaded(Scene scene) + { + //m_log.InfoFormat("[MONEY MODULE]: RegionLoaded:"); + } + + + public Type ReplaceableInterface + { + //get { return typeof(IMoneyModule); } + get { return null; } + } + + + public bool IsSharedModule + { + get { return true; } + } + + + public string Name + { + get { return "DTLNSLMoneyModule"; } + } + + + public void PostInitialise() + { + //m_log.InfoFormat("[MONEY MODULE]: PostInitialise:"); + } + + + public void Close() + { + //m_log.InfoFormat("[MONEY MODULE]: Close:"); + } + + #endregion + + + #region IMoneyModule interface. + + // for LSL llGiveMoney() function + public bool ObjectGiveMoney(UUID objectID, UUID fromID, UUID toID, int amount, UUID txn, out string result) + { + //m_log.InfoFormat("[MONEY MODULE]: ObjectGiveMoney: LSL ObjectGiveMoney. UUID = {0}", objectID.ToString()); + + 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"; + return ret; + } + + + // + public int UploadCharge + { + get { return PriceUpload; } + } + + + // + public int GroupCreationCharge + { + get { return PriceGroupCreate; } + } + + + public int GetBalance(UUID agentID) + { + IClientAPI client = GetLocateClient(agentID); + return QueryBalanceFromMoneyServer(client); + } + + + 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; + } + return false; + } + + + 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; + } + return false; + } + + + 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); + } + + + public void ApplyCharge(UUID agentID, int amount, MoneyTransactionType type) + { + ApplyCharge(agentID, amount, type, string.Empty); + } + + + 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); + } + + + 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); + } + + + 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 + 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); + } + + // for 0.9.1 over + 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); + return ret; + } + + #endregion + + + #region MoneyModule event handlers + + // + private void OnNewClient(IClientAPI client) + { + //m_log.InfoFormat("[MONEY MODULE]: OnNewClient"); + + client.OnEconomyDataRequest += OnEconomyDataRequest; + client.OnLogout += ClientClosed; + + client.OnMoneyBalanceRequest += OnMoneyBalanceRequest; + client.OnRequestPayPrice += OnRequestPayPrice; + client.OnObjectBuy += OnObjectBuy; + } + + + public void OnMakeRootAgent(ScenePresence agent) + { + //m_log.InfoFormat("[MONEY MODULE]: OnMakeRootAgent:"); + + 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); + + //client.OnMoneyBalanceRequest += OnMoneyBalanceRequest; + //client.OnRequestPayPrice += OnRequestPayPrice; + //client.OnObjectBuy += OnObjectBuy; + } + + + // for OnClientClosed event + private void ClientClosed(IClientAPI client) + { + //m_log.InfoFormat("[MONEY MODULE]: ClientClosed:"); + + if (m_enable_server && client!=null) { + LogoffMoneyServer(client); + } + } + + + // for OnMakeChildAgent event + private void MakeChildAgent(ScenePresence avatar) + { + //m_log.InfoFormat("[MONEY MODULE]: MakeChildAgent:"); + } + + + // for OnMoneyTransfer event + private void MoneyTransferAction(Object sender, EventManager.MoneyTransferArgs moneyEvent) + { + //m_log.InfoFormat("[MONEY MODULE]: MoneyTransferAction: type = {0}", moneyEvent.transactiontype); + + 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 + private void ValidateLandBuy(Object sender, EventManager.LandBuyArgs landBuyEvent) + { + //m_log.InfoFormat("[MONEY MODULE]: ValidateLandBuy:"); + + 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 + private void processLandBuy(Object sender, EventManager.LandBuyArgs landBuyEvent) + { + //m_log.InfoFormat("[MONEY MODULE]: processLandBuy:"); + + 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 + 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(); + 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); + 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"); + } + } + + + 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) + private void OnEconomyDataRequest(IClientAPI user) + { + //m_log.InfoFormat("[MONEY MODULE]: OnEconomyDataRequest:"); + //IClientAPI user = GetLocateClient(agentId); + + 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); + } + } + } + + #endregion + + + #region MoneyModule XML-RPC Handler + + // "OnMoneyTransfered" RPC from MoneyServer + 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 + public XmlRpcResponse BalanceUpdateHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY MODULE]: BalanceUpdateHandler:"); + + bool ret = false; + + #region Update the balance from money server. + + 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; + } + } + } + } + } + + #endregion + + // 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 + public XmlRpcResponse UserAlertHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY MODULE]: UserAlertHandler:"); + + bool ret = false; + + #region confirm the request and show the notice from money server. + + 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; + } + } + } + } + } + // + #endregion + + // 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 + public XmlRpcResponse GetBalanceHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY MODULE]: GetBalanceHandler:"); + + 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 + 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 + public XmlRpcResponse SendMoneyHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY MODULE]: SendMoneyHandler:"); + + 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 + public XmlRpcResponse MoveMoneyHandler(XmlRpcRequest request, IPEndPoint remoteClient) + { + //m_log.InfoFormat("[MONEY MODULE]: MoveMoneyHandler:"); + + 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; + } + + #endregion + + + #region MoneyModule private help functions + + /// + /// 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) + { + //m_log.InfoFormat("[MONEY MODULE]: TransferMoney:"); + + 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) + /// 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) + { + //m_log.InfoFormat("[MONEY MODULE]: ForceTransferMoney:"); + + bool ret = false; + + #region Force send transaction request to money server and parse the resultes. + + 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()); + } + //else m_log.ErrorFormat("[MONEY MODULE]: ForceTransferMoney: Money Server is not available!!"); + + #endregion + + 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) + { + //m_log.InfoFormat("[MONEY MODULE]: SendMoneyTo:"); + + 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"); + } + //else m_log.ErrorFormat("[MONEY MODULE]: SendMoneyTo: Money Server is not available!!"); + + 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) + { + //m_log.InfoFormat("[MONEY MODULE]: MoveMoneyFromTo:"); + + 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"); + } + //else m_log.ErrorFormat("[MONEY MODULE]: MoveMoneyFromTo: Money Server is not available!!"); + + 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) + { + //m_log.InfoFormat("[MONEY MODULE]: AddBankerMoney:"); + + 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"); + } + //else m_log.ErrorFormat("[MONEY MODULE]: AddBankerMoney: Money Server is not available!!"); + + 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) + { + //m_log.InfoFormat("[MONEY MODULE]: PayMoneyCharge:"); + + 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) + /// 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) + { + //m_log.InfoFormat("[MONEY MODULE]: LoginMoneyServer:"); + + balance = 0; + bool ret = false; + bool isNpc = avatar.IsNPC; + + IClientAPI client = avatar.ControllingClient; + + #region Send money server the client info for login. + + 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; + } + } + } + + ////////////////////////////////////////////////////////////// + // 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; + } + + // + 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!! Server URL is Null or empty!!"); + + #endregion + + // Viewerへ設定を通知する. + 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) + { + //m_log.InfoFormat("[MONEY MODULE]: LogoffMoneyServer:"); + + 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; + } + } + } + + return ret; + } + + + // + private EventManager.MoneyTransferArgs GetTransactionInfo(IClientAPI client, string transactionID) + { + //m_log.InfoFormat("[MONEY MODULE]: GetTransactionInfo:"); + + 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) + { + //m_log.InfoFormat("[MONEY MODULE]: genericCurrencyXMLRPCRequest:"); + + 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; + } + } + } + } + } + + return client; + } + + + 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; + } + } + } + } + + return scene; + } + + + 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; + } + + #endregion + } + +} diff --git a/release/0.9.2/src/OpenSim.Modules.Currency/NSLCertificateTools.cs b/release/0.9.2/src/OpenSim.Modules.Currency/NSLCertificateTools.cs new file mode 100644 index 0000000..b0c3034 --- /dev/null +++ b/release/0.9.2/src/OpenSim.Modules.Currency/NSLCertificateTools.cs @@ -0,0 +1,291 @@ +/* + * Copyright (c) Contributors, http://www.nsl.tuis.ac.jp + * + */ + +#pragma warning disable S1128 // Unused "using" should be removed +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using System.Net; +using System.Net.Security; +using System.Text; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using log4net; +#pragma warning restore S1128 // Unused "using" should be removed + + +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; + } + + + /// + /// NSL Certificate Verify + /// + /// + public NSLCertificateVerify(string certfile) + { + SetPrivateCA(certfile); + } + + + /// + /// NSL Certificate Verify + /// + /// + /// + public NSLCertificateVerify(string certfile, string crlfile) + { + SetPrivateCA (certfile); + SetPrivateCRL(crlfile); + } + + + /// + /// 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; + } + } + + + // + 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 + /// 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); + + /* + // RemoteCertificateNotAvailableはエラーとする. + if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateNotAvailable)==SslPolicyErrors.RemoteCertificateNotAvailable) { + m_log.InfoFormat("[NSL SERVER CERT VERIFY]: ValidateServerCertificate: Policy Error! {0}", sslPolicyErrors); + return false; + } + */ + + // None, ChainErrors Error except for. // None, ChainErrors 以外は全てエラーとする. + 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 以外は全てエラーとする. + 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; + } + } + + +/* + /// + /// class NSL Certificate Policy + /// + public class NSLCertificatePolicy : ICertificatePolicy + { + /// + /// Check Validation Result + /// + /// + /// + /// + /// + /// + public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) + { + if (certificateProblem == 0 || // normal // 正常 + certificateProblem == -2146762487 || // Not trusted // 信頼されてない + certificateProblem == -2146762495 || // Expired // 期限切れ + certificateProblem == -2146762481) { // Incorrect name // 名前不正 + return true; + } + else { + return false; + } + } + } +*/ +} diff --git a/release/0.9.2/src/OpenSim.Modules.Currency/NSLXmlRpc.cs b/release/0.9.2/src/OpenSim.Modules.Currency/NSLXmlRpc.cs new file mode 100644 index 0000000..025236a --- /dev/null +++ b/release/0.9.2/src/OpenSim.Modules.Currency/NSLXmlRpc.cs @@ -0,0 +1,115 @@ +/* + * Copyright (c) Contributors, http://www.nsl.tuis.ac.jp + * + */ + + +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); + + private Encoding _encoding = new UTF8Encoding(); + private XmlRpcRequestSerializer _serializer = new XmlRpcRequestSerializer(); + private XmlRpcResponseDeserializer _deserializer = new XmlRpcResponseDeserializer(); + + + public NSLXmlRpcRequest() + { + _params = new ArrayList(); + } + + + public NSLXmlRpcRequest(String methodName, IList parameters) + { + MethodName = methodName; + _params = parameters; + } + + + //public XmlRpcResponse certSend(String url, X509Certificate2 myClientCert, bool checkServerCert, Int32 timeout) + public XmlRpcResponse certSend(String url, NSLCertificateVerify certVerify, bool checkServerCert, Int32 timeout) + { + m_log.InfoFormat("[MONEY NSL XMLRPC]: XmlRpcResponse certSend: connect to {0}", url); + +//#pragma warning disable SYSLIB0014 // Use HttpClient instead of WebRequest.Create. + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); +//#pragma warning restore SYSLIB0014 + if (request==null) { + throw new XmlRpcException(XmlRpcErrorCodes.TRANSPORT_ERROR, XmlRpcErrorCodes.TRANSPORT_ERROR_MSG +": Could not create request with " + url); + } + + X509Certificate2 clientCert = null; + + request.Method = "POST"; + request.ContentType = "text/xml"; + request.AllowWriteStreamBuffering = true; + request.Timeout = timeout; + request.UserAgent = "NSLXmlRpcRequest"; + + if (certVerify != null) { + clientCert = certVerify.GetPrivateCert(); + if (clientCert != null) request.ClientCertificates.Add(clientCert); // Own certificate // 自身の証明書 + request.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(certVerify.ValidateServerCertificate); + } + else { + checkServerCert = false; + } + // + if (!checkServerCert) { + request.Headers.Add("NoVerifyCert", "true"); // Do not verify the certificate of the other party // 相手の証明書を検証しない + } + + // + Stream stream = null; + try { + stream = request.GetRequestStream(); + } +#pragma warning disable CS0168 + catch (Exception ex) { +#pragma warning restore CS0168 + m_log.ErrorFormat("[MONEY NSL XMLRPC]: GetRequestStream Error: {0}", ex); + stream = null; + } + if (stream==null) return null; + + // + XmlTextWriter xml = new XmlTextWriter(stream, _encoding); + _serializer.Serialize(xml, this); + xml.Flush(); + xml.Close(); + + HttpWebResponse response = null; + try { + response = (HttpWebResponse)request.GetResponse(); + } + catch (Exception ex) { + m_log.ErrorFormat("[MONEY NSL XMLRPC]: XmlRpcResponse certSend: GetResponse Error: {0}", ex.ToString()); + } + StreamReader input = new StreamReader(response.GetResponseStream()); + + string inputXml = input.ReadToEnd(); + XmlRpcResponse resp = (XmlRpcResponse)_deserializer.Deserialize(inputXml); + + input.Close(); + response.Close(); + return resp; + } + } +} diff --git a/runprebuild.bat b/runprebuild.bat new file mode 100755 index 0000000..603267f --- /dev/null +++ b/runprebuild.bat @@ -0,0 +1,61 @@ +@echo OFF + +..\bin\Prebuild.exe /target vs2015 + +setlocal ENABLEEXTENSIONS +set VALUE_NAME=MSBuildToolsPath + +if "%PROCESSOR_ARCHITECTURE%"=="x86" set PROGRAMS=%ProgramFiles% +if defined ProgramFiles(x86) set PROGRAMS=%ProgramFiles(x86)% + +rem Try to find VS2019 +for %%e in (Enterprise Professional Community) do ( + if exist "%PROGRAMS%\Microsoft Visual Studio\2019\%%e\MSBuild\Current\Bin\MSBuild.exe" ( + + set ValueValue="%PROGRAMS%\Microsoft Visual Studio\2019\%%e\MSBuild\Current\Bin\MSBuild" + goto :found + ) +) + +rem try find vs2017 +for %%e in (Enterprise Professional Community) do ( + if exist "%PROGRAMS%\Microsoft Visual Studio\2017\%%e\MSBuild\15.0\Bin\MSBuild.exe" ( + + set ValueValue="%PROGRAMS%\Microsoft Visual Studio\2017\%%e\MSBuild\15.0\Bin\MSBuild" + goto :found + ) +) + +rem We have to use grep or find to locate the correct line, because reg query spits +rem out 4 lines before Windows 7 but 2 lines after Windows 7. +rem We use grep if it's on the path; otherwise we use the built-in find command +rem from Windows. (We must use grep on Cygwin because it overrides the "find" command.) + +for %%X in (grep.exe) do (set FOUNDGREP=%%~$PATH:X) +if defined FOUNDGREP ( + set FINDCMD=grep +) else ( + set FINDCMD=find +) + +rem try vs2015 +FOR /F "usebackq tokens=1-3" %%A IN (`REG QUERY "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSBuild\ToolsVersions\14.0" /v %VALUE_NAME% 2^>nul ^| %FINDCMD% "%VALUE_NAME%"`) DO ( + set ValueValue=%%C\msbuild + goto :found +) + +@echo msbuild for at least VS2015 not found, please install a (Community) edition of VS2017 or VS2015 +@echo Not creating compile.bat +if exist "compile.bat" ( + del compile.bat + ) +goto :done + +:found + @echo Found msbuild at %ValueValue% + @echo Creating compile.bat +rem To compile in debug mode + @echo %ValueValue% opensim.sln > compile.bat +rem To compile in release mode comment line (add rem to start) above and uncomment next (remove rem) +rem @echo %ValueValue% /P:Config=Release opensim.sln > compile.bat +:done diff --git a/runprebuild.net.bat b/runprebuild.net.bat new file mode 100755 index 0000000..162d76b --- /dev/null +++ b/runprebuild.net.bat @@ -0,0 +1,18 @@ +@echo OFF + +dotnet ..\bin\prebuild.dll /target vs2022 /targetframework net6_0 /excludedir = "obj | bin" /file prebuild.net.xml + + @echo Creating compile.bat +rem To compile in debug mode + @echo dotnet build --configuration Release OpenSim.Currency.sln > compile.bat +rem To compile in release mode comment line (add rem to start) above and uncomment next (remove rem) +rem @echo dotnet build --configuration Debug OpenSim.sln > compile.bat +:done +if exist "..\bin\addin-db-002" ( + del /F/Q/S ..\bin\addin-db-002 > NUL + rmdir /Q/S ..\bin\addin-db-002 + ) +if exist "..\bin\addin-db-004" ( + del /F/Q/S ..\bin\addin-db-004 > NUL + rmdir /Q/S ..\bin\addin-db-004 + ) diff --git a/runprebuild.net.sh b/runprebuild.net.sh new file mode 100755 index 0000000..d80ef02 --- /dev/null +++ b/runprebuild.net.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# +echo y | dotnet ../bin/prebuild.dll /file prebuild.net.xml /clean + +dotnet ../bin/prebuild.dll /target vs2022 /targetframework net6_0 /excludedir = "obj | bin" /file prebuild.net.xml + diff --git a/runprebuild.sh b/runprebuild.sh new file mode 100755 index 0000000..086f7e9 --- /dev/null +++ b/runprebuild.sh @@ -0,0 +1,4 @@ +#mono ../bin/Prebuild.exe /target vs2008 /targetframework v3_5 /conditionals NET_3_5 +#mono ../bin/Prebuild.exe /target vs2010 +mono ../bin/Prebuild.exe /target vs2015 +