diff --git a/README.md b/README.md index daf46cf..f6772cd 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,71 @@ -# OpenSim-DTL-NSL-Currency-2020-2 -OpenSim-DTL-NSL-Currency-2020-2 +# OpenSim-DTL-NSL-Currency-2020 +DTL/NSL MoneyServer 2020 für OpenSim 0.9.2.0 + +NPC und Hypergrid Avatare funktioniert auch mit dieser DTL-NSL-Currency Version. + +Ab der Version opensim-0.9.2.0Dev-90-g8d8b5b7 braucht der OpenSimulator für den MoneyServer nicht mehr gepatcht werden. + +Dieses Geld System ist nur zur unterhaltung geeignet und ist nicht sicher. + +Verzeichnisstruktur + + binary = Binäre ausführbare Dateien falls sie nicht selber kompilieren können oder wollen. + helper-php = helper Dateien die benötigt werden (helper.php, currency.php, landtool.php). + source = Quelldateien für den OpenSimulator die zum kompilieren benötigt werden. + +# English +DTL/NSL MoneyServer 2020 for OpenSim 0.9.2.0 + +NPC and Hypergrid avatars also work with this DTL-NSL-Currency version. + +As of version opensim-0.9.2.0Dev-90-g8d8b5b7, the OpenSimulator for the MoneyServer no longer needs to be patched. + +This money system is for entertainment purposes only and is not secure. + +directory structure + + binary = binary executable files if you cannot or do not want to compile yourself. + helper-php = helper files needed (helper.php, currency.php, landtool.php). + source = source files needed to compile. + + +# Download +Fertige opensim 0.9.2.45 money Dev Version inclusive DTL-NSL-Currency + +Diese Version läuft mit oder ohne Money, sowie ist sie für Standalone und Grid. + +Finished opensim 0.9.2.45 money Dev version including DTL-NSL-Currency + +This version runs with or without money, as well as for standalone and grid. + +Download: https://www.mediafire.com/file/vvapjo9c7gvbl7l/opensim092-45-money.zip/file + +# Wiki +click Wiki at the top of the page. + +# Lizenz +MIT License + +# Ausschluss aller Gewährleistungen: +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 USERS ``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. diff --git a/binary/MoneyServer.exe b/binary/MoneyServer.exe new file mode 100644 index 0000000..e4b51c5 Binary files /dev/null and b/binary/MoneyServer.exe differ diff --git a/binary/MoneyServer.exe.config b/binary/MoneyServer.exe.config new file mode 100644 index 0000000..1f31302 --- /dev/null +++ b/binary/MoneyServer.exe.config @@ -0,0 +1,29 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/binary/MoneyServer.ini b/binary/MoneyServer.ini new file mode 100644 index 0000000..f439360 --- /dev/null +++ b/binary/MoneyServer.ini @@ -0,0 +1,99 @@ +[Startup] +; +; Place to create a PID file +; PIDFile = "/tmp/money.pid" + + +[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 = 50 + + +[MoneyServer] +; +; 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 = true + +; +; 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 = true +EnableGuestAvatar = true +HGAvatarDefaultBalance = 1000 +GuestAvatarDefaultBalance = 1000 + +; +; 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/binary/OpenSim.Data.MySQL.MySQLMoneyDataWrapper.dll b/binary/OpenSim.Data.MySQL.MySQLMoneyDataWrapper.dll new file mode 100644 index 0000000..a272c30 Binary files /dev/null and b/binary/OpenSim.Data.MySQL.MySQLMoneyDataWrapper.dll differ diff --git a/binary/OpenSim.Modules.Currency.dll b/binary/OpenSim.Modules.Currency.dll new file mode 100644 index 0000000..b86d504 Binary files /dev/null and b/binary/OpenSim.Modules.Currency.dll differ diff --git a/binary/OpenSim.ini.sample b/binary/OpenSim.ini.sample new file mode 100644 index 0000000..c932f78 --- /dev/null +++ b/binary/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 = 0 + + ;; Mesh upload factors + ;MeshModelUploadCostFactor = 0.0 + ;MeshModelUploadTextureCostFactor = 1.0 + ;MeshModelMinCostFactor = 0.0 + + ;; Money Unit fee to create groups. Default is 0. + PriceGroupCreate = 0 + + ; + ;; 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/binary/README.md b/binary/README.md new file mode 100644 index 0000000..a72025a --- /dev/null +++ b/binary/README.md @@ -0,0 +1,3 @@ +# Compiled with Ubuntu 19.04 Server OpenSim 0.9.2 mono 6.x + +Copy all to /opensim/bin \ No newline at end of file diff --git a/binary/SineWaveCert.pfx b/binary/SineWaveCert.pfx new file mode 100644 index 0000000..11c8ae5 Binary files /dev/null and b/binary/SineWaveCert.pfx differ diff --git a/binary/server_cert.p12 b/binary/server_cert.p12 new file mode 100644 index 0000000..16adb01 Binary files /dev/null and b/binary/server_cert.p12 differ diff --git a/commands.txt b/commands.txt new file mode 100644 index 0000000..b02b88e --- /dev/null +++ b/commands.txt @@ -0,0 +1,59 @@ +command-script diff --git a/helper-php/DTLNSL_helper_scripts/helper/landtool.php b/helper-php/DTLNSL_helper_scripts/helper/landtool.php new file mode 100644 index 0000000..22067bc --- /dev/null +++ b/helper-php/DTLNSL_helper_scripts/helper/landtool.php @@ -0,0 +1,171 @@ + array('id' => "00000000-0000-0000-0000-000000000000", 'description' => "some level")); + $landUse = array('upgrade' => False, 'action' => "".SYSURL.""); + $currency = array('estimatedCost' => convert_to_real($amount)); + $membership = array('upgrade' => False, 'action' => "".SYSURL."", 'levels' => $membership_levels); + $response_xml = xmlrpc_encode(array('success' => True, + 'currency' => $currency, + 'membership'=> $membership, + 'landUse' => $landUse, + 'currency' => $currency, + 'confirm' => $confirmvalue)); + } + else { + $response_xml = xmlrpc_encode(array( 'success' => False, + 'errorMessage' => "Unable to Authenticate\n\nClick URL for more info.", + 'errorURI' => "".SYSURL."")); + } + + header("Content-type: text/xml"); + echo $response_xml; + + return ""; +} + + +# +# Perform the buy (所持金が足りないとき) +# +xmlrpc_server_register_method($xmlrpc_server, "buyLandPrep", "buy_land"); + +function buy_land($method_name, $params, $app_data) +{ + $req = $params[0]; + $agentid = $req['agentId']; + $secureid = $req['secureSessionId']; + $amount = $req['currencyBuy']; + $cost = $req['estimatedCost']; + $billableArea = $req['billableArea']; + $confim = $req['confirm']; + $ipAddress = $_SERVER['REMOTE_ADDR']; + + // + if ($confim!=get_confirm_value($ipAddress)) { + $response_xml = xmlrpc_encode(array('success' => False, + 'errorMessage'=> "\n\nMissmatch Confirm Value!!", + 'errorURI' => "".SYSURL."")); + header("Content-type: text/xml"); + echo $response_xml; + return ""; + } + + $ret = opensim_check_secure_session($agentid, null, $secureid); + + if ($ret) { + if($amount>=0) { + if (!$cost) $cost = convert_to_real($amount); + if(!process_transaction($agentid, $cost, $ipAddress)) { + $response_xml = xmlrpc_encode(array( + 'success' => False, + 'errorMessage' => "\n\nThe gateway has declined your transaction. Please update your payment method AND try again later.", + 'errorURI' => "".SYSURL."")); + } + // + $enough_money = false; + $res = add_money($agentid, $amount, $secureid); + if ($res["success"]) $enough_money = true; + + if ($enough_money) { + $amount += get_balance($agentid); + move_money($agentid, null, $amount, 5002, 0, "Land Purchase", 0, 0, $ipAddress); + update_simulator_balance($agentid, -1, $secureid); + $response_xml = xmlrpc_encode(array('success' => True)); + } + else { + $response_xml = xmlrpc_encode(array('success' => False, + 'errorMessage'=> "\n\nYou do not have sufficient funds for this purchase", + 'errorURI' => "".SYSURL."")); + } + } + } + else { + $response_xml = xmlrpc_encode(array('success' => False, + 'errorMessage' => "\n\nUnable to Authenticate\n\nClick URL for more info.", + 'errorURI' => "".SYSURL."")); + } + + header("Content-type: text/xml"); + echo $response_xml; + + return ""; +} + + + +# +# Process XMLRPC request +# +if (!isset($HTTP_RAW_POST_DATA)) $HTTP_RAW_POST_DATA = file_get_contents('php://input'); +$request_xml = $HTTP_RAW_POST_DATA; +//error_log("landtool.php: ".$request_xml); + +xmlrpc_server_call_method($xmlrpc_server, $request_xml, ''); +xmlrpc_server_destroy($xmlrpc_server); diff --git a/helper-php/DTLNSL_helper_scripts/helper/offline.php b/helper-php/DTLNSL_helper_scripts/helper/offline.php new file mode 100644 index 0000000..812c711 --- /dev/null +++ b/helper-php/DTLNSL_helper_scripts/helper/offline.php @@ -0,0 +1,78 @@ +"); + + if ($start!=-1) { + $start+=2; + $msg = substr($msg, $start); + $parts = preg_split("/[<>]/", $msg); + $from_agent = $parts[4]; + $to_agent = $parts[12]; + + if (isGUID($from_agent) and isGUID($to_agent)) { + + $esc_msg = $DbLink->escape($msg); + $query_str = "INSERT INTO ".OFFLINE_MESSAGE_TBL." (to_uuid,from_uuid,message) VALUES ('".$to_agent."','".$from_agent."','".$esc_msg."')"; + $DbLink->query($query_str); + + if ($DbLink->Errno==0) { + echo 'true'; + exit; + } + } + } + + echo 'false'; + exit; +} + +if ($method == '/RetrieveMessages/') { + $parms = $HTTP_RAW_POST_DATA; + $parts = preg_split("/[<>]/", $parms); + $agent_id = $parts[6]; + $errno = -1; + + if (isGUID($agent_id)) { + $DbLink->query("SELECT message FROM ".OFFLINE_MESSAGE_TBL." WHERE to_uuid='".$agent_id."'"); + $errno = $DbLink->Errno; + } + + echo ''; + echo ''; + + if ($errno==0) { + while(list($message) = $DbLink->next_record()) { + echo $message; + } + } + echo ''; + + if ($errno==0) { + $DbLink->query("DELETE FROM ".OFFLINE_MESSAGE_TBL." WHERE to_uuid='".$agent_id."'"); + } + exit; +} diff --git a/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/compat/array_key_exists.php b/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/compat/array_key_exists.php new file mode 100644 index 0000000..4a93ce4 --- /dev/null +++ b/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/compat/array_key_exists.php @@ -0,0 +1,55 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: array_key_exists.php 2 2009-03-16 20:22:51Z ggiunta $ + + +/** + * Replace array_key_exists() + * + * @category PHP + * @package PHP_Compat + * @link http://php.net/function.array_key_exists + * @author Aidan Lister + * @version $Revision: 1.1 $ + * @since PHP 4.1.0 + * @require PHP 4.0.0 (user_error) + */ +if (!function_exists('array_key_exists')) { + function array_key_exists($key, $search) + { + if (!is_scalar($key)) { + user_error('array_key_exists() The first argument should be either a string or an integer', + E_USER_WARNING); + return false; + } + + if (is_object($search)) { + $search = get_object_vars($search); + } + + if (!is_array($search)) { + user_error('array_key_exists() The second argument should be either an array or an object', + E_USER_WARNING); + return false; + } + + return in_array($key, array_keys($search)); + } +} + +?> \ No newline at end of file diff --git a/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/compat/is_a.php b/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/compat/is_a.php new file mode 100644 index 0000000..2c5c129 --- /dev/null +++ b/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/compat/is_a.php @@ -0,0 +1,47 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: is_a.php 2 2009-03-16 20:22:51Z ggiunta $ + + +/** + * Replace function is_a() + * + * @category PHP + * @package PHP_Compat + * @link http://php.net/function.is_a + * @author Aidan Lister + * @version $Revision: 1.2 $ + * @since PHP 4.2.0 + * @require PHP 4.0.0 (user_error) (is_subclass_of) + */ +if (!function_exists('is_a')) { + function is_a($object, $class) + { + if (!is_object($object)) { + return false; + } + + if (get_class($object) == strtolower($class)) { + return true; + } else { + return is_subclass_of($object, $class); + } + } +} + +?> \ No newline at end of file diff --git a/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/compat/is_callable.php b/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/compat/is_callable.php new file mode 100644 index 0000000..419697a --- /dev/null +++ b/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/compat/is_callable.php @@ -0,0 +1,53 @@ + + * @version $Id: is_callable.php 2 2009-03-16 20:22:51Z ggiunta $ + * @since PHP 4.0.6 + * @require PHP 4.0.0 (true, false, etc...) + * @todo add the 3rd parameter syntax... + */ +if (!function_exists('is_callable')) { + function is_callable($var, $syntax_only=false) + { + if ($syntax_only) + { + /* from The Manual: + * If the syntax_only argument is TRUE the function only verifies + * that var might be a function or method. It will only reject simple + * variables that are not strings, or an array that does not have a + * valid structure to be used as a callback. The valid ones are + * supposed to have only 2 entries, the first of which is an object + * or a string, and the second a string + */ + return (is_string($var) || (is_array($var) && count($var) == 2 && is_string(end($var)) && (is_string(reset($var)) || is_object(reset($var))))); + } + else + { + if (is_string($var)) + { + return function_exists($var); + } + else if (is_array($var) && count($var) == 2 && is_string($method = end($var))) + { + $obj = reset($var); + if (is_string($obj)) + { + $methods = get_class_methods($obj); + return (bool)(is_array($methods) && in_array(strtolower($method), $methods)); + } + else if (is_object($obj)) + { + return method_exists($obj, $method); + } + } + return false; + } + } +} + +?> \ No newline at end of file diff --git a/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/compat/is_scalar.php b/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/compat/is_scalar.php new file mode 100644 index 0000000..dd87730 --- /dev/null +++ b/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/compat/is_scalar.php @@ -0,0 +1,38 @@ + \ No newline at end of file diff --git a/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/compat/var_export.php b/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/compat/var_export.php new file mode 100644 index 0000000..7273a1e --- /dev/null +++ b/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/compat/var_export.php @@ -0,0 +1,105 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: var_export.php 2 2009-03-16 20:22:51Z ggiunta $ + + +/** + * Replace var_export() + * + * @category PHP + * @package PHP_Compat + * @link http://php.net/function.var_export + * @author Aidan Lister + * @version $Revision: 1.2 $ + * @since PHP 4.2.0 + * @require PHP 4.0.0 (user_error) + */ +if (!function_exists('var_export')) { + function var_export($array, $return = false, $lvl=0) + { + // Common output variables + $indent = ' '; + $doublearrow = ' => '; + $lineend = ",\n"; + $stringdelim = '\''; + + // Check the export isn't a simple string / int + if (is_string($array)) { + $out = $stringdelim . str_replace('\'', '\\\'', str_replace('\\', '\\\\', $array)) . $stringdelim; + } elseif (is_int($array) || is_float($array)) { + $out = (string)$array; + } elseif (is_bool($array)) { + $out = $array ? 'true' : 'false'; + } elseif (is_null($array)) { + $out = 'NULL'; + } elseif (is_resource($array)) { + $out = 'resource'; + } else { + // Begin the array export + // Start the string + $out = "array (\n"; + + // Loop through each value in array + foreach ($array as $key => $value) { + // If the key is a string, delimit it + if (is_string($key)) { + $key = str_replace('\'', '\\\'', str_replace('\\', '\\\\', $key)); + $key = $stringdelim . $key . $stringdelim; + } + + $val = var_export($value, true, $lvl+1); + // Delimit value + /*if (is_array($value)) { + // We have an array, so do some recursion + // Do some basic recursion while increasing the indent + $recur_array = explode($newline, var_export($value, true)); + $temp_array = array(); + foreach ($recur_array as $recur_line) { + $temp_array[] = $indent . $recur_line; + } + $recur_array = implode($newline, $temp_array); + $value = $newline . $recur_array; + } elseif (is_null($value)) { + $value = 'NULL'; + } else { + $value = str_replace($find, $replace, $value); + $value = $stringdelim . $value . $stringdelim; + }*/ + + // Piece together the line + for ($i = 0; $i < $lvl; $i++) + $out .= $indent; + $out .= $key . $doublearrow . $val . $lineend; + } + + // End our string + for ($i = 0; $i < $lvl; $i++) + $out .= $indent; + $out .= ")"; + } + + // Decide method of output + if ($return === true) { + return $out; + } else { + echo $out; + return; + } + } +} +?> \ No newline at end of file diff --git a/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/compat/version_compare.php b/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/compat/version_compare.php new file mode 100644 index 0000000..a1211ea --- /dev/null +++ b/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/compat/version_compare.php @@ -0,0 +1,179 @@ + | +// | Aidan Lister | +// +----------------------------------------------------------------------+ +// +// $Id: version_compare.php 2 2009-03-16 20:22:51Z ggiunta $ + + +/** + * Replace version_compare() + * + * @category PHP + * @package PHP_Compat + * @link http://php.net/function.version_compare + * @author Philippe Jausions + * @author Aidan Lister + * @version $Revision: 1.1 $ + * @since PHP 4.1.0 + * @require PHP 4.0.0 (user_error) + */ +if (!function_exists('version_compare')) { + function version_compare($version1, $version2, $operator = '<') + { + // Check input + if (!is_scalar($version1)) { + user_error('version_compare() expects parameter 1 to be string, ' . + gettype($version1) . ' given', E_USER_WARNING); + return; + } + + if (!is_scalar($version2)) { + user_error('version_compare() expects parameter 2 to be string, ' . + gettype($version2) . ' given', E_USER_WARNING); + return; + } + + if (!is_scalar($operator)) { + user_error('version_compare() expects parameter 3 to be string, ' . + gettype($operator) . ' given', E_USER_WARNING); + return; + } + + // Standardise versions + $v1 = explode('.', + str_replace('..', '.', + preg_replace('/([^0-9\.]+)/', '.$1.', + str_replace(array('-', '_', '+'), '.', + trim($version1))))); + + $v2 = explode('.', + str_replace('..', '.', + preg_replace('/([^0-9\.]+)/', '.$1.', + str_replace(array('-', '_', '+'), '.', + trim($version2))))); + + // Replace empty entries at the start of the array + while (empty($v1[0]) && array_shift($v1)) {} + while (empty($v2[0]) && array_shift($v2)) {} + + // Release state order + // '#' stands for any number + $versions = array( + 'dev' => 0, + 'alpha' => 1, + 'a' => 1, + 'beta' => 2, + 'b' => 2, + 'RC' => 3, + '#' => 4, + 'p' => 5, + 'pl' => 5); + + // Loop through each segment in the version string + $compare = 0; + for ($i = 0, $x = min(count($v1), count($v2)); $i < $x; $i++) { + if ($v1[$i] == $v2[$i]) { + continue; + } + $i1 = $v1[$i]; + $i2 = $v2[$i]; + if (is_numeric($i1) && is_numeric($i2)) { + $compare = ($i1 < $i2) ? -1 : 1; + break; + } + // We use the position of '#' in the versions list + // for numbers... (so take care of # in original string) + if ($i1 == '#') { + $i1 = ''; + } elseif (is_numeric($i1)) { + $i1 = '#'; + } + if ($i2 == '#') { + $i2 = ''; + } elseif (is_numeric($i2)) { + $i2 = '#'; + } + if (isset($versions[$i1]) && isset($versions[$i2])) { + $compare = ($versions[$i1] < $versions[$i2]) ? -1 : 1; + } elseif (isset($versions[$i1])) { + $compare = 1; + } elseif (isset($versions[$i2])) { + $compare = -1; + } else { + $compare = 0; + } + + break; + } + + // If previous loop didn't find anything, compare the "extra" segments + if ($compare == 0) { + if (count($v2) > count($v1)) { + if (isset($versions[$v2[$i]])) { + $compare = ($versions[$v2[$i]] < 4) ? 1 : -1; + } else { + $compare = -1; + } + } elseif (count($v2) < count($v1)) { + if (isset($versions[$v1[$i]])) { + $compare = ($versions[$v1[$i]] < 4) ? -1 : 1; + } else { + $compare = 1; + } + } + } + + // Compare the versions + if (func_num_args() > 2) { + switch ($operator) { + case '>': + case 'gt': + return (bool) ($compare > 0); + break; + case '>=': + case 'ge': + return (bool) ($compare >= 0); + break; + case '<=': + case 'le': + return (bool) ($compare <= 0); + break; + case '==': + case '=': + case 'eq': + return (bool) ($compare == 0); + break; + case '<>': + case '!=': + case 'ne': + return (bool) ($compare != 0); + break; + case '': + case '<': + case 'lt': + return (bool) ($compare < 0); + break; + default: + return; + } + } + + return $compare; + } +} + +?> \ No newline at end of file diff --git a/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/xmlrpc.inc b/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/xmlrpc.inc new file mode 100644 index 0000000..e02a9f1 --- /dev/null +++ b/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/xmlrpc.inc @@ -0,0 +1,3773 @@ + +// $Id: xmlrpc.inc,v 1.174 2009/03/16 19:36:38 ggiunta Exp $ + +// Copyright (c) 1999,2000,2002 Edd Dumbill. +// All rights reserved. +// +// 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 "XML-RPC for PHP" 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 COPYRIGHT HOLDERS AND CONTRIBUTORS +// "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 +// REGENTS OR 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. + + if(!function_exists('xml_parser_create')) + { + // For PHP 4 onward, XML functionality is always compiled-in on windows: + // no more need to dl-open it. It might have been compiled out on *nix... + if(strtoupper(substr(PHP_OS, 0, 3) != 'WIN')) + { + dl('xml.so'); + } + } + + // Try to be backward compat with php < 4.2 (are we not being nice ?) + $phpversion = phpversion(); + if($phpversion[0] == '4' && $phpversion[2] < 2) + { + // give an opportunity to user to specify where to include other files from + if(!defined('PHP_XMLRPC_COMPAT_DIR')) + { + define('PHP_XMLRPC_COMPAT_DIR', dirname(__FILE__).'/compat/'); + } + if($phpversion[2] == '0') + { + if($phpversion[4] < 6) + { + include(PHP_XMLRPC_COMPAT_DIR.'is_callable.php'); + } + include(PHP_XMLRPC_COMPAT_DIR.'is_scalar.php'); + include(PHP_XMLRPC_COMPAT_DIR.'array_key_exists.php'); + include(PHP_XMLRPC_COMPAT_DIR.'version_compare.php'); + } + include(PHP_XMLRPC_COMPAT_DIR.'var_export.php'); + include(PHP_XMLRPC_COMPAT_DIR.'is_a.php'); + } + + // G. Giunta 2005/01/29: declare global these variables, + // so that xmlrpc.inc will work even if included from within a function + // Milosch: 2005/08/07 - explicitly request these via $GLOBALS where used. + $GLOBALS['xmlrpcI4']='i4'; + $GLOBALS['xmlrpcInt']='int'; + $GLOBALS['xmlrpcBoolean']='boolean'; + $GLOBALS['xmlrpcDouble']='double'; + $GLOBALS['xmlrpcString']='string'; + $GLOBALS['xmlrpcDateTime']='dateTime.iso8601'; + $GLOBALS['xmlrpcBase64']='base64'; + $GLOBALS['xmlrpcArray']='array'; + $GLOBALS['xmlrpcStruct']='struct'; + $GLOBALS['xmlrpcValue']='undefined'; + + $GLOBALS['xmlrpcTypes']=array( + $GLOBALS['xmlrpcI4'] => 1, + $GLOBALS['xmlrpcInt'] => 1, + $GLOBALS['xmlrpcBoolean'] => 1, + $GLOBALS['xmlrpcString'] => 1, + $GLOBALS['xmlrpcDouble'] => 1, + $GLOBALS['xmlrpcDateTime'] => 1, + $GLOBALS['xmlrpcBase64'] => 1, + $GLOBALS['xmlrpcArray'] => 2, + $GLOBALS['xmlrpcStruct'] => 3 + ); + + $GLOBALS['xmlrpc_valid_parents'] = array( + 'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'), + 'BOOLEAN' => array('VALUE'), + 'I4' => array('VALUE'), + 'INT' => array('VALUE'), + 'STRING' => array('VALUE'), + 'DOUBLE' => array('VALUE'), + 'DATETIME.ISO8601' => array('VALUE'), + 'BASE64' => array('VALUE'), + 'MEMBER' => array('STRUCT'), + 'NAME' => array('MEMBER'), + 'DATA' => array('ARRAY'), + 'ARRAY' => array('VALUE'), + 'STRUCT' => array('VALUE'), + 'PARAM' => array('PARAMS'), + 'METHODNAME' => array('METHODCALL'), + 'PARAMS' => array('METHODCALL', 'METHODRESPONSE'), + 'FAULT' => array('METHODRESPONSE'), + 'NIL' => array('VALUE') // only used when extension activated + ); + + // define extra types for supporting NULL (useful for json or ) + $GLOBALS['xmlrpcNull']='null'; + $GLOBALS['xmlrpcTypes']['null']=1; + + // Not in use anymore since 2.0. Shall we remove it? + /// @deprecated + $GLOBALS['xmlEntities']=array( + 'amp' => '&', + 'quot' => '"', + 'lt' => '<', + 'gt' => '>', + 'apos' => "'" + ); + + // tables used for transcoding different charsets into us-ascii xml + + $GLOBALS['xml_iso88591_Entities']=array(); + $GLOBALS['xml_iso88591_Entities']['in'] = array(); + $GLOBALS['xml_iso88591_Entities']['out'] = array(); + for ($i = 0; $i < 32; $i++) + { + $GLOBALS['xml_iso88591_Entities']['in'][] = chr($i); + $GLOBALS['xml_iso88591_Entities']['out'][] = '&#'.$i.';'; + } + for ($i = 160; $i < 256; $i++) + { + $GLOBALS['xml_iso88591_Entities']['in'][] = chr($i); + $GLOBALS['xml_iso88591_Entities']['out'][] = '&#'.$i.';'; + } + + /// @todo add to iso table the characters from cp_1252 range, i.e. 128 to 159? + /// These will NOT be present in true ISO-8859-1, but will save the unwary + /// windows user from sending junk (though no luck when reciving them...) + /* + $GLOBALS['xml_cp1252_Entities']=array(); + for ($i = 128; $i < 160; $i++) + { + $GLOBALS['xml_cp1252_Entities']['in'][] = chr($i); + } + $GLOBALS['xml_cp1252_Entities']['out'] = array( + '€', '?', '‚', 'ƒ', + '„', '…', '†', '‡', + 'ˆ', '‰', 'Š', '‹', + 'Œ', '?', 'Ž', '?', + '?', '‘', '’', '“', + '”', '•', '–', '—', + '˜', '™', 'š', '›', + 'œ', '?', 'ž', 'Ÿ' + ); + */ + + $GLOBALS['xmlrpcerr'] = array( + 'unknown_method'=>1, + 'invalid_return'=>2, + 'incorrect_params'=>3, + 'introspect_unknown'=>4, + 'http_error'=>5, + 'no_data'=>6, + 'no_ssl'=>7, + 'curl_fail'=>8, + 'invalid_request'=>15, + 'no_curl'=>16, + 'server_error'=>17, + 'multicall_error'=>18, + 'multicall_notstruct'=>9, + 'multicall_nomethod'=>10, + 'multicall_notstring'=>11, + 'multicall_recursion'=>12, + 'multicall_noparams'=>13, + 'multicall_notarray'=>14, + + 'cannot_decompress'=>103, + 'decompress_fail'=>104, + 'dechunk_fail'=>105, + 'server_cannot_decompress'=>106, + 'server_decompress_fail'=>107 + ); + + $GLOBALS['xmlrpcstr'] = array( + 'unknown_method'=>'Unknown method', + 'invalid_return'=>'Invalid return payload: enable debugging to examine incoming payload', + 'incorrect_params'=>'Incorrect parameters passed to method', + 'introspect_unknown'=>"Can't introspect: method unknown", + 'http_error'=>"Didn't receive 200 OK from remote server.", + 'no_data'=>'No data received from server.', + 'no_ssl'=>'No SSL support compiled in.', + 'curl_fail'=>'CURL error', + 'invalid_request'=>'Invalid request payload', + 'no_curl'=>'No CURL support compiled in.', + 'server_error'=>'Internal server error', + 'multicall_error'=>'Received from server invalid multicall response', + 'multicall_notstruct'=>'system.multicall expected struct', + 'multicall_nomethod'=>'missing methodName', + 'multicall_notstring'=>'methodName is not a string', + 'multicall_recursion'=>'recursive system.multicall forbidden', + 'multicall_noparams'=>'missing params', + 'multicall_notarray'=>'params is not an array', + + 'cannot_decompress'=>'Received from server compressed HTTP and cannot decompress', + 'decompress_fail'=>'Received from server invalid compressed HTTP', + 'dechunk_fail'=>'Received from server invalid chunked HTTP', + 'server_cannot_decompress'=>'Received from client compressed HTTP request and cannot decompress', + 'server_decompress_fail'=>'Received from client invalid compressed HTTP request' + ); + + // The charset encoding used by the server for received messages and + // by the client for received responses when received charset cannot be determined + // or is not supported + $GLOBALS['xmlrpc_defencoding']='UTF-8'; + + // The encoding used internally by PHP. + // String values received as xml will be converted to this, and php strings will be converted to xml + // as if having been coded with this + + // by Fumi.Iseki for Japanese + //$GLOBALS['xmlrpc_internalencoding']='ISO-8859-1'; + if ($GLOBALS['xmlrpc_internalencoding']=="") { + if (defined('_CHARSET')) { + $GLOBALS['xmlrpc_internalencoding']=_CHARSET; + } + else { + $GLOBALS['xmlrpc_internalencoding']='ISO-8859-1'; + } + } + + $GLOBALS['xmlrpcName']='XML-RPC for PHP'; + $GLOBALS['xmlrpcVersion']='2.2.2'; + + // let user errors start at 800 + $GLOBALS['xmlrpcerruser']=800; + // let XML parse errors start at 100 + $GLOBALS['xmlrpcerrxml']=100; + + // formulate backslashes for escaping regexp + // Not in use anymore since 2.0. Shall we remove it? + /// @deprecated + $GLOBALS['xmlrpc_backslash']=chr(92).chr(92); + + // set to TRUE to enable correct decoding of values + $GLOBALS['xmlrpc_null_extension']=false; + + // used to store state during parsing + // quick explanation of components: + // ac - used to accumulate values + // isf - used to indicate a parsing fault (2) or xmlrpcresp fault (1) + // isf_reason - used for storing xmlrpcresp fault string + // lv - used to indicate "looking for a value": implements + // the logic to allow values with no types to be strings + // params - used to store parameters in method calls + // method - used to store method name + // stack - array with genealogy of xml elements names: + // used to validate nesting of xmlrpc elements + $GLOBALS['_xh']=null; + + /** + * Convert a string to the correct XML representation in a target charset + * To help correct communication of non-ascii chars inside strings, regardless + * of the charset used when sending requests, parsing them, sending responses + * and parsing responses, an option is to convert all non-ascii chars present in the message + * into their equivalent 'charset entity'. Charset entities enumerated this way + * are independent of the charset encoding used to transmit them, and all XML + * parsers are bound to understand them. + * Note that in the std case we are not sending a charset encoding mime type + * along with http headers, so we are bound by RFC 3023 to emit strict us-ascii. + * + * @todo do a bit of basic benchmarking (strtr vs. str_replace) + * @todo make usage of iconv() or recode_string() or mb_string() where available + */ + function xmlrpc_encode_entitites($data, $src_encoding='', $dest_encoding='') + { + if ($src_encoding == '') + { + // lame, but we know no better... + $src_encoding = $GLOBALS['xmlrpc_internalencoding']; + } + + switch(strtoupper($src_encoding.'_'.$dest_encoding)) + { + case 'ISO-8859-1_': + case 'ISO-8859-1_US-ASCII': + $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data); + $escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data); + break; + case 'ISO-8859-1_UTF-8': + $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data); + $escaped_data = utf8_encode($escaped_data); + break; + case 'ISO-8859-1_ISO-8859-1': + case 'US-ASCII_US-ASCII': + case 'US-ASCII_UTF-8': + case 'US-ASCII_': + case 'US-ASCII_ISO-8859-1': + case 'UTF-8_UTF-8': + //case 'CP1252_CP1252': + $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data); + break; + case 'UTF-8_': + case 'UTF-8_US-ASCII': + case 'UTF-8_ISO-8859-1': + // NB: this will choke on invalid UTF-8, going most likely beyond EOF + $escaped_data = ''; + // be kind to users creating string xmlrpcvals out of different php types + $data = (string) $data; + $ns = strlen ($data); + for ($nn = 0; $nn < $ns; $nn++) + { + $ch = $data[$nn]; + $ii = ord($ch); + //1 7 0bbbbbbb (127) + if ($ii < 128) + { + /// @todo shall we replace this with a (supposedly) faster str_replace? + switch($ii){ + case 34: + $escaped_data .= '"'; + break; + case 38: + $escaped_data .= '&'; + break; + case 39: + $escaped_data .= '''; + break; + case 60: + $escaped_data .= '<'; + break; + case 62: + $escaped_data .= '>'; + break; + default: + $escaped_data .= $ch; + } // switch + } + //2 11 110bbbbb 10bbbbbb (2047) + else if ($ii>>5 == 6) + { + $b1 = ($ii & 31); + $ii = ord($data[$nn+1]); + $b2 = ($ii & 63); + $ii = ($b1 * 64) + $b2; + $ent = sprintf ('&#%d;', $ii); + $escaped_data .= $ent; + $nn += 1; + } + //3 16 1110bbbb 10bbbbbb 10bbbbbb + else if ($ii>>4 == 14) + { + $b1 = ($ii & 15); + $ii = ord($data[$nn+1]); + $b2 = ($ii & 63); + $ii = ord($data[$nn+2]); + $b3 = ($ii & 63); + $ii = ((($b1 * 64) + $b2) * 64) + $b3; + $ent = sprintf ('&#%d;', $ii); + $escaped_data .= $ent; + $nn += 2; + } + //4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb + else if ($ii>>3 == 30) + { + $b1 = ($ii & 7); + $ii = ord($data[$nn+1]); + $b2 = ($ii & 63); + $ii = ord($data[$nn+2]); + $b3 = ($ii & 63); + $ii = ord($data[$nn+3]); + $b4 = ($ii & 63); + $ii = ((((($b1 * 64) + $b2) * 64) + $b3) * 64) + $b4; + $ent = sprintf ('&#%d;', $ii); + $escaped_data .= $ent; + $nn += 3; + } + } + break; +/* + case 'CP1252_': + case 'CP1252_US-ASCII': + $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data); + $escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data); + $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data); + break; + case 'CP1252_UTF-8': + $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data); + /// @todo we could use real UTF8 chars here instead of xml entities... (note that utf_8 encode all allone will NOT convert them) + $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data); + $escaped_data = utf8_encode($escaped_data); + break; + case 'CP1252_ISO-8859-1': + $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data); + // we might as well replave all funky chars with a '?' here, but we are kind and leave it to the receiving application layer to decide what to do with these weird entities... + $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data); + break; +*/ + default: + $escaped_data = ''; + error_log("Converting from $src_encoding to $dest_encoding: not supported..."); + } + return $escaped_data; + } + + /// xml parser handler function for opening element tags + function xmlrpc_se($parser, $name, $attrs, $accept_single_vals=false) + { + // if invalid xmlrpc already detected, skip all processing + if ($GLOBALS['_xh']['isf'] < 2) + { + // check for correct element nesting + // top level element can only be of 2 types + /// @todo optimization creep: save this check into a bool variable, instead of using count() every time: + /// there is only a single top level element in xml anyway + if (count($GLOBALS['_xh']['stack']) == 0) + { + if ($name != 'METHODRESPONSE' && $name != 'METHODCALL' && ( + $name != 'VALUE' && !$accept_single_vals)) + { + $GLOBALS['_xh']['isf'] = 2; + $GLOBALS['_xh']['isf_reason'] = 'missing top level xmlrpc element'; + return; + } + else + { + $GLOBALS['_xh']['rt'] = strtolower($name); + } + } + else + { + // not top level element: see if parent is OK + $parent = end($GLOBALS['_xh']['stack']); + if (!array_key_exists($name, $GLOBALS['xmlrpc_valid_parents']) || !in_array($parent, $GLOBALS['xmlrpc_valid_parents'][$name])) + { + $GLOBALS['_xh']['isf'] = 2; + $GLOBALS['_xh']['isf_reason'] = "xmlrpc element $name cannot be child of $parent"; + return; + } + } + + switch($name) + { + // optimize for speed switch cases: most common cases first + case 'VALUE': + /// @todo we could check for 2 VALUE elements inside a MEMBER or PARAM element + $GLOBALS['_xh']['vt']='value'; // indicator: no value found yet + $GLOBALS['_xh']['ac']=''; + $GLOBALS['_xh']['lv']=1; + $GLOBALS['_xh']['php_class']=null; + break; + case 'I4': + case 'INT': + case 'STRING': + case 'BOOLEAN': + case 'DOUBLE': + case 'DATETIME.ISO8601': + case 'BASE64': + if ($GLOBALS['_xh']['vt']!='value') + { + //two data elements inside a value: an error occurred! + $GLOBALS['_xh']['isf'] = 2; + $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value"; + return; + } + $GLOBALS['_xh']['ac']=''; // reset the accumulator + break; + case 'STRUCT': + case 'ARRAY': + if ($GLOBALS['_xh']['vt']!='value') + { + //two data elements inside a value: an error occurred! + $GLOBALS['_xh']['isf'] = 2; + $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value"; + return; + } + // create an empty array to hold child values, and push it onto appropriate stack + $cur_val = array(); + $cur_val['values'] = array(); + $cur_val['type'] = $name; + // check for out-of-band information to rebuild php objs + // and in case it is found, save it + if (@isset($attrs['PHP_CLASS'])) + { + $cur_val['php_class'] = $attrs['PHP_CLASS']; + } + $GLOBALS['_xh']['valuestack'][] = $cur_val; + $GLOBALS['_xh']['vt']='data'; // be prepared for a data element next + break; + case 'DATA': + if ($GLOBALS['_xh']['vt']!='data') + { + //two data elements inside a value: an error occurred! + $GLOBALS['_xh']['isf'] = 2; + $GLOBALS['_xh']['isf_reason'] = "found two data elements inside an array element"; + return; + } + case 'METHODCALL': + case 'METHODRESPONSE': + case 'PARAMS': + // valid elements that add little to processing + break; + case 'METHODNAME': + case 'NAME': + /// @todo we could check for 2 NAME elements inside a MEMBER element + $GLOBALS['_xh']['ac']=''; + break; + case 'FAULT': + $GLOBALS['_xh']['isf']=1; + break; + case 'MEMBER': + $GLOBALS['_xh']['valuestack'][count($GLOBALS['_xh']['valuestack'])-1]['name']=''; // set member name to null, in case we do not find in the xml later on + //$GLOBALS['_xh']['ac']=''; + // Drop trough intentionally + case 'PARAM': + // clear value type, so we can check later if no value has been passed for this param/member + $GLOBALS['_xh']['vt']=null; + break; + case 'NIL': + if ($GLOBALS['xmlrpc_null_extension']) + { + if ($GLOBALS['_xh']['vt']!='value') + { + //two data elements inside a value: an error occurred! + $GLOBALS['_xh']['isf'] = 2; + $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value"; + return; + } + $GLOBALS['_xh']['ac']=''; // reset the accumulator + break; + } + // we do not support the extension, so + // drop through intentionally + default: + /// INVALID ELEMENT: RAISE ISF so that it is later recognized!!! + $GLOBALS['_xh']['isf'] = 2; + $GLOBALS['_xh']['isf_reason'] = "found not-xmlrpc xml element $name"; + break; + } + + // Save current element name to stack, to validate nesting + $GLOBALS['_xh']['stack'][] = $name; + + /// @todo optimization creep: move this inside the big switch() above + if($name!='VALUE') + { + $GLOBALS['_xh']['lv']=0; + } + } + } + + /// Used in decoding xml chunks that might represent single xmlrpc values + function xmlrpc_se_any($parser, $name, $attrs) + { + xmlrpc_se($parser, $name, $attrs, true); + } + + /// xml parser handler function for close element tags + function xmlrpc_ee($parser, $name, $rebuild_xmlrpcvals = true) + { + if ($GLOBALS['_xh']['isf'] < 2) + { + // push this element name from stack + // NB: if XML validates, correct opening/closing is guaranteed and + // we do not have to check for $name == $curr_elem. + // we also checked for proper nesting at start of elements... + $curr_elem = array_pop($GLOBALS['_xh']['stack']); + + switch($name) + { + case 'VALUE': + // This if() detects if no scalar was inside + if ($GLOBALS['_xh']['vt']=='value') + { + $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac']; + $GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcString']; + } + + if ($rebuild_xmlrpcvals) + { + // build the xmlrpc val out of the data received, and substitute it + $temp = new xmlrpcval($GLOBALS['_xh']['value'], $GLOBALS['_xh']['vt']); + // in case we got info about underlying php class, save it + // in the object we're rebuilding + if (isset($GLOBALS['_xh']['php_class'])) + $temp->_php_class = $GLOBALS['_xh']['php_class']; + // check if we are inside an array or struct: + // if value just built is inside an array, let's move it into array on the stack + $vscount = count($GLOBALS['_xh']['valuestack']); + if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY') + { + $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $temp; + } + else + { + $GLOBALS['_xh']['value'] = $temp; + } + } + else + { + /// @todo this needs to treat correctly php-serialized objects, + /// since std deserializing is done by php_xmlrpc_decode, + /// which we will not be calling... + if (isset($GLOBALS['_xh']['php_class'])) + { + } + + // check if we are inside an array or struct: + // if value just built is inside an array, let's move it into array on the stack + $vscount = count($GLOBALS['_xh']['valuestack']); + if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY') + { + $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $GLOBALS['_xh']['value']; + } + } + break; + case 'BOOLEAN': + case 'I4': + case 'INT': + case 'STRING': + case 'DOUBLE': + case 'DATETIME.ISO8601': + case 'BASE64': + $GLOBALS['_xh']['vt']=strtolower($name); + /// @todo: optimization creep - remove the if/elseif cycle below + /// since the case() in which we are already did that + if ($name=='STRING') + { + $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac']; + } + elseif ($name=='DATETIME.ISO8601') + { + if (!preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $GLOBALS['_xh']['ac'])) + { + error_log('XML-RPC: invalid value received in DATETIME: '.$GLOBALS['_xh']['ac']); + } + $GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcDateTime']; + $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac']; + } + elseif ($name=='BASE64') + { + /// @todo check for failure of base64 decoding / catch warnings + $GLOBALS['_xh']['value']=base64_decode($GLOBALS['_xh']['ac']); + } + elseif ($name=='BOOLEAN') + { + // special case here: we translate boolean 1 or 0 into PHP + // constants true or false. + // Strings 'true' and 'false' are accepted, even though the + // spec never mentions them (see eg. Blogger api docs) + // NB: this simple checks helps a lot sanitizing input, ie no + // security problems around here + if ($GLOBALS['_xh']['ac']=='1' || strcasecmp($GLOBALS['_xh']['ac'], 'true') == 0) + { + $GLOBALS['_xh']['value']=true; + } + else + { + // log if receiveing something strange, even though we set the value to false anyway + if ($GLOBALS['_xh']['ac']!='0' && strcasecmp($GLOBALS['_xh']['ac'], 'false') != 0) + error_log('XML-RPC: invalid value received in BOOLEAN: '.$GLOBALS['_xh']['ac']); + $GLOBALS['_xh']['value']=false; + } + } + elseif ($name=='DOUBLE') + { + // we have a DOUBLE + // we must check that only 0123456789-. are characters here + // NOTE: regexp could be much stricter than this... + if (!preg_match('/^[+-eE0123456789 \t.]+$/', $GLOBALS['_xh']['ac'])) + { + /// @todo: find a better way of throwing an error than this! + error_log('XML-RPC: non numeric value received in DOUBLE: '.$GLOBALS['_xh']['ac']); + $GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND'; + } + else + { + // it's ok, add it on + $GLOBALS['_xh']['value']=(double)$GLOBALS['_xh']['ac']; + } + } + else + { + // we have an I4/INT + // we must check that only 0123456789- are characters here + if (!preg_match('/^[+-]?[0123456789 \t]+$/', $GLOBALS['_xh']['ac'])) + { + /// @todo find a better way of throwing an error than this! + error_log('XML-RPC: non numeric value received in INT: '.$GLOBALS['_xh']['ac']); + $GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND'; + } + else + { + // it's ok, add it on + $GLOBALS['_xh']['value']=(int)$GLOBALS['_xh']['ac']; + } + } + //$GLOBALS['_xh']['ac']=''; // is this necessary? + $GLOBALS['_xh']['lv']=3; // indicate we've found a value + break; + case 'NAME': + $GLOBALS['_xh']['valuestack'][count($GLOBALS['_xh']['valuestack'])-1]['name'] = $GLOBALS['_xh']['ac']; + break; + case 'MEMBER': + //$GLOBALS['_xh']['ac']=''; // is this necessary? + // add to array in the stack the last element built, + // unless no VALUE was found + if ($GLOBALS['_xh']['vt']) + { + $vscount = count($GLOBALS['_xh']['valuestack']); + $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][$GLOBALS['_xh']['valuestack'][$vscount-1]['name']] = $GLOBALS['_xh']['value']; + } else + error_log('XML-RPC: missing VALUE inside STRUCT in received xml'); + break; + case 'DATA': + //$GLOBALS['_xh']['ac']=''; // is this necessary? + $GLOBALS['_xh']['vt']=null; // reset this to check for 2 data elements in a row - even if they're empty + break; + case 'STRUCT': + case 'ARRAY': + // fetch out of stack array of values, and promote it to current value + $curr_val = array_pop($GLOBALS['_xh']['valuestack']); + $GLOBALS['_xh']['value'] = $curr_val['values']; + $GLOBALS['_xh']['vt']=strtolower($name); + if (isset($curr_val['php_class'])) + { + $GLOBALS['_xh']['php_class'] = $curr_val['php_class']; + } + break; + case 'PARAM': + // add to array of params the current value, + // unless no VALUE was found + if ($GLOBALS['_xh']['vt']) + { + $GLOBALS['_xh']['params'][]=$GLOBALS['_xh']['value']; + $GLOBALS['_xh']['pt'][]=$GLOBALS['_xh']['vt']; + } + else + error_log('XML-RPC: missing VALUE inside PARAM in received xml'); + break; + case 'METHODNAME': + $GLOBALS['_xh']['method']=preg_replace('/^[\n\r\t ]+/', '', $GLOBALS['_xh']['ac']); + break; + case 'NIL': + if ($GLOBALS['xmlrpc_null_extension']) + { + $GLOBALS['_xh']['vt']='null'; + $GLOBALS['_xh']['value']=null; + $GLOBALS['_xh']['lv']=3; + break; + } + // drop through intentionally if nil extension not enabled + case 'PARAMS': + case 'FAULT': + case 'METHODCALL': + case 'METHORESPONSE': + break; + default: + // End of INVALID ELEMENT! + // shall we add an assert here for unreachable code??? + break; + } + } + } + + /// Used in decoding xmlrpc requests/responses without rebuilding xmlrpc values + function xmlrpc_ee_fast($parser, $name) + { + xmlrpc_ee($parser, $name, false); + } + + /// xml parser handler function for character data + function xmlrpc_cd($parser, $data) + { + // skip processing if xml fault already detected + if ($GLOBALS['_xh']['isf'] < 2) + { + // "lookforvalue==3" means that we've found an entire value + // and should discard any further character data + if($GLOBALS['_xh']['lv']!=3) + { + // G. Giunta 2006-08-23: useless change of 'lv' from 1 to 2 + //if($GLOBALS['_xh']['lv']==1) + //{ + // if we've found text and we're just in a then + // say we've found a value + //$GLOBALS['_xh']['lv']=2; + //} + // we always initialize the accumulator before starting parsing, anyway... + //if(!@isset($GLOBALS['_xh']['ac'])) + //{ + // $GLOBALS['_xh']['ac'] = ''; + //} + $GLOBALS['_xh']['ac'].=$data; + } + } + } + + /// xml parser handler function for 'other stuff', ie. not char data or + /// element start/end tag. In fact it only gets called on unknown entities... + function xmlrpc_dh($parser, $data) + { + // skip processing if xml fault already detected + if ($GLOBALS['_xh']['isf'] < 2) + { + if(substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';') + { + // G. Giunta 2006-08-25: useless change of 'lv' from 1 to 2 + //if($GLOBALS['_xh']['lv']==1) + //{ + // $GLOBALS['_xh']['lv']=2; + //} + $GLOBALS['_xh']['ac'].=$data; + } + } + return true; + } + + class xmlrpc_client + { + var $path; + var $server; + var $port=0; + var $method='http'; + var $errno; + var $errstr; + var $debug=0; + var $username=''; + var $password=''; + var $authtype=1; + var $cert=''; + var $certpass=''; + var $cacert=''; + var $cacertdir=''; + var $key=''; + var $keypass=''; + var $verifypeer=true; + var $verifyhost=1; + var $no_multicall=false; + var $proxy=''; + var $proxyport=0; + var $proxy_user=''; + var $proxy_pass=''; + var $proxy_authtype=1; + var $cookies=array(); + /** + * List of http compression methods accepted by the client for responses. + * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib + * + * NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since + * in those cases it will be up to CURL to decide the compression methods + * it supports. You might check for the presence of 'zlib' in the output of + * curl_version() to determine wheter compression is supported or not + */ + var $accepted_compression = array(); + /** + * Name of compression scheme to be used for sending requests. + * Either null, gzip or deflate + */ + var $request_compression = ''; + /** + * CURL handle: used for keep-alive connections (PHP 4.3.8 up, see: + * http://curl.haxx.se/docs/faq.html#7.3) + */ + var $xmlrpc_curl_handle = null; + /// Wheter to use persistent connections for http 1.1 and https + var $keepalive = false; + /// Charset encodings that can be decoded without problems by the client + var $accepted_charset_encodings = array(); + /// Charset encoding to be used in serializing request. NULL = use ASCII + var $request_charset_encoding = ''; + /** + * Decides the content of xmlrpcresp objects returned by calls to send() + * valid strings are 'xmlrpcvals', 'phpvals' or 'xml' + */ + var $return_type = 'xmlrpcvals'; + + /** + * @param string $path either the complete server URL or the PATH part of the xmlrc server URL, e.g. /xmlrpc/server.php + * @param string $server the server name / ip address + * @param integer $port the port the server is listening on, defaults to 80 or 443 depending on protocol used + * @param string $method the http protocol variant: defaults to 'http', 'https' and 'http11' can be used if CURL is installed + */ + function __construct($path, $server='', $port='', $method='') + { + // allow user to specify all params in $path + if($server == '' and $port == '' and $method == '') + { + $parts = parse_url($path); + $server = $parts['host']; + $path = isset($parts['path']) ? $parts['path'] : ''; + if(isset($parts['query'])) + { + $path .= '?'.$parts['query']; + } + if(isset($parts['fragment'])) + { + $path .= '#'.$parts['fragment']; + } + if(isset($parts['port'])) + { + $port = $parts['port']; + } + if(isset($parts['scheme'])) + { + $method = $parts['scheme']; + } + if(isset($parts['user'])) + { + $this->username = $parts['user']; + } + if(isset($parts['pass'])) + { + $this->password = $parts['pass']; + } + } + if($path == '' || $path[0] != '/') + { + $this->path='/'.$path; + } + else + { + $this->path=$path; + } + $this->server=$server; + if($port != '') + { + $this->port=$port; + } + if($method != '') + { + $this->method=$method; + } + + // if ZLIB is enabled, let the client by default accept compressed responses + if(function_exists('gzinflate') || ( + function_exists('curl_init') && (($info = curl_version()) && + ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version']))) + )) + { + $this->accepted_compression = array('gzip', 'deflate'); + } + + // keepalives: enabled by default ONLY for PHP >= 4.3.8 + // (see http://curl.haxx.se/docs/faq.html#7.3) + if(version_compare(phpversion(), '4.3.8') >= 0) + { + $this->keepalive = true; + } + + // by default the xml parser can support these 3 charset encodings + $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII'); + } + + /** + * Enables/disables the echoing to screen of the xmlrpc responses received + * @param integer $debug values 0, 1 and 2 are supported (2 = echo sent msg too, before received response) + * @access public + */ + function setDebug($in) + { + $this->debug=$in; + } + + /** + * Add some http BASIC AUTH credentials, used by the client to authenticate + * @param string $u username + * @param string $p password + * @param integer $t auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC (basic auth) + * @access public + */ + function setCredentials($u, $p, $t=1) + { + $this->username=$u; + $this->password=$p; + $this->authtype=$t; + } + + /** + * Add a client-side https certificate + * @param string $cert + * @param string $certpass + * @access public + */ + function setCertificate($cert, $certpass) + { + $this->cert = $cert; + $this->certpass = $certpass; + } + + /** + * Add a CA certificate to verify server with (see man page about + * CURLOPT_CAINFO for more details + * @param string $cacert certificate file name (or dir holding certificates) + * @param bool $is_dir set to true to indicate cacert is a dir. defaults to false + * @access public + */ + function setCaCertificate($cacert, $is_dir=false) + { + if ($is_dir) + { + $this->cacertdir = $cacert; + } + else + { + $this->cacert = $cacert; + } + } + + /** + * Set attributes for SSL communication: private SSL key + * NB: does not work in older php/curl installs + * Thanks to Daniel Convissor + * @param string $key The name of a file containing a private SSL key + * @param string $keypass The secret password needed to use the private SSL key + * @access public + */ + function setKey($key, $keypass) + { + $this->key = $key; + $this->keypass = $keypass; + } + + /** + * Set attributes for SSL communication: verify server certificate + * @param bool $i enable/disable verification of peer certificate + * @access public + */ + function setSSLVerifyPeer($i) + { + $this->verifypeer = $i; + } + + /** + * Set attributes for SSL communication: verify match of server cert w. hostname + * @param int $i + * @access public + */ + function setSSLVerifyHost($i) + { + $this->verifyhost = $i; + } + + /** + * Set proxy info + * @param string $proxyhost + * @param string $proxyport Defaults to 8080 for HTTP and 443 for HTTPS + * @param string $proxyusername Leave blank if proxy has public access + * @param string $proxypassword Leave blank if proxy has public access + * @param int $proxyauthtype set to constant CURLAUTH_NTLM to use NTLM auth with proxy + * @access public + */ + function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '', $proxyauthtype = 1) + { + $this->proxy = $proxyhost; + $this->proxyport = $proxyport; + $this->proxy_user = $proxyusername; + $this->proxy_pass = $proxypassword; + $this->proxy_authtype = $proxyauthtype; + } + + /** + * Enables/disables reception of compressed xmlrpc responses. + * Note that enabling reception of compressed responses merely adds some standard + * http headers to xmlrpc requests. It is up to the xmlrpc server to return + * compressed responses when receiving such requests. + * @param string $compmethod either 'gzip', 'deflate', 'any' or '' + * @access public + */ + function setAcceptedCompression($compmethod) + { + if ($compmethod == 'any') + $this->accepted_compression = array('gzip', 'deflate'); + else + $this->accepted_compression = array($compmethod); + } + + /** + * Enables/disables http compression of xmlrpc request. + * Take care when sending compressed requests: servers might not support them + * (and automatic fallback to uncompressed requests is not yet implemented) + * @param string $compmethod either 'gzip', 'deflate' or '' + * @access public + */ + function setRequestCompression($compmethod) + { + $this->request_compression = $compmethod; + } + + /** + * Adds a cookie to list of cookies that will be sent to server. + * NB: setting any param but name and value will turn the cookie into a 'version 1' cookie: + * do not do it unless you know what you are doing + * @param string $name + * @param string $value + * @param string $path + * @param string $domain + * @param int $port + * @access public + * + * @todo check correctness of urlencoding cookie value (copied from php way of doing it...) + */ + function setCookie($name, $value='', $path='', $domain='', $port=null) + { + $this->cookies[$name]['value'] = urlencode($value); + if ($path || $domain || $port) + { + $this->cookies[$name]['path'] = $path; + $this->cookies[$name]['domain'] = $domain; + $this->cookies[$name]['port'] = $port; + $this->cookies[$name]['version'] = 1; + } + else + { + $this->cookies[$name]['version'] = 0; + } + } + + /** + * Send an xmlrpc request + * @param mixed $msg The message object, or an array of messages for using multicall, or the complete xml representation of a request + * @param integer $timeout Connection timeout, in seconds, If unspecified, a platform specific timeout will apply + * @param string $method if left unspecified, the http protocol chosen during creation of the object will be used + * @return xmlrpcresp + * @access public + */ + function& send($msg, $timeout=0, $method='') + { + // if user deos not specify http protocol, use native method of this client + // (i.e. method set during call to constructor) + if($method == '') + { + $method = $this->method; + } + + if(is_array($msg)) + { + // $msg is an array of xmlrpcmsg's + $r = $this->multicall($msg, $timeout, $method); + return $r; + } + elseif(is_string($msg)) + { + $n = new xmlrpcmsg(''); + $n->payload = $msg; + $msg = $n; + } + + // where msg is an xmlrpcmsg + $msg->debug=$this->debug; + + if($method == 'https') + { + $r =& $this->sendPayloadHTTPS( + $msg, + $this->server, + $this->port, + $timeout, + $this->username, + $this->password, + $this->authtype, + $this->cert, + $this->certpass, + $this->cacert, + $this->cacertdir, + $this->proxy, + $this->proxyport, + $this->proxy_user, + $this->proxy_pass, + $this->proxy_authtype, + $this->keepalive, + $this->key, + $this->keypass + ); + } + elseif($method == 'http11') + { + $r =& $this->sendPayloadCURL( + $msg, + $this->server, + $this->port, + $timeout, + $this->username, + $this->password, + $this->authtype, + null, + null, + null, + null, + $this->proxy, + $this->proxyport, + $this->proxy_user, + $this->proxy_pass, + $this->proxy_authtype, + 'http', + $this->keepalive + ); + } + else + { + $r =& $this->sendPayloadHTTP10( + $msg, + $this->server, + $this->port, + $timeout, + $this->username, + $this->password, + $this->authtype, + $this->proxy, + $this->proxyport, + $this->proxy_user, + $this->proxy_pass, + $this->proxy_authtype + ); + } + + return $r; + } + + /** + * @access private + */ + function &sendPayloadHTTP10($msg, $server, $port, $timeout=0, + $username='', $password='', $authtype=1, $proxyhost='', + $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1) + { + if($port==0) + { + $port=80; + } + + // Only create the payload if it was not created previously + if(empty($msg->payload)) + { + $msg->createPayload($this->request_charset_encoding); + } + + $payload = $msg->payload; + // Deflate request body and set appropriate request headers + if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) + { + if($this->request_compression == 'gzip') + { + $a = @gzencode($payload); + if($a) + { + $payload = $a; + $encoding_hdr = "Content-Encoding: gzip\r\n"; + } + } + else + { + $a = @gzcompress($payload); + if($a) + { + $payload = $a; + $encoding_hdr = "Content-Encoding: deflate\r\n"; + } + } + } + else + { + $encoding_hdr = ''; + } + + // thanks to Grant Rauscher for this + $credentials=''; + if($username!='') + { + $credentials='Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n"; + if ($authtype != 1) + { + error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth is supported with HTTP 1.0'); + } + } + + $accepted_encoding = ''; + if(is_array($this->accepted_compression) && count($this->accepted_compression)) + { + $accepted_encoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n"; + } + + $proxy_credentials = ''; + if($proxyhost) + { + if($proxyport == 0) + { + $proxyport = 8080; + } + $connectserver = $proxyhost; + $connectport = $proxyport; + $uri = 'http://'.$server.':'.$port.$this->path; + if($proxyusername != '') + { + if ($proxyauthtype != 1) + { + error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth to proxy is supported with HTTP 1.0'); + } + $proxy_credentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyusername.':'.$proxypassword) . "\r\n"; + } + } + else + { + $connectserver = $server; + $connectport = $port; + $uri = $this->path; + } + + // Cookie generation, as per rfc2965 (version 1 cookies) or + // netscape's rules (version 0 cookies) + $cookieheader=''; + if (count($this->cookies)) + { + $version = ''; + foreach ($this->cookies as $name => $cookie) + { + if ($cookie['version']) + { + $version = ' $Version="' . $cookie['version'] . '";'; + $cookieheader .= ' ' . $name . '="' . $cookie['value'] . '";'; + if ($cookie['path']) + $cookieheader .= ' $Path="' . $cookie['path'] . '";'; + if ($cookie['domain']) + $cookieheader .= ' $Domain="' . $cookie['domain'] . '";'; + if ($cookie['port']) + $cookieheader .= ' $Port="' . $cookie['port'] . '";'; + } + else + { + $cookieheader .= ' ' . $name . '=' . $cookie['value'] . ";"; + } + } + $cookieheader = 'Cookie:' . $version . substr($cookieheader, 0, -1) . "\r\n"; + } + + $op= 'POST ' . $uri. " HTTP/1.0\r\n" . + 'User-Agent: ' . $GLOBALS['xmlrpcName'] . ' ' . $GLOBALS['xmlrpcVersion'] . "\r\n" . + 'Host: '. $server . ':' . $port . "\r\n" . + $credentials . + $proxy_credentials . + $accepted_encoding . + $encoding_hdr . + 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings) . "\r\n" . + $cookieheader . + 'Content-Type: ' . $msg->content_type . "\r\nContent-Length: " . + strlen($payload) . "\r\n\r\n" . + $payload; + + if($this->debug > 1) + { + print "
\n---SENDING---\n" . htmlentities($op) . "\n---END---\n
"; + // let the client see this now in case http times out... + flush(); + } + + if($timeout>0) + { + $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr, $timeout); + } + else + { + $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr); + } + if($fp) + { + if($timeout>0 && function_exists('stream_set_timeout')) + { + stream_set_timeout($fp, $timeout); + } + } + else + { + $this->errstr='Connect error: '.$this->errstr; + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr . ' (' . $this->errno . ')'); + return $r; + } + + if(!fputs($fp, $op, strlen($op))) + { + fclose($fp); + $this->errstr='Write error'; + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr); + return $r; + } + else + { + // reset errno and errstr on succesful socket connection + $this->errstr = ''; + } + // G. Giunta 2005/10/24: close socket before parsing. + // should yeld slightly better execution times, and make easier recursive calls (e.g. to follow http redirects) + $ipd=''; + do + { + // shall we check for $data === FALSE? + // as per the manual, it signals an error + $ipd.=fread($fp, 32768); + } while(!feof($fp)); + fclose($fp); + $r =& $msg->parseResponse($ipd, false, $this->return_type); + return $r; + + } + + /** + * @access private + */ + function &sendPayloadHTTPS($msg, $server, $port, $timeout=0, $username='', + $password='', $authtype=1, $cert='',$certpass='', $cacert='', $cacertdir='', + $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1, + $keepalive=false, $key='', $keypass='') + { + $r =& $this->sendPayloadCURL($msg, $server, $port, $timeout, $username, + $password, $authtype, $cert, $certpass, $cacert, $cacertdir, $proxyhost, $proxyport, + $proxyusername, $proxypassword, $proxyauthtype, 'https', $keepalive, $key, $keypass); + return $r; + } + + /** + * Contributed by Justin Miller + * Requires curl to be built into PHP + * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers! + * @access private + */ + function &sendPayloadCURL($msg, $server, $port, $timeout=0, $username='', + $password='', $authtype=1, $cert='', $certpass='', $cacert='', $cacertdir='', + $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1, $method='https', + $keepalive=false, $key='', $keypass='') + { + if(!function_exists('curl_init')) + { + $this->errstr='CURL unavailable on this install'; + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_curl'], $GLOBALS['xmlrpcstr']['no_curl']); + return $r; + } + if($method == 'https') + { + if(($info = curl_version()) && + ((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version'])))) + { + $this->errstr='SSL unavailable on this install'; + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_ssl'], $GLOBALS['xmlrpcstr']['no_ssl']); + return $r; + } + } + + if($port == 0) + { + if($method == 'http') + { + $port = 80; + } + else + { + $port = 443; + } + } + + // Only create the payload if it was not created previously + if(empty($msg->payload)) + { + $msg->createPayload($this->request_charset_encoding); + } + + // Deflate request body and set appropriate request headers + $payload = $msg->payload; + if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) + { + if($this->request_compression == 'gzip') + { + $a = @gzencode($payload); + if($a) + { + $payload = $a; + $encoding_hdr = 'Content-Encoding: gzip'; + } + } + else + { + $a = @gzcompress($payload); + if($a) + { + $payload = $a; + $encoding_hdr = 'Content-Encoding: deflate'; + } + } + } + else + { + $encoding_hdr = ''; + } + + if($this->debug > 1) + { + print "
\n---SENDING---\n" . htmlentities($payload) . "\n---END---\n
"; + // let the client see this now in case http times out... + flush(); + } + + if(!$keepalive || !$this->xmlrpc_curl_handle) + { + $curl = curl_init($method . '://' . $server . ':' . $port . $this->path); + if($keepalive) + { + $this->xmlrpc_curl_handle = $curl; + } + } + else + { + $curl = $this->xmlrpc_curl_handle; + } + + // results into variable + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + + if($this->debug) + { + curl_setopt($curl, CURLOPT_VERBOSE, 1); + } + curl_setopt($curl, CURLOPT_USERAGENT, $GLOBALS['xmlrpcName'].' '.$GLOBALS['xmlrpcVersion']); + // required for XMLRPC: post the data + curl_setopt($curl, CURLOPT_POST, 1); + // the data + curl_setopt($curl, CURLOPT_POSTFIELDS, $payload); + + // return the header too + curl_setopt($curl, CURLOPT_HEADER, 1); + + // will only work with PHP >= 5.0 + // NB: if we set an empty string, CURL will add http header indicating + // ALL methods it is supporting. This is possibly a better option than + // letting the user tell what curl can / cannot do... + if(is_array($this->accepted_compression) && count($this->accepted_compression)) + { + //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression)); + // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?) + if (count($this->accepted_compression) == 1) + { + curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]); + } + else + curl_setopt($curl, CURLOPT_ENCODING, ''); + } + // extra headers + $headers = array('Content-Type: ' . $msg->content_type , 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings)); + // if no keepalive is wanted, let the server know it in advance + if(!$keepalive) + { + $headers[] = 'Connection: close'; + } + // request compression header + if($encoding_hdr) + { + $headers[] = $encoding_hdr; + } + + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + // timeout is borked + if($timeout) + { + curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1); + } + + if($username && $password) + { + curl_setopt($curl, CURLOPT_USERPWD, $username.':'.$password); + if (defined('CURLOPT_HTTPAUTH')) + { + curl_setopt($curl, CURLOPT_HTTPAUTH, $authtype); + } + else if ($authtype != 1) + { + error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth is supported by the current PHP/curl install'); + } + } + + if($method == 'https') + { + // set cert file + if($cert) + { + curl_setopt($curl, CURLOPT_SSLCERT, $cert); + } + // set cert password + if($certpass) + { + curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certpass); + } + // whether to verify remote host's cert + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer); + // set ca certificates file/dir + if($cacert) + { + curl_setopt($curl, CURLOPT_CAINFO, $cacert); + } + if($cacertdir) + { + curl_setopt($curl, CURLOPT_CAPATH, $cacertdir); + } + // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?) + if($key) + { + curl_setopt($curl, CURLOPT_SSLKEY, $key); + } + // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?) + if($keypass) + { + curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keypass); + } + // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that it matches the hostname used + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost); + } + + // proxy info + if($proxyhost) + { + if($proxyport == 0) + { + $proxyport = 8080; // NB: even for HTTPS, local connection is on port 8080 + } + curl_setopt($curl, CURLOPT_PROXY, $proxyhost.':'.$proxyport); + //curl_setopt($curl, CURLOPT_PROXYPORT,$proxyport); + if($proxyusername) + { + curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyusername.':'.$proxypassword); + if (defined('CURLOPT_PROXYAUTH')) + { + curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyauthtype); + } + else if ($proxyauthtype != 1) + { + error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth to proxy is supported by the current PHP/curl install'); + } + } + } + + // NB: should we build cookie http headers by hand rather than let CURL do it? + // the following code does not honour 'expires', 'path' and 'domain' cookie attributes + // set to client obj the the user... + if (count($this->cookies)) + { + $cookieheader = ''; + foreach ($this->cookies as $name => $cookie) + { + $cookieheader .= $name . '=' . $cookie['value'] . '; '; + } + curl_setopt($curl, CURLOPT_COOKIE, substr($cookieheader, 0, -2)); + } + + $result = curl_exec($curl); + + if ($this->debug > 1) + { + print "
\n---CURL INFO---\n";
+				foreach(curl_getinfo($curl) as $name => $val)
+					 print $name . ': ' . htmlentities($val). "\n";
+				print "---END---\n
"; + } + + if(!$result) /// @todo we should use a better check here - what if we get back '' or '0'? + { + $this->errstr='no response'; + $resp = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['curl_fail'], $GLOBALS['xmlrpcstr']['curl_fail']. ': '. curl_error($curl)); + curl_close($curl); + if($keepalive) + { + $this->xmlrpc_curl_handle = null; + } + } + else + { + if(!$keepalive) + { + curl_close($curl); + } + $resp =& $msg->parseResponse($result, true, $this->return_type); + } + return $resp; + } + + /** + * Send an array of request messages and return an array of responses. + * Unless $this->no_multicall has been set to true, it will try first + * to use one single xmlrpc call to server method system.multicall, and + * revert to sending many successive calls in case of failure. + * This failure is also stored in $this->no_multicall for subsequent calls. + * Unfortunately, there is no server error code universally used to denote + * the fact that multicall is unsupported, so there is no way to reliably + * distinguish between that and a temporary failure. + * If you are sure that server supports multicall and do not want to + * fallback to using many single calls, set the fourth parameter to FALSE. + * + * NB: trying to shoehorn extra functionality into existing syntax has resulted + * in pretty much convoluted code... + * + * @param array $msgs an array of xmlrpcmsg objects + * @param integer $timeout connection timeout (in seconds) + * @param string $method the http protocol variant to be used + * @param boolean fallback When true, upon receiveing an error during multicall, multiple single calls will be attempted + * @return array + * @access public + */ + function multicall($msgs, $timeout=0, $method='', $fallback=true) + { + if ($method == '') + { + $method = $this->method; + } + if(!$this->no_multicall) + { + $results = $this->_try_multicall($msgs, $timeout, $method); + if(is_array($results)) + { + // System.multicall succeeded + return $results; + } + else + { + // either system.multicall is unsupported by server, + // or call failed for some other reason. + if ($fallback) + { + // Don't try it next time... + $this->no_multicall = true; + } + else + { + if (is_a($results, 'xmlrpcresp')) + { + $result = $results; + } + else + { + $result = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['multicall_error'], $GLOBALS['xmlrpcstr']['multicall_error']); + } + } + } + } + else + { + // override fallback, in case careless user tries to do two + // opposite things at the same time + $fallback = true; + } + + $results = array(); + if ($fallback) + { + // system.multicall is (probably) unsupported by server: + // emulate multicall via multiple requests + foreach($msgs as $msg) + { + $results[] =& $this->send($msg, $timeout, $method); + } + } + else + { + // user does NOT want to fallback on many single calls: + // since we should always return an array of responses, + // return an array with the same error repeated n times + foreach($msgs as $msg) + { + $results[] = $result; + } + } + return $results; + } + + /** + * Attempt to boxcar $msgs via system.multicall. + * Returns either an array of xmlrpcreponses, an xmlrpc error response + * or false (when received response does not respect valid multicall syntax) + * @access private + */ + function _try_multicall($msgs, $timeout, $method) + { + // Construct multicall message + $calls = array(); + foreach($msgs as $msg) + { + $call['methodName'] = new xmlrpcval($msg->method(),'string'); + $numParams = $msg->getNumParams(); + $params = array(); + for($i = 0; $i < $numParams; $i++) + { + $params[$i] = $msg->getParam($i); + } + $call['params'] = new xmlrpcval($params, 'array'); + $calls[] = new xmlrpcval($call, 'struct'); + } + $multicall = new xmlrpcmsg('system.multicall'); + $multicall->addParam(new xmlrpcval($calls, 'array')); + + // Attempt RPC call + $result =& $this->send($multicall, $timeout, $method); + + if($result->faultCode() != 0) + { + // call to system.multicall failed + return $result; + } + + // Unpack responses. + $rets = $result->value(); + + if ($this->return_type == 'xml') + { + return $rets; + } + else if ($this->return_type == 'phpvals') + { + ///@todo test this code branch... + $rets = $result->value(); + if(!is_array($rets)) + { + return false; // bad return type from system.multicall + } + $numRets = count($rets); + if($numRets != count($msgs)) + { + return false; // wrong number of return values. + } + + $response = array(); + for($i = 0; $i < $numRets; $i++) + { + $val = $rets[$i]; + if (!is_array($val)) { + return false; + } + switch(count($val)) + { + case 1: + if(!isset($val[0])) + { + return false; // Bad value + } + // Normal return value + $response[$i] = new xmlrpcresp($val[0], 0, '', 'phpvals'); + break; + case 2: + /// @todo remove usage of @: it is apparently quite slow + $code = @$val['faultCode']; + if(!is_int($code)) + { + return false; + } + $str = @$val['faultString']; + if(!is_string($str)) + { + return false; + } + $response[$i] = new xmlrpcresp(0, $code, $str); + break; + default: + return false; + } + } + return $response; + } + else // return type == 'xmlrpcvals' + { + $rets = $result->value(); + if($rets->kindOf() != 'array') + { + return false; // bad return type from system.multicall + } + $numRets = $rets->arraysize(); + if($numRets != count($msgs)) + { + return false; // wrong number of return values. + } + + $response = array(); + for($i = 0; $i < $numRets; $i++) + { + $val = $rets->arraymem($i); + switch($val->kindOf()) + { + case 'array': + if($val->arraysize() != 1) + { + return false; // Bad value + } + // Normal return value + $response[$i] = new xmlrpcresp($val->arraymem(0)); + break; + case 'struct': + $code = $val->structmem('faultCode'); + if($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') + { + return false; + } + $str = $val->structmem('faultString'); + if($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') + { + return false; + } + $response[$i] = new xmlrpcresp(0, $code->scalarval(), $str->scalarval()); + break; + default: + return false; + } + } + return $response; + } + } + } // end class xmlrpc_client + + + + class xmlrpcresp + { + var $val = 0; + var $valtyp; + var $errno = 0; + var $errstr = ''; + var $payload; + var $hdrs = array(); + var $_cookies = array(); + var $content_type = 'text/xml'; + var $raw_data = ''; + + /** + * @param mixed $val either an xmlrpcval obj, a php value or the xml serialization of an xmlrpcval (a string) + * @param integer $fcode set it to anything but 0 to create an error response + * @param string $fstr the error string, in case of an error response + * @param string $valtyp either 'xmlrpcvals', 'phpvals' or 'xml' + * + * @todo add check that $val / $fcode / $fstr is of correct type??? + * NB: as of now we do not do it, since it might be either an xmlrpcval or a plain + * php val, or a complete xml chunk, depending on usage of xmlrpc_client::send() inside which creator is called... + */ + function __construct($val, $fcode = 0, $fstr = '', $valtyp='') + { + if($fcode != 0) + { + // error response + $this->errno = $fcode; + $this->errstr = $fstr; + //$this->errstr = htmlspecialchars($fstr); // XXX: encoding probably shouldn't be done here; fix later. + } + else + { + // successful response + $this->val = $val; + if ($valtyp == '') + { + // user did not declare type of response value: try to guess it + if (is_object($this->val) && is_a($this->val, 'xmlrpcval')) + { + $this->valtyp = 'xmlrpcvals'; + } + else if (is_string($this->val)) + { + $this->valtyp = 'xml'; + + } + else + { + $this->valtyp = 'phpvals'; + } + } + else + { + // user declares type of resp value: believe him + $this->valtyp = $valtyp; + } + } + } + + /** + * Returns the error code of the response. + * @return integer the error code of this response (0 for not-error responses) + * @access public + */ + function faultCode() + { + return $this->errno; + } + + /** + * Returns the error code of the response. + * @return string the error string of this response ('' for not-error responses) + * @access public + */ + function faultString() + { + return $this->errstr; + } + + /** + * Returns the value received by the server. + * @return mixed the xmlrpcval object returned by the server. Might be an xml string or php value if the response has been created by specially configured xmlrpc_client objects + * @access public + */ + function value() + { + return $this->val; + } + + /** + * Returns an array with the cookies received from the server. + * Array has the form: $cookiename => array ('value' => $val, $attr1 => $val1, $attr2 = $val2, ...) + * with attributes being e.g. 'expires', 'path', domain'. + * NB: cookies sent as 'expired' by the server (i.e. with an expiry date in the past) + * are still present in the array. It is up to the user-defined code to decide + * how to use the received cookies, and wheter they have to be sent back with the next + * request to the server (using xmlrpc_client::setCookie) or not + * @return array array of cookies received from the server + * @access public + */ + function cookies() + { + return $this->_cookies; + } + + /** + * Returns xml representation of the response. XML prologue not included + * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed + * @return string the xml representation of the response + * @access public + */ + function serialize($charset_encoding='') + { + if ($charset_encoding != '') + $this->content_type = 'text/xml; charset=' . $charset_encoding; + else + $this->content_type = 'text/xml'; + $result = "\n"; + if($this->errno) + { + // G. Giunta 2005/2/13: let non-ASCII response messages be tolerated by clients + // by xml-encoding non ascii chars + $result .= "\n" . +"\nfaultCode\n" . $this->errno . +"\n\n\nfaultString\n" . +xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding) . "\n\n" . +"\n\n"; + } + else + { + if(!is_object($this->val) || !is_a($this->val, 'xmlrpcval')) + { + if (is_string($this->val) && $this->valtyp == 'xml') + { + $result .= "\n\n" . + $this->val . + "\n"; + } + else + { + /// @todo try to build something serializable? + die('cannot serialize xmlrpcresp objects whose content is native php values'); + } + } + else + { + $result .= "\n\n" . + $this->val->serialize($charset_encoding) . + "\n"; + } + } + $result .= "\n"; + $this->payload = $result; + return $result; + } + } + + + + class xmlrpcmsg + { + var $payload; + var $methodname; + var $params=array(); + var $debug=0; + var $content_type = 'text/xml'; + + /** + * @param string $meth the name of the method to invoke + * @param array $pars array of parameters to be paased to the method (xmlrpcval objects) + */ + function __construct($meth, $pars=0) + { + $this->methodname=$meth; + if(is_array($pars) && count($pars)>0) + { + for($i=0; $iaddParam($pars[$i]); + } + } + } + + /** + * @access private + */ + function xml_header($charset_encoding='') + { + if ($charset_encoding != '') + { + return "\n\n"; + } + else + { + return "\n\n"; + } + } + + /** + * @access private + */ + function xml_footer() + { + return ''; + } + + /** + * @access private + */ + function kindOf() + { + return 'msg'; + } + + /** + * @access private + */ + function createPayload($charset_encoding='') + { + if ($charset_encoding != '') + $this->content_type = 'text/xml; charset=' . $charset_encoding; + else + $this->content_type = 'text/xml'; + $this->payload=$this->xml_header($charset_encoding); + $this->payload.='' . $this->methodname . "\n"; + $this->payload.="\n"; + for($i=0; $iparams); $i++) + { + $p=$this->params[$i]; + $this->payload.="\n" . $p->serialize($charset_encoding) . + "\n"; + } + $this->payload.="\n"; + $this->payload.=$this->xml_footer(); + } + + /** + * Gets/sets the xmlrpc method to be invoked + * @param string $meth the method to be set (leave empty not to set it) + * @return string the method that will be invoked + * @access public + */ + function method($meth='') + { + if($meth!='') + { + $this->methodname=$meth; + } + return $this->methodname; + } + + /** + * Returns xml representation of the message. XML prologue included + * @return string the xml representation of the message, xml prologue included + * @access public + */ + function serialize($charset_encoding='') + { + $this->createPayload($charset_encoding); + return $this->payload; + } + + /** + * Add a parameter to the list of parameters to be used upon method invocation + * @param xmlrpcval $par + * @return boolean false on failure + * @access public + */ + function addParam($par) + { + // add check: do not add to self params which are not xmlrpcvals + if(is_object($par) && is_a($par, 'xmlrpcval')) + { + $this->params[]=$par; + return true; + } + else + { + return false; + } + } + + /** + * Returns the nth parameter in the message. The index zero-based. + * @param integer $i the index of the parameter to fetch (zero based) + * @return xmlrpcval the i-th parameter + * @access public + */ + function getParam($i) { return $this->params[$i]; } + + /** + * Returns the number of parameters in the messge. + * @return integer the number of parameters currently set + * @access public + */ + function getNumParams() { return count($this->params); } + + /** + * Given an open file handle, read all data available and parse it as axmlrpc response. + * NB: the file handle is not closed by this function. + * NNB: might have trouble in rare cases to work on network streams, as we + * check for a read of 0 bytes instead of feof($fp). + * But since checking for feof(null) returns false, we would risk an + * infinite loop in that case, because we cannot trust the caller + * to give us a valid pointer to an open file... + * @access public + * @return xmlrpcresp + * @todo add 2nd & 3rd param to be passed to ParseResponse() ??? + */ + function &parseResponseFile($fp) + { + $ipd=''; + while($data=fread($fp, 32768)) + { + $ipd.=$data; + } + //fclose($fp); + $r =& $this->parseResponse($ipd); + return $r; + } + + /** + * Parses HTTP headers and separates them from data. + * @access private + */ + function &parseResponseHeaders(&$data, $headers_processed=false) + { + // Support "web-proxy-tunelling" connections for https through proxies + if(preg_match('/^HTTP\/1\.[0-1] 200 Connection established/', $data)) + { + // Look for CR/LF or simple LF as line separator, + // (even though it is not valid http) + $pos = strpos($data,"\r\n\r\n"); + if($pos || is_int($pos)) + { + $bd = $pos+4; + } + else + { + $pos = strpos($data,"\n\n"); + if($pos || is_int($pos)) + { + $bd = $pos+2; + } + else + { + // No separation between response headers and body: fault? + $bd = 0; + } + } + if ($bd) + { + // this filters out all http headers from proxy. + // maybe we could take them into account, too? + $data = substr($data, $bd); + } + else + { + error_log('XML-RPC: xmlrpcmsg::parseResponse: HTTPS via proxy error, tunnel connection possibly failed'); + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (HTTPS via proxy error, tunnel connection possibly failed)'); + return $r; + } + } + + // Strip HTTP 1.1 100 Continue header if present + while(preg_match('/^HTTP\/1\.1 1[0-9]{2} /', $data)) + { + $pos = strpos($data, 'HTTP', 12); + // server sent a Continue header without any (valid) content following... + // give the client a chance to know it + if(!$pos && !is_int($pos)) // works fine in php 3, 4 and 5 + { + break; + } + $data = substr($data, $pos); + } + if(!preg_match('/^HTTP\/[0-9.]+ 200 /', $data)) + { + $errstr= substr($data, 0, strpos($data, "\n")-1); + error_log('XML-RPC: xmlrpcmsg::parseResponse: HTTP error, got response: ' .$errstr); + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (' . $errstr . ')'); + return $r; + } + + $GLOBALS['_xh']['headers'] = array(); + $GLOBALS['_xh']['cookies'] = array(); + + // be tolerant to usage of \n instead of \r\n to separate headers and data + // (even though it is not valid http) + $pos = strpos($data,"\r\n\r\n"); + if($pos || is_int($pos)) + { + $bd = $pos+4; + } + else + { + $pos = strpos($data,"\n\n"); + if($pos || is_int($pos)) + { + $bd = $pos+2; + } + else + { + // No separation between response headers and body: fault? + // we could take some action here instead of going on... + $bd = 0; + } + } + // be tolerant to line endings, and extra empty lines + $ar = split("\r?\n", trim(substr($data, 0, $pos))); + + //while(list(,$line) = @each($ar)) + while($line = current($ar)) + { + next($ar); + + // take care of multi-line headers and cookies + $arr = explode(':',$line,2); + if(count($arr) > 1) + { + $header_name = strtolower(trim($arr[0])); + /// @todo some other headers (the ones that allow a CSV list of values) + /// do allow many values to be passed using multiple header lines. + /// We should add content to $GLOBALS['_xh']['headers'][$header_name] + /// instead of replacing it for those... + if ($header_name == 'set-cookie' || $header_name == 'set-cookie2') + { + if ($header_name == 'set-cookie2') + { + // version 2 cookies: + // there could be many cookies on one line, comma separated + $cookies = explode(',', $arr[1]); + } + else + { + $cookies = array($arr[1]); + } + foreach ($cookies as $cookie) + { + // glue together all received cookies, using a comma to separate them + // (same as php does with getallheaders()) + if (isset($GLOBALS['_xh']['headers'][$header_name])) + $GLOBALS['_xh']['headers'][$header_name] .= ', ' . trim($cookie); + else + $GLOBALS['_xh']['headers'][$header_name] = trim($cookie); + // parse cookie attributes, in case user wants to correctly honour them + // feature creep: only allow rfc-compliant cookie attributes? + // @todo support for server sending multiple time cookie with same name, but using different PATHs + $cookie = explode(';', $cookie); + foreach ($cookie as $pos => $val) + { + $val = explode('=', $val, 2); + $tag = trim($val[0]); + $val = trim(@$val[1]); + /// @todo with version 1 cookies, we should strip leading and trailing " chars + if ($pos == 0) + { + $cookiename = $tag; + $GLOBALS['_xh']['cookies'][$tag] = array(); + $GLOBALS['_xh']['cookies'][$cookiename]['value'] = urldecode($val); + } + else + { + if ($tag != 'value') + { + $GLOBALS['_xh']['cookies'][$cookiename][$tag] = $val; + } + } + } + } + } + else + { + $GLOBALS['_xh']['headers'][$header_name] = trim($arr[1]); + } + } + elseif(isset($header_name)) + { + /// @todo version1 cookies might span multiple lines, thus breaking the parsing above + $GLOBALS['_xh']['headers'][$header_name] .= ' ' . trim($line); + } + } + + $data = substr($data, $bd); + + if($this->debug && count($GLOBALS['_xh']['headers'])) + { + print '
';
+					foreach($GLOBALS['_xh']['headers'] as $header => $value)
+					{
+						print htmlentities("HEADER: $header: $value\n");
+					}
+					foreach($GLOBALS['_xh']['cookies'] as $header => $value)
+					{
+						print htmlentities("COOKIE: $header={$value['value']}\n");
+					}
+					print "
\n"; + } + + // if CURL was used for the call, http headers have been processed, + // and dechunking + reinflating have been carried out + if(!$headers_processed) + { + // Decode chunked encoding sent by http 1.1 servers + if(isset($GLOBALS['_xh']['headers']['transfer-encoding']) && $GLOBALS['_xh']['headers']['transfer-encoding'] == 'chunked') + { + if(!$data = decode_chunked($data)) + { + error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to rebuild the chunked data received from server'); + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['dechunk_fail'], $GLOBALS['xmlrpcstr']['dechunk_fail']); + return $r; + } + } + + // Decode gzip-compressed stuff + // code shamelessly inspired from nusoap library by Dietrich Ayala + if(isset($GLOBALS['_xh']['headers']['content-encoding'])) + { + $GLOBALS['_xh']['headers']['content-encoding'] = str_replace('x-', '', $GLOBALS['_xh']['headers']['content-encoding']); + if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' || $GLOBALS['_xh']['headers']['content-encoding'] == 'gzip') + { + // if decoding works, use it. else assume data wasn't gzencoded + if(function_exists('gzinflate')) + { + if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data)) + { + $data = $degzdata; + if($this->debug) + print "
---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---
"; + } + elseif($GLOBALS['_xh']['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) + { + $data = $degzdata; + if($this->debug) + print "
---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---
"; + } + else + { + error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to decode the deflated data received from server'); + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['decompress_fail'], $GLOBALS['xmlrpcstr']['decompress_fail']); + return $r; + } + } + else + { + error_log('XML-RPC: xmlrpcmsg::parseResponse: the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.'); + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['cannot_decompress'], $GLOBALS['xmlrpcstr']['cannot_decompress']); + return $r; + } + } + } + } // end of 'if needed, de-chunk, re-inflate response' + + // real stupid hack to avoid PHP 4 complaining about returning NULL by ref + $r = null; + $r =& $r; + return $r; + } + + /** + * Parse the xmlrpc response contained in the string $data and return an xmlrpcresp object. + * @param string $data the xmlrpc response, eventually including http headers + * @param bool $headers_processed when true prevents parsing HTTP headers for interpretation of content-encoding and consequent decoding + * @param string $return_type decides return type, i.e. content of response->value(). Either 'xmlrpcvals', 'xml' or 'phpvals' + * @return xmlrpcresp + * @access public + */ + function &parseResponse($data='', $headers_processed=false, $return_type='xmlrpcvals') + { + if($this->debug) + { + //by maHo, replaced htmlspecialchars with htmlentities + print "
---GOT---\n" . htmlentities($data) . "\n---END---\n
"; + } + + if($data == '') + { + error_log('XML-RPC: xmlrpcmsg::parseResponse: no response received from server.'); + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_data'], $GLOBALS['xmlrpcstr']['no_data']); + return $r; + } + + $GLOBALS['_xh']=array(); + + $raw_data = $data; + // parse the HTTP headers of the response, if present, and separate them from data + if(substr($data, 0, 4) == 'HTTP') + { + $r =& $this->parseResponseHeaders($data, $headers_processed); + if ($r) + { + // failed processing of HTTP response headers + // save into response obj the full payload received, for debugging + $r->raw_data = $data; + return $r; + } + } + else + { + $GLOBALS['_xh']['headers'] = array(); + $GLOBALS['_xh']['cookies'] = array(); + } + + if($this->debug) + { + $start = strpos($data, '', $start); + $comments = substr($data, $start, $end-$start); + print "
---SERVER DEBUG INFO (DECODED) ---\n\t".htmlentities(str_replace("\n", "\n\t", base64_decode($comments)))."\n---END---\n
"; + } + } + + // be tolerant of extra whitespace in response body + $data = trim($data); + + /// @todo return an error msg if $data=='' ? + + // be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts) + // idea from Luca Mariano originally in PEARified version of the lib + $bd = false; + // Poor man's version of strrpos for php 4... + $pos = strpos($data, ''); + while($pos || is_int($pos)) + { + $bd = $pos+17; + $pos = strpos($data, '', $bd); + } + if($bd) + { + $data = substr($data, 0, $bd); + } + + // if user wants back raw xml, give it to him + if ($return_type == 'xml') + { + $r = new xmlrpcresp($data, 0, '', 'xml'); + $r->hdrs = $GLOBALS['_xh']['headers']; + $r->_cookies = $GLOBALS['_xh']['cookies']; + $r->raw_data = $raw_data; + return $r; + } + + // try to 'guestimate' the character encoding of the received response + $resp_encoding = guess_encoding(@$GLOBALS['_xh']['headers']['content-type'], $data); + + $GLOBALS['_xh']['ac']=''; + //$GLOBALS['_xh']['qt']=''; //unused... + $GLOBALS['_xh']['stack'] = array(); + $GLOBALS['_xh']['valuestack'] = array(); + $GLOBALS['_xh']['isf']=0; // 0 = OK, 1 for xmlrpc fault responses, 2 = invalid xmlrpc + $GLOBALS['_xh']['isf_reason']=''; + $GLOBALS['_xh']['rt']=''; // 'methodcall or 'methodresponse' + + // if response charset encoding is not known / supported, try to use + // the default encoding and parse the xml anyway, but log a warning... + if (!in_array($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) + // the following code might be better for mb_string enabled installs, but + // makes the lib about 200% slower... + //if (!is_valid_charset($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) + { + error_log('XML-RPC: xmlrpcmsg::parseResponse: invalid charset encoding of received response: '.$resp_encoding); + $resp_encoding = $GLOBALS['xmlrpc_defencoding']; + } + $parser = xml_parser_create($resp_encoding); + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); + // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell + // the xml parser to give us back data in the expected charset. + // What if internal encoding is not in one of the 3 allowed? + // we use the broadest one, ie. utf8 + // This allows to send data which is native in various charset, + // by extending xmlrpc_encode_entitites() and setting xmlrpc_internalencoding + if (!in_array($GLOBALS['xmlrpc_internalencoding'], array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) + { + xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8'); + } + else + { + xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']); + } + + if ($return_type == 'phpvals') + { + xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast'); + } + else + { + xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); + } + + xml_set_character_data_handler($parser, 'xmlrpc_cd'); + xml_set_default_handler($parser, 'xmlrpc_dh'); + + // first error check: xml not well formed + if(!xml_parse($parser, $data, count($data))) + { + // thanks to Peter Kocks + if((xml_get_current_line_number($parser)) == 1) + { + $errstr = 'XML error at line 1, check URL'; + } + else + { + $errstr = sprintf('XML error: %s at line %d, column %d', + xml_error_string(xml_get_error_code($parser)), + xml_get_current_line_number($parser), xml_get_current_column_number($parser)); + } + error_log($errstr); + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], $GLOBALS['xmlrpcstr']['invalid_return'].' ('.$errstr.')'); + xml_parser_free($parser); + if($this->debug) + { + print $errstr; + } + $r->hdrs = $GLOBALS['_xh']['headers']; + $r->_cookies = $GLOBALS['_xh']['cookies']; + $r->raw_data = $raw_data; + return $r; + } + xml_parser_free($parser); + // second error check: xml well formed but not xml-rpc compliant + if ($GLOBALS['_xh']['isf'] > 1) + { + if ($this->debug) + { + /// @todo echo something for user? + } + + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], + $GLOBALS['xmlrpcstr']['invalid_return'] . ' ' . $GLOBALS['_xh']['isf_reason']); + } + // third error check: parsing of the response has somehow gone boink. + // NB: shall we omit this check, since we trust the parsing code? + elseif ($return_type == 'xmlrpcvals' && !is_object($GLOBALS['_xh']['value'])) + { + // something odd has happened + // and it's time to generate a client side error + // indicating something odd went on + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], + $GLOBALS['xmlrpcstr']['invalid_return']); + } + else + { + if ($this->debug) + { + print "
---PARSED---\n";
+					// somehow htmlentities chokes on var_export, and some full html string...
+					//print htmlentitites(var_export($GLOBALS['_xh']['value'], true));
+					print htmlspecialchars(var_export($GLOBALS['_xh']['value'], true));
+					print "\n---END---
"; + } + + // note that using =& will raise an error if $GLOBALS['_xh']['st'] does not generate an object. + $v =& $GLOBALS['_xh']['value']; + + if($GLOBALS['_xh']['isf']) + { + /// @todo we should test here if server sent an int and a string, + /// and/or coerce them into such... + if ($return_type == 'xmlrpcvals') + { + $errno_v = $v->structmem('faultCode'); + $errstr_v = $v->structmem('faultString'); + $errno = $errno_v->scalarval(); + $errstr = $errstr_v->scalarval(); + } + else + { + $errno = $v['faultCode']; + $errstr = $v['faultString']; + } + + if($errno == 0) + { + // FAULT returned, errno needs to reflect that + $errno = -1; + } + + $r = new xmlrpcresp(0, $errno, $errstr); + } + else + { + $r = new xmlrpcresp($v, 0, '', $return_type); + } + } + + $r->hdrs = $GLOBALS['_xh']['headers']; + $r->_cookies = $GLOBALS['_xh']['cookies']; + $r->raw_data = $raw_data; + return $r; + } + } + + + + class xmlrpcval + { + var $me=array(); + var $mytype=0; + var $_php_class=null; + + /** + * @param mixed $val + * @param string $type any valid xmlrpc type name (lowercase). If null, 'string' is assumed + */ + function __construct($val=-1, $type='') + { + /// @todo: optimization creep - do not call addXX, do it all inline. + /// downside: booleans will not be coerced anymore + if($val!==-1 || $type!='') + { + // optimization creep: inlined all work done by constructor + switch($type) + { + case '': + $this->mytype=1; + $this->me['string']=$val; + break; + case 'i4': + case 'int': + case 'double': + case 'string': + case 'boolean': + case 'dateTime.iso8601': + case 'base64': + case 'null': + $this->mytype=1; + $this->me[$type]=$val; + break; + case 'array': + $this->mytype=2; + $this->me['array']=$val; + break; + case 'struct': + $this->mytype=3; + $this->me['struct']=$val; + break; + default: + error_log("XML-RPC: xmlrpcval::xmlrpcval: not a known type ($type)"); + } + /*if($type=='') + { + $type='string'; + } + if($GLOBALS['xmlrpcTypes'][$type]==1) + { + $this->addScalar($val,$type); + } + elseif($GLOBALS['xmlrpcTypes'][$type]==2) + { + $this->addArray($val); + } + elseif($GLOBALS['xmlrpcTypes'][$type]==3) + { + $this->addStruct($val); + }*/ + } + } + + /** + * Add a single php value to an (unitialized) xmlrpcval + * @param mixed $val + * @param string $type + * @return int 1 or 0 on failure + */ + function addScalar($val, $type='string') + { + $typeof=@$GLOBALS['xmlrpcTypes'][$type]; + if($typeof!=1) + { + error_log("XML-RPC: xmlrpcval::addScalar: not a scalar type ($type)"); + return 0; + } + + // coerce booleans into correct values + // NB: we should iether do it for datetimes, integers and doubles, too, + // or just plain remove this check, implemnted on booleans only... + if($type==$GLOBALS['xmlrpcBoolean']) + { + if(strcasecmp($val,'true')==0 || $val==1 || ($val==true && strcasecmp($val,'false'))) + { + $val=true; + } + else + { + $val=false; + } + } + + switch($this->mytype) + { + case 1: + error_log('XML-RPC: xmlrpcval::addScalar: scalar xmlrpcval can have only one value'); + return 0; + case 3: + error_log('XML-RPC: xmlrpcval::addScalar: cannot add anonymous scalar to struct xmlrpcval'); + return 0; + case 2: + // we're adding a scalar value to an array here + //$ar=$this->me['array']; + //$ar[] = new xmlrpcval($val, $type); + //$this->me['array']=$ar; + // Faster (?) avoid all the costly array-copy-by-val done here... + $this->me['array'][] = new xmlrpcval($val, $type); + return 1; + default: + // a scalar, so set the value and remember we're scalar + $this->me[$type]=$val; + $this->mytype=$typeof; + return 1; + } + } + + /** + * Add an array of xmlrpcval objects to an xmlrpcval + * @param array $vals + * @return int 1 or 0 on failure + * @access public + * + * @todo add some checking for $vals to be an array of xmlrpcvals? + */ + function addArray($vals) + { + if($this->mytype==0) + { + $this->mytype=$GLOBALS['xmlrpcTypes']['array']; + $this->me['array']=$vals; + return 1; + } + elseif($this->mytype==2) + { + // we're adding to an array here + $this->me['array'] = array_merge($this->me['array'], $vals); + return 1; + } + else + { + error_log('XML-RPC: xmlrpcval::addArray: already initialized as a [' . $this->kindOf() . ']'); + return 0; + } + } + + /** + * Add an array of named xmlrpcval objects to an xmlrpcval + * @param array $vals + * @return int 1 or 0 on failure + * @access public + * + * @todo add some checking for $vals to be an array? + */ + function addStruct($vals) + { + if($this->mytype==0) + { + $this->mytype=$GLOBALS['xmlrpcTypes']['struct']; + $this->me['struct']=$vals; + return 1; + } + elseif($this->mytype==3) + { + // we're adding to a struct here + $this->me['struct'] = array_merge($this->me['struct'], $vals); + return 1; + } + else + { + error_log('XML-RPC: xmlrpcval::addStruct: already initialized as a [' . $this->kindOf() . ']'); + return 0; + } + } + + // poor man's version of print_r ??? + // DEPRECATED! + function dump($ar) + { + foreach($ar as $key => $val) + { + echo "$key => $val
"; + if($key == 'array') + { + //while(list($key2, $val2) = each($val)) + while(list($key2, $val2) = array(key($val), current($val))) + { + echo "-- $key2 => $val2
"; + next($val); + } + } + } + } + + /** + * Returns a string containing "struct", "array" or "scalar" describing the base type of the value + * @return string + * @access public + */ + function kindOf() + { + switch($this->mytype) + { + case 3: + return 'struct'; + break; + case 2: + return 'array'; + break; + case 1: + return 'scalar'; + break; + default: + return 'undef'; + } + } + + /** + * @access private + */ + function serializedata($typ, $val, $charset_encoding='') + { + $rs=''; + switch(@$GLOBALS['xmlrpcTypes'][$typ]) + { + case 1: + switch($typ) + { + case $GLOBALS['xmlrpcBase64']: + $rs.="<${typ}>" . base64_encode($val) . ""; + break; + case $GLOBALS['xmlrpcBoolean']: + $rs.="<${typ}>" . ($val ? '1' : '0') . ""; + break; + case $GLOBALS['xmlrpcString']: + // G. Giunta 2005/2/13: do NOT use htmlentities, since + // it will produce named html entities, which are invalid xml + $rs.="<${typ}>" . xmlrpc_encode_entitites($val, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding). ""; + break; + case $GLOBALS['xmlrpcInt']: + case $GLOBALS['xmlrpcI4']: + $rs.="<${typ}>".(int)$val.""; + break; + case $GLOBALS['xmlrpcDouble']: + // avoid using standard conversion of float to string because it is locale-dependent, + // and also because the xmlrpc spec forbids exponential notation + // sprintf('%F') would be most likely ok but it is only available since PHP 4.3.10 and PHP 5.0.3. + // The code below tries its best at keeping max precision while avoiding exp notation, + // but there is of course no limit in the number of decimal places to be used... + $rs.="<${typ}>".preg_replace('/\\.?0+$/','',number_format((double)$val, 128, '.', '')).""; + break; + case $GLOBALS['xmlrpcNull']: + $rs.=""; + break; + default: + // no standard type value should arrive here, but provide a possibility + // for xmlrpcvals of unknown type... + $rs.="<${typ}>${val}"; + } + break; + case 3: + // struct + if ($this->_php_class) + { + $rs.='\n"; + } + else + { + $rs.="\n"; + } + foreach($val as $key2 => $val2) + { + $rs.=''.xmlrpc_encode_entitites($key2, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding)."\n"; + //$rs.=$this->serializeval($val2); + $rs.=$val2->serialize($charset_encoding); + $rs.="\n"; + } + $rs.=''; + break; + case 2: + // array + $rs.="\n\n"; + for($i=0; $iserializeval($val[$i]); + $rs.=$val[$i]->serialize($charset_encoding); + } + $rs.="\n"; + break; + default: + break; + } + return $rs; + } + + /** + * Returns xml representation of the value. XML prologue not included + * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed + * @return string + * @access public + */ + function serialize($charset_encoding='') + { + // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals... + //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval'))) + //{ + reset($this->me); + //list($typ, $val) = each($this->me); + $typ = key($this->me); + $val = current($this->me); + next($this->me); + return '' . $this->serializedata($typ, $val, $charset_encoding) . "\n"; + //} + } + + // DEPRECATED + function serializeval($o) + { + // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals... + //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval'))) + //{ + $ar=$o->me; + reset($ar); + //list($typ, $val) = each($ar); + $typ = key($ar); + $val = current($ar); + next($ar); + return '' . $this->serializedata($typ, $val) . "\n"; + //} + } + + /** + * Checks wheter a struct member with a given name is present. + * Works only on xmlrpcvals of type struct. + * @param string $m the name of the struct member to be looked up + * @return boolean + * @access public + */ + function structmemexists($m) + { + return array_key_exists($m, $this->me['struct']); + } + + /** + * Returns the value of a given struct member (an xmlrpcval object in itself). + * Will raise a php warning if struct member of given name does not exist + * @param string $m the name of the struct member to be looked up + * @return xmlrpcval + * @access public + */ + function structmem($m) + { + return $this->me['struct'][$m]; + } + + /** + * Reset internal pointer for xmlrpcvals of type struct. + * @access public + */ + function structreset() + { + reset($this->me['struct']); + } + + /** + * Return next member element for xmlrpcvals of type struct. + * @return xmlrpcval + * @access public + */ + function structeach() + { + //return each($this->me['struct']); + + $ret = array(key($this->me['struct']), current($this->me['struct'])); + next($this->me['struct']); + return $ret; + } + + // DEPRECATED! this code looks like it is very fragile and has not been fixed + // for a long long time. Shall we remove it for 2.0? + function getval() + { + // UNSTABLE + reset($this->me); + //list($a,$b)=each($this->me); + $a = key($this->me); + $b = current($this->me); + next($this->me); + + // contributed by I Sofer, 2001-03-24 + // add support for nested arrays to scalarval + // i've created a new method here, so as to + // preserve back compatibility + + if(is_array($b)) + { + @reset($b); + + //while(list($id,$cont) = @each($b)) + while(list($id, $cont) = array(key($b), current($b))) + { + next($b); + $b[$id] = $cont->scalarval(); + } + } + + // add support for structures directly encoding php objects + if(is_object($b)) + { + $t = get_object_vars($b); + + @reset($t); + //while(list($id,$cont) = @each($t)) + while(list($id, $cont) = array(key($t), current($t))) + { + next($t); + $t[$id] = $cont->scalarval(); + } + + @reset($t); + //while(list($id,$cont) = @each($t)) + while(list($id, $cont) = array(key($t), current($t))) + { + next($t); + @$b->$id = $cont; + } + } + // end contrib + return $b; + } + + /** + * Returns the value of a scalar xmlrpcval + * @return mixed + * @access public + */ + function scalarval() + { + reset($this->me); + //list(,$b)=each($this->me); + $b = current($this->me); + next($this->me); + return $b; + } + + /** + * Returns the type of the xmlrpcval. + * For integers, 'int' is always returned in place of 'i4' + * @return string + * @access public + */ + function scalartyp() + { + reset($this->me); + //list($a,)=each($this->me); + $a = current($this->me); + next($this->me); + + if($a==$GLOBALS['xmlrpcI4']) + { + $a=$GLOBALS['xmlrpcInt']; + } + return $a; + } + + /** + * Returns the m-th member of an xmlrpcval of struct type + * @param integer $m the index of the value to be retrieved (zero based) + * @return xmlrpcval + * @access public + */ + function arraymem($m) + { + return $this->me['array'][$m]; + } + + /** + * Returns the number of members in an xmlrpcval of array type + * @return integer + * @access public + */ + function arraysize() + { + return count($this->me['array']); + } + + /** + * Returns the number of members in an xmlrpcval of struct type + * @return integer + * @access public + */ + function structsize() + { + return count($this->me['struct']); + } + } + + + // date helpers + + /** + * Given a timestamp, return the corresponding ISO8601 encoded string. + * + * Really, timezones ought to be supported + * but the XML-RPC spec says: + * + * "Don't assume a timezone. It should be specified by the server in its + * documentation what assumptions it makes about timezones." + * + * These routines always assume localtime unless + * $utc is set to 1, in which case UTC is assumed + * and an adjustment for locale is made when encoding + * + * @param int $timet (timestamp) + * @param int $utc (0 or 1) + * @return string + */ + function iso8601_encode($timet, $utc=0) + { + if(!$utc) + { + $t=strftime("%Y%m%dT%H:%M:%S", $timet); + } + else + { + if(function_exists('gmstrftime')) + { + // gmstrftime doesn't exist in some versions + // of PHP + $t=gmstrftime("%Y%m%dT%H:%M:%S", $timet); + } + else + { + $t=strftime("%Y%m%dT%H:%M:%S", $timet-date('Z')); + } + } + return $t; + } + + /** + * Given an ISO8601 date string, return a timet in the localtime, or UTC + * @param string $idate + * @param int $utc either 0 or 1 + * @return int (datetime) + */ + function iso8601_decode($idate, $utc=0) + { + $t=0; + if(preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/', $idate, $regs)) + { + if($utc) + { + $t=gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); + } + else + { + $t=mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); + } + } + return $t; + } + + /** + * Takes an xmlrpc value in PHP xmlrpcval object format and translates it into native PHP types. + * + * Works with xmlrpc message objects as input, too. + * + * Given proper options parameter, can rebuild generic php object instances + * (provided those have been encoded to xmlrpc format using a corresponding + * option in php_xmlrpc_encode()) + * PLEASE NOTE that rebuilding php objects involves calling their constructor function. + * This means that the remote communication end can decide which php code will + * get executed on your server, leaving the door possibly open to 'php-injection' + * style of attacks (provided you have some classes defined on your server that + * might wreak havoc if instances are built outside an appropriate context). + * Make sure you trust the remote server/client before eanbling this! + * + * @author Dan Libby (dan@libby.com) + * + * @param xmlrpcval $xmlrpc_val + * @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php objects + * @return mixed + */ + function php_xmlrpc_decode($xmlrpc_val, $options=array()) + { + switch($xmlrpc_val->kindOf()) + { + case 'scalar': + if (in_array('extension_api', $options)) + { + reset($xmlrpc_val->me); + //list($typ,$val) = each($xmlrpc_val->me); + $typ = key($xmlrpc_val->me); + $val = current($xmlrpc_val->me); + next($xmlrpc_val->me); + + switch ($typ) + { + case 'dateTime.iso8601': + $xmlrpc_val->scalar = $val; + $xmlrpc_val->xmlrpc_type = 'datetime'; + $xmlrpc_val->timestamp = iso8601_decode($val); + return $xmlrpc_val; + case 'base64': + $xmlrpc_val->scalar = $val; + $xmlrpc_val->type = $typ; + return $xmlrpc_val; + default: + return $xmlrpc_val->scalarval(); + } + } + return $xmlrpc_val->scalarval(); + case 'array': + $size = $xmlrpc_val->arraysize(); + $arr = array(); + for($i = 0; $i < $size; $i++) + { + $arr[] = php_xmlrpc_decode($xmlrpc_val->arraymem($i), $options); + } + return $arr; + case 'struct': + $xmlrpc_val->structreset(); + // If user said so, try to rebuild php objects for specific struct vals. + /// @todo should we raise a warning for class not found? + // shall we check for proper subclass of xmlrpcval instead of + // presence of _php_class to detect what we can do? + if (in_array('decode_php_objs', $options) && $xmlrpc_val->_php_class != '' + && class_exists($xmlrpc_val->_php_class)) + { + $obj = @new $xmlrpc_val->_php_class; + while(list($key,$value)=$xmlrpc_val->structeach()) + { + $obj->$key = php_xmlrpc_decode($value, $options); + } + return $obj; + } + else + { + $arr = array(); + while(list($key,$value)=$xmlrpc_val->structeach()) + { + $arr[$key] = php_xmlrpc_decode($value, $options); + } + return $arr; + } + case 'msg': + $paramcount = $xmlrpc_val->getNumParams(); + $arr = array(); + for($i = 0; $i < $paramcount; $i++) + { + $arr[] = php_xmlrpc_decode($xmlrpc_val->getParam($i)); + } + return $arr; + } + } + + // This constant left here only for historical reasons... + // it was used to decide if we have to define xmlrpc_encode on our own, but + // we do not do it anymore + if(function_exists('xmlrpc_decode')) + { + define('XMLRPC_EPI_ENABLED','1'); + } + else + { + define('XMLRPC_EPI_ENABLED','0'); + } + + /** + * Takes native php types and encodes them into xmlrpc PHP object format. + * It will not re-encode xmlrpcval objects. + * + * Feature creep -- could support more types via optional type argument + * (string => datetime support has been added, ??? => base64 not yet) + * + * If given a proper options parameter, php object instances will be encoded + * into 'special' xmlrpc values, that can later be decoded into php objects + * by calling php_xmlrpc_decode() with a corresponding option + * + * @author Dan Libby (dan@libby.com) + * + * @param mixed $php_val the value to be converted into an xmlrpcval object + * @param array $options can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api' + * @return xmlrpcval + */ + function &php_xmlrpc_encode($php_val, $options=array()) + { + $type = gettype($php_val); + switch($type) + { + case 'string': + if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $php_val)) + $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcDateTime']); + else + $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcString']); + break; + case 'integer': + $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcInt']); + break; + case 'double': + $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcDouble']); + break; + // + // Add support for encoding/decoding of booleans, since they are supported in PHP + case 'boolean': + $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcBoolean']); + break; + // + case 'array': + // PHP arrays can be encoded to either xmlrpc structs or arrays, + // depending on wheter they are hashes or plain 0..n integer indexed + // A shorter one-liner would be + // $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1)); + // but execution time skyrockets! + $j = 0; + $arr = array(); + $ko = false; + foreach($php_val as $key => $val) + { + $arr[$key] =& php_xmlrpc_encode($val, $options); + if(!$ko && $key !== $j) + { + $ko = true; + } + $j++; + } + if($ko) + { + $xmlrpc_val = new xmlrpcval($arr, $GLOBALS['xmlrpcStruct']); + } + else + { + $xmlrpc_val = new xmlrpcval($arr, $GLOBALS['xmlrpcArray']); + } + break; + case 'object': + if(is_a($php_val, 'xmlrpcval')) + { + $xmlrpc_val = $php_val; + } + else + { + $arr = array(); + //while(list($k,$v) = each($php_val)) + while(list($k,$v) = array(key($php_val), current($php_val))) + { + $arr[$k] = php_xmlrpc_encode($v, $options); + next($php_val); + } + $xmlrpc_val = new xmlrpcval($arr, $GLOBALS['xmlrpcStruct']); + if (in_array('encode_php_objs', $options)) + { + // let's save original class name into xmlrpcval: + // might be useful later on... + $xmlrpc_val->_php_class = get_class($php_val); + } + } + break; + case 'NULL': + if (in_array('extension_api', $options)) + { + $xmlrpc_val = new xmlrpcval('', $GLOBALS['xmlrpcString']); + } + if (in_array('null_extension', $options)) + { + $xmlrpc_val = new xmlrpcval('', $GLOBALS['xmlrpcNull']); + } + else + { + $xmlrpc_val = new xmlrpcval(); + } + break; + case 'resource': + if (in_array('extension_api', $options)) + { + $xmlrpc_val = new xmlrpcval((int)$php_val, $GLOBALS['xmlrpcInt']); + } + else + { + $xmlrpc_val = new xmlrpcval(); + } + // catch "user function", "unknown type" + default: + // giancarlo pinerolo + // it has to return + // an empty object in case, not a boolean. + $xmlrpc_val = new xmlrpcval(); + break; + } + return $xmlrpc_val; + } + + /** + * Convert the xml representation of a method response, method request or single + * xmlrpc value into the appropriate object (a.k.a. deserialize) + * @param string $xml_val + * @param array $options + * @return mixed false on error, or an instance of either xmlrpcval, xmlrpcmsg or xmlrpcresp + */ + function php_xmlrpc_decode_xml($xml_val, $options=array()) + { + $GLOBALS['_xh'] = array(); + $GLOBALS['_xh']['ac'] = ''; + $GLOBALS['_xh']['stack'] = array(); + $GLOBALS['_xh']['valuestack'] = array(); + $GLOBALS['_xh']['params'] = array(); + $GLOBALS['_xh']['pt'] = array(); + $GLOBALS['_xh']['isf'] = 0; + $GLOBALS['_xh']['isf_reason'] = ''; + $GLOBALS['_xh']['method'] = false; + $GLOBALS['_xh']['rt'] = ''; + /// @todo 'guestimate' encoding + $parser = xml_parser_create(); + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); + // What if internal encoding is not in one of the 3 allowed? + // we use the broadest one, ie. utf8! + if (!in_array($GLOBALS['xmlrpc_internalencoding'], array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) + { + xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8'); + } + else + { + xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']); + } + xml_set_element_handler($parser, 'xmlrpc_se_any', 'xmlrpc_ee'); + xml_set_character_data_handler($parser, 'xmlrpc_cd'); + xml_set_default_handler($parser, 'xmlrpc_dh'); + if(!xml_parse($parser, $xml_val, 1)) + { + $errstr = sprintf('XML error: %s at line %d, column %d', + xml_error_string(xml_get_error_code($parser)), + xml_get_current_line_number($parser), xml_get_current_column_number($parser)); + error_log($errstr); + xml_parser_free($parser); + return false; + } + xml_parser_free($parser); + if ($GLOBALS['_xh']['isf'] > 1) // test that $GLOBALS['_xh']['value'] is an obj, too??? + { + error_log($GLOBALS['_xh']['isf_reason']); + return false; + } + switch ($GLOBALS['_xh']['rt']) + { + case 'methodresponse': + $v =& $GLOBALS['_xh']['value']; + if ($GLOBALS['_xh']['isf'] == 1) + { + $vc = $v->structmem('faultCode'); + $vs = $v->structmem('faultString'); + $r = new xmlrpcresp(0, $vc->scalarval(), $vs->scalarval()); + } + else + { + $r = new xmlrpcresp($v); + } + return $r; + case 'methodcall': + $m = new xmlrpcmsg($GLOBALS['_xh']['method']); + for($i=0; $i < count($GLOBALS['_xh']['params']); $i++) + { + $m->addParam($GLOBALS['_xh']['params'][$i]); + } + return $m; + case 'value': + return $GLOBALS['_xh']['value']; + default: + return false; + } + } + + /** + * decode a string that is encoded w/ "chunked" transfer encoding + * as defined in rfc2068 par. 19.4.6 + * code shamelessly stolen from nusoap library by Dietrich Ayala + * + * @param string $buffer the string to be decoded + * @return string + */ + function decode_chunked($buffer) + { + // length := 0 + $length = 0; + $new = ''; + + // read chunk-size, chunk-extension (if any) and crlf + // get the position of the linebreak + $chunkend = strpos($buffer,"\r\n") + 2; + $temp = substr($buffer,0,$chunkend); + $chunk_size = hexdec( trim($temp) ); + $chunkstart = $chunkend; + while($chunk_size > 0) + { + $chunkend = strpos($buffer, "\r\n", $chunkstart + $chunk_size); + + // just in case we got a broken connection + if($chunkend == false) + { + $chunk = substr($buffer,$chunkstart); + // append chunk-data to entity-body + $new .= $chunk; + $length += strlen($chunk); + break; + } + + // read chunk-data and crlf + $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart); + // append chunk-data to entity-body + $new .= $chunk; + // length := length + chunk-size + $length += strlen($chunk); + // read chunk-size and crlf + $chunkstart = $chunkend + 2; + + $chunkend = strpos($buffer,"\r\n",$chunkstart)+2; + if($chunkend == false) + { + break; //just in case we got a broken connection + } + $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart); + $chunk_size = hexdec( trim($temp) ); + $chunkstart = $chunkend; + } + return $new; + } + + /** + * xml charset encoding guessing helper function. + * Tries to determine the charset encoding of an XML chunk received over HTTP. + * NB: according to the spec (RFC 3023), if text/xml content-type is received over HTTP without a content-type, + * we SHOULD assume it is strictly US-ASCII. But we try to be more tolerant of unconforming (legacy?) clients/servers, + * which will be most probably using UTF-8 anyway... + * + * @param string $httpheaders the http Content-type header + * @param string $xmlchunk xml content buffer + * @param string $encoding_prefs comma separated list of character encodings to be used as default (when mb extension is enabled) + * + * @todo explore usage of mb_http_input(): does it detect http headers + post data? if so, use it instead of hand-detection!!! + */ + function guess_encoding($httpheader='', $xmlchunk='', $encoding_prefs=null) + { + // discussion: see http://www.yale.edu/pclt/encoding/ + // 1 - test if encoding is specified in HTTP HEADERS + + //Details: + // LWS: (\13\10)?( |\t)+ + // token: (any char but excluded stuff)+ + // quoted string: " (any char but double quotes and cointrol chars)* " + // header: Content-type = ...; charset=value(; ...)* + // where value is of type token, no LWS allowed between 'charset' and value + // Note: we do not check for invalid chars in VALUE: + // this had better be done using pure ereg as below + // Note 2: we might be removing whitespace/tabs that ought to be left in if + // the received charset is a quoted string. But nobody uses such charset names... + + /// @todo this test will pass if ANY header has charset specification, not only Content-Type. Fix it? + $matches = array(); + if(preg_match('/;\s*charset\s*=([^;]+)/i', $httpheader, $matches)) + { + return strtoupper(trim($matches[1], " \t\"")); + } + + // 2 - scan the first bytes of the data for a UTF-16 (or other) BOM pattern + // (source: http://www.w3.org/TR/2000/REC-xml-20001006) + // NOTE: actually, according to the spec, even if we find the BOM and determine + // an encoding, we should check if there is an encoding specified + // in the xml declaration, and verify if they match. + /// @todo implement check as described above? + /// @todo implement check for first bytes of string even without a BOM? (It sure looks harder than for cases WITH a BOM) + if(preg_match('/^(\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\x00\x00\xFF\xFE|\xFE\xFF\x00\x00)/', $xmlchunk)) + { + return 'UCS-4'; + } + elseif(preg_match('/^(\xFE\xFF|\xFF\xFE)/', $xmlchunk)) + { + return 'UTF-16'; + } + elseif(preg_match('/^(\xEF\xBB\xBF)/', $xmlchunk)) + { + return 'UTF-8'; + } + + // 3 - test if encoding is specified in the xml declaration + // Details: + // SPACE: (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+ + // EQ: SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]* + if (preg_match('/^<\?xml\s+version\s*=\s*'. "((?:\"[a-zA-Z0-9_.:-]+\")|(?:'[a-zA-Z0-9_.:-]+'))". + '\s+encoding\s*=\s*' . "((?:\"[A-Za-z][A-Za-z0-9._-]*\")|(?:'[A-Za-z][A-Za-z0-9._-]*'))/", + $xmlchunk, $matches)) + { + return strtoupper(substr($matches[2], 1, -1)); + } + + // 4 - if mbstring is available, let it do the guesswork + // NB: we favour finding an encoding that is compatible with what we can process + if(extension_loaded('mbstring')) + { + if($encoding_prefs) + { + $enc = mb_detect_encoding($xmlchunk, $encoding_prefs); + } + else + { + $enc = mb_detect_encoding($xmlchunk); + } + // NB: mb_detect likes to call it ascii, xml parser likes to call it US_ASCII... + // IANA also likes better US-ASCII, so go with it + if($enc == 'ASCII') + { + $enc = 'US-'.$enc; + } + return $enc; + } + else + { + // no encoding specified: as per HTTP1.1 assume it is iso-8859-1? + // Both RFC 2616 (HTTP 1.1) and 1945 (HTTP 1.0) clearly state that for text/xxx content types + // this should be the standard. And we should be getting text/xml as request and response. + // BUT we have to be backward compatible with the lib, which always used UTF-8 as default... + return $GLOBALS['xmlrpc_defencoding']; + } + } + + /** + * Checks if a given charset encoding is present in a list of encodings or + * if it is a valid subset of any encoding in the list + * @param string $encoding charset to be tested + * @param mixed $validlist comma separated list of valid charsets (or array of charsets) + */ + function is_valid_charset($encoding, $validlist) + { + $charset_supersets = array( + 'US-ASCII' => array ('ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4', + 'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8', + 'ISO-8859-9', 'ISO-8859-10', 'ISO-8859-11', 'ISO-8859-12', + 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'UTF-8', + 'EUC-JP', 'EUC-', 'EUC-KR', 'EUC-CN') + ); + if (is_string($validlist)) + $validlist = explode(',', $validlist); + if (@in_array(strtoupper($encoding), $validlist)) + return true; + else + { + if (array_key_exists($encoding, $charset_supersets)) + foreach ($validlist as $allowed) + if (in_array($allowed, $charset_supersets[$encoding])) + return true; + return false; + } + } + diff --git a/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/xmlrpc_wrappers.inc b/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/xmlrpc_wrappers.inc new file mode 100644 index 0000000..58a07e7 --- /dev/null +++ b/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/xmlrpc_wrappers.inc @@ -0,0 +1,944 @@ +' . $funcname[1]; + } + $exists = method_exists($funcname[0], $funcname[1]); + } + else + { + $plainfuncname = $funcname; + $exists = function_exists($funcname); + } + + if(!$exists) + { + error_log('XML-RPC: function to be wrapped is not defined: '.$plainfuncname); + return false; + } + else + { + // determine name of new php function + if($newfuncname == '') + { + if(is_array($funcname)) + { + if(is_string($funcname[0])) + $xmlrpcfuncname = "{$prefix}_".implode('_', $funcname); + else + $xmlrpcfuncname = "{$prefix}_".get_class($funcname[0]) . '_' . $funcname[1]; + } + else + { + $xmlrpcfuncname = "{$prefix}_$funcname"; + } + } + else + { + $xmlrpcfuncname = $newfuncname; + } + while($buildit && function_exists($xmlrpcfuncname)) + { + $xmlrpcfuncname .= 'x'; + } + + // start to introspect PHP code + if(is_array($funcname)) + { + $func = new ReflectionMethod($funcname[0], $funcname[1]); + if($func->isPrivate()) + { + error_log('XML-RPC: method to be wrapped is private: '.$plainfuncname); + return false; + } + if($func->isProtected()) + { + error_log('XML-RPC: method to be wrapped is protected: '.$plainfuncname); + return false; + } + if($func->isConstructor()) + { + error_log('XML-RPC: method to be wrapped is the constructor: '.$plainfuncname); + return false; + } + if($func->isDestructor()) + { + error_log('XML-RPC: method to be wrapped is the destructor: '.$plainfuncname); + return false; + } + if($func->isAbstract()) + { + error_log('XML-RPC: method to be wrapped is abstract: '.$plainfuncname); + return false; + } + /// @todo add more checks for static vs. nonstatic? + } + else + { + $func = new ReflectionFunction($funcname); + } + if($func->isInternal()) + { + // Note: from PHP 5.1.0 onward, we will possibly be able to use invokeargs + // instead of getparameters to fully reflect internal php functions ? + error_log('XML-RPC: function to be wrapped is internal: '.$plainfuncname); + return false; + } + + // retrieve parameter names, types and description from javadoc comments + + // function description + $desc = ''; + // type of return val: by default 'any' + $returns = $GLOBALS['xmlrpcValue']; + // desc of return val + $returnsDocs = ''; + // type + name of function parameters + $paramDocs = array(); + + $docs = $func->getDocComment(); + if($docs != '') + { + $docs = explode("\n", $docs); + $i = 0; + foreach($docs as $doc) + { + $doc = trim($doc, " \r\t/*"); + if(strlen($doc) && strpos($doc, '@') !== 0 && !$i) + { + if($desc) + { + $desc .= "\n"; + } + $desc .= $doc; + } + elseif(strpos($doc, '@param') === 0) + { + // syntax: @param type [$name] desc + if(preg_match('/@param\s+(\S+)(\s+\$\S+)?\s+(.+)/', $doc, $matches)) + { + if(strpos($matches[1], '|')) + { + //$paramDocs[$i]['type'] = explode('|', $matches[1]); + $paramDocs[$i]['type'] = 'mixed'; + } + else + { + $paramDocs[$i]['type'] = $matches[1]; + } + $paramDocs[$i]['name'] = trim($matches[2]); + $paramDocs[$i]['doc'] = $matches[3]; + } + $i++; + } + elseif(strpos($doc, '@return') === 0) + { + // syntax: @return type desc + //$returns = preg_split('/\s+/', $doc); + if(preg_match('/@return\s+(\S+)\s+(.+)/', $doc, $matches)) + { + $returns = php_2_xmlrpc_type($matches[1]); + if(isset($matches[2])) + { + $returnsDocs = $matches[2]; + } + } + } + } + } + + // execute introspection of actual function prototype + $params = array(); + $i = 0; + foreach($func->getParameters() as $paramobj) + { + $params[$i] = array(); + $params[$i]['name'] = '$'.$paramobj->getName(); + $params[$i]['isoptional'] = $paramobj->isOptional(); + $i++; + } + + + // start building of PHP code to be eval'd + $innercode = ''; + $i = 0; + $parsvariations = array(); + $pars = array(); + $pnum = count($params); + foreach($params as $param) + { + if (isset($paramDocs[$i]['name']) && $paramDocs[$i]['name'] && strtolower($paramDocs[$i]['name']) != strtolower($param['name'])) + { + // param name from phpdoc info does not match param definition! + $paramDocs[$i]['type'] = 'mixed'; + } + + if($param['isoptional']) + { + // this particular parameter is optional. save as valid previous list of parameters + $innercode .= "if (\$paramcount > $i) {\n"; + $parsvariations[] = $pars; + } + $innercode .= "\$p$i = \$msg->getParam($i);\n"; + if ($decode_php_objects) + { + $innercode .= "if (\$p{$i}->kindOf() == 'scalar') \$p$i = \$p{$i}->scalarval(); else \$p$i = php_{$prefix}_decode(\$p$i, array('decode_php_objs'));\n"; + } + else + { + $innercode .= "if (\$p{$i}->kindOf() == 'scalar') \$p$i = \$p{$i}->scalarval(); else \$p$i = php_{$prefix}_decode(\$p$i);\n"; + } + + $pars[] = "\$p$i"; + $i++; + if($param['isoptional']) + { + $innercode .= "}\n"; + } + if($i == $pnum) + { + // last allowed parameters combination + $parsvariations[] = $pars; + } + } + + $sigs = array(); + $psigs = array(); + if(count($parsvariations) == 0) + { + // only known good synopsis = no parameters + $parsvariations[] = array(); + $minpars = 0; + } + else + { + $minpars = count($parsvariations[0]); + } + + if($minpars) + { + // add to code the check for min params number + // NB: this check needs to be done BEFORE decoding param values + $innercode = "\$paramcount = \$msg->getNumParams();\n" . + "if (\$paramcount < $minpars) return new {$prefix}resp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}');\n" . $innercode; + } + else + { + $innercode = "\$paramcount = \$msg->getNumParams();\n" . $innercode; + } + + $innercode .= "\$np = false;\n"; + // since there are no closures in php, if we are given an object instance, + // we store a pointer to it in a global var... + if ( is_array($funcname) && is_object($funcname[0]) ) + { + $GLOBALS['xmlrpcWPFObjHolder'][$xmlrpcfuncname] =& $funcname[0]; + $innercode .= "\$obj =& \$GLOBALS['xmlrpcWPFObjHolder']['$xmlrpcfuncname'];\n"; + $realfuncname = '$obj->'.$funcname[1]; + } + else + { + $realfuncname = $plainfuncname; + } + foreach($parsvariations as $pars) + { + $innercode .= "if (\$paramcount == " . count($pars) . ") \$retval = {$catch_warnings}$realfuncname(" . implode(',', $pars) . "); else\n"; + // build a 'generic' signature (only use an appropriate return type) + $sig = array($returns); + $psig = array($returnsDocs); + for($i=0; $i < count($pars); $i++) + { + if (isset($paramDocs[$i]['type'])) + { + $sig[] = php_2_xmlrpc_type($paramDocs[$i]['type']); + } + else + { + $sig[] = $GLOBALS['xmlrpcValue']; + } + $psig[] = isset($paramDocs[$i]['doc']) ? $paramDocs[$i]['doc'] : ''; + } + $sigs[] = $sig; + $psigs[] = $psig; + } + $innercode .= "\$np = true;\n"; + $innercode .= "if (\$np) return new {$prefix}resp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}'); else {\n"; + //$innercode .= "if (\$_xmlrpcs_error_occurred) return new xmlrpcresp(0, $GLOBALS['xmlrpcerr']user, \$_xmlrpcs_error_occurred); else\n"; + $innercode .= "if (is_a(\$retval, '{$prefix}resp')) return \$retval; else\n"; + if($returns == $GLOBALS['xmlrpcDateTime'] || $returns == $GLOBALS['xmlrpcBase64']) + { + $innercode .= "return new {$prefix}resp(new {$prefix}val(\$retval, '$returns'));"; + } + else + { + if ($encode_php_objects) + $innercode .= "return new {$prefix}resp(php_{$prefix}_encode(\$retval, array('encode_php_objs')));\n"; + else + $innercode .= "return new {$prefix}resp(php_{$prefix}_encode(\$retval));\n"; + } + // shall we exclude functions returning by ref? + // if($func->returnsReference()) + // return false; + $code = "function $xmlrpcfuncname(\$msg) {\n" . $innercode . "}\n}"; + //print_r($code); + if ($buildit) + { + $allOK = 0; + eval($code.'$allOK=1;'); + // alternative + //$xmlrpcfuncname = create_function('$m', $innercode); + + if(!$allOK) + { + error_log('XML-RPC: could not create function '.$xmlrpcfuncname.' to wrap php function '.$plainfuncname); + return false; + } + } + + /// @todo examine if $paramDocs matches $parsvariations and build array for + /// usage as method signature, plus put together a nice string for docs + + $ret = array('function' => $xmlrpcfuncname, 'signature' => $sigs, 'docstring' => $desc, 'signature_docs' => $psigs, 'source' => $code); + return $ret; + } + } + + /** + * Given a user-defined PHP class or php object, map its methods onto a list of + * PHP 'wrapper' functions that can be exposed as xmlrpc methods from an xmlrpc_server + * object and called from remote clients (as well as their corresponding signature info). + * + * @param mixed $classname the name of the class whose methods are to be exposed as xmlrpc methods, or an object instance of that class + * @param array $extra_options see the docs for wrap_php_method for more options + * string method_type 'static', 'nonstatic', 'all' and 'auto' (default); the latter will switch between static and non-static depending on wheter $classname is a class name or object instance + * @return array or false on failure + * + * @todo get_class_methods will return both static and non-static methods. + * we have to differentiate the action, depending on wheter we recived a class name or object + */ + function wrap_php_class($classname, $extra_options=array()) + { + $methodfilter = isset($extra_options['method_filter']) ? $extra_options['method_filter'] : ''; + $methodtype = isset($extra_options['method_type']) ? $extra_options['method_type'] : 'auto'; + + if(version_compare(phpversion(), '5.0.3') == -1) + { + // up to php 5.0.3 some useful reflection methods were missing + error_log('XML-RPC: cannot not wrap php functions unless running php version bigger than 5.0.3'); + return false; + } + + $result = array(); + $mlist = get_class_methods($classname); + foreach($mlist as $mname) + { + if ($methodfilter == '' || preg_match($methodfilter, $mname)) + { + // echo $mlist."\n"; + $func = new ReflectionMethod($classname, $mname); + if(!$func->isPrivate() && !$func->isProtected() && !$func->isConstructor() && !$func->isDestructor() && !$func->isAbstract()) + { + if(($func->isStatic && ($methodtype == 'all' || $methodtype == 'static' || ($methodtype == 'auto' && is_string($classname)))) || + (!$func->isStatic && ($methodtype == 'all' || $methodtype == 'nonstatic' || ($methodtype == 'auto' && is_object($classname))))) + { + $methodwrap = wrap_php_function(array($classname, $mname), '', $extra_options); + if ( $methodwrap ) + { + $result[$methodwrap['function']] = $methodwrap['function']; + } + } + } + } + } + return $result; + } + + /** + * Given an xmlrpc client and a method name, register a php wrapper function + * that will call it and return results using native php types for both + * params and results. The generated php function will return an xmlrpcresp + * oject for failed xmlrpc calls + * + * Known limitations: + * - server must support system.methodsignature for the wanted xmlrpc method + * - for methods that expose many signatures, only one can be picked (we + * could in priciple check if signatures differ only by number of params + * and not by type, but it would be more complication than we can spare time) + * - nested xmlrpc params: the caller of the generated php function has to + * encode on its own the params passed to the php function if these are structs + * or arrays whose (sub)members include values of type datetime or base64 + * + * Notes: the connection properties of the given client will be copied + * and reused for the connection used during the call to the generated + * php function. + * Calling the generated php function 'might' be slow: a new xmlrpc client + * is created on every invocation and an xmlrpc-connection opened+closed. + * An extra 'debug' param is appended to param list of xmlrpc method, useful + * for debugging purposes. + * + * @param xmlrpc_client $client an xmlrpc client set up correctly to communicate with target server + * @param string $methodname the xmlrpc method to be mapped to a php function + * @param array $extra_options array of options that specify conversion details. valid ptions include + * integer signum the index of the method signature to use in mapping (if method exposes many sigs) + * integer timeout timeout (in secs) to be used when executing function/calling remote method + * string protocol 'http' (default), 'http11' or 'https' + * string new_function_name the name of php function to create. If unsepcified, lib will pick an appropriate name + * string return_source if true return php code w. function definition instead fo function name + * bool encode_php_objs let php objects be sent to server using the 'improved' xmlrpc notation, so server can deserialize them as php objects + * bool decode_php_objs --- WARNING !!! possible security hazard. only use it with trusted servers --- + * mixed return_on_fault a php value to be returned when the xmlrpc call fails/returns a fault response (by default the xmlrpcresp object is returned in this case). If a string is used, '%faultCode%' and '%faultString%' tokens will be substituted with actual error values + * bool debug set it to 1 or 2 to see debug results of querying server for method synopsis + * @return string the name of the generated php function (or false) - OR AN ARRAY... + */ + function wrap_xmlrpc_method($client, $methodname, $extra_options=0, $timeout=0, $protocol='', $newfuncname='') + { + // mind numbing: let caller use sane calling convention (as per javadoc, 3 params), + // OR the 2.0 calling convention (no options) - we really love backward compat, don't we? + if (!is_array($extra_options)) + { + $signum = $extra_options; + $extra_options = array(); + } + else + { + $signum = isset($extra_options['signum']) ? (int)$extra_options['signum'] : 0; + $timeout = isset($extra_options['timeout']) ? (int)$extra_options['timeout'] : 0; + $protocol = isset($extra_options['protocol']) ? $extra_options['protocol'] : ''; + $newfuncname = isset($extra_options['new_function_name']) ? $extra_options['new_function_name'] : ''; + } + //$encode_php_objects = in_array('encode_php_objects', $extra_options); + //$verbatim_client_copy = in_array('simple_client_copy', $extra_options) ? 1 : + // in_array('build_class_code', $extra_options) ? 2 : 0; + + $encode_php_objects = isset($extra_options['encode_php_objs']) ? (bool)$extra_options['encode_php_objs'] : false; + $decode_php_objects = isset($extra_options['decode_php_objs']) ? (bool)$extra_options['decode_php_objs'] : false; + $simple_client_copy = isset($extra_options['simple_client_copy']) ? (int)($extra_options['simple_client_copy']) : 0; + $buildit = isset($extra_options['return_source']) ? !($extra_options['return_source']) : true; + $prefix = isset($extra_options['prefix']) ? $extra_options['prefix'] : 'xmlrpc'; + if (isset($extra_options['return_on_fault'])) + { + $decode_fault = true; + $fault_response = $extra_options['return_on_fault']; + } + else + { + $decode_fault = false; + $fault_response = ''; + } + $debug = isset($extra_options['debug']) ? ($extra_options['debug']) : 0; + + $msgclass = $prefix.'msg'; + $valclass = $prefix.'val'; + $decodefunc = 'php_'.$prefix.'_decode'; + + $msg = new $msgclass('system.methodSignature'); + $msg->addparam(new $valclass($methodname)); + $client->setDebug($debug); + $response =& $client->send($msg, $timeout, $protocol); + if($response->faultCode()) + { + error_log('XML-RPC: could not retrieve method signature from remote server for method '.$methodname); + return false; + } + else + { + $msig = $response->value(); + if ($client->return_type != 'phpvals') + { + $msig = $decodefunc($msig); + } + if(!is_array($msig) || count($msig) <= $signum) + { + error_log('XML-RPC: could not retrieve method signature nr.'.$signum.' from remote server for method '.$methodname); + return false; + } + else + { + // pick a suitable name for the new function, avoiding collisions + if($newfuncname != '') + { + $xmlrpcfuncname = $newfuncname; + } + else + { + // take care to insure that methodname is translated to valid + // php function name + $xmlrpcfuncname = $prefix.'_'.preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'), + array('_', ''), $methodname); + } + while($buildit && function_exists($xmlrpcfuncname)) + { + $xmlrpcfuncname .= 'x'; + } + + $msig = $msig[$signum]; + $mdesc = ''; + // if in 'offline' mode, get method description too. + // in online mode, favour speed of operation + if(!$buildit) + { + $msg = new $msgclass('system.methodHelp'); + $msg->addparam(new $valclass($methodname)); + $response =& $client->send($msg, $timeout, $protocol); + if (!$response->faultCode()) + { + $mdesc = $response->value(); + if ($client->return_type != 'phpvals') + { + $mdesc = $mdesc->scalarval(); + } + } + } + + $results = build_remote_method_wrapper_code($client, $methodname, + $xmlrpcfuncname, $msig, $mdesc, $timeout, $protocol, $simple_client_copy, + $prefix, $decode_php_objects, $encode_php_objects, $decode_fault, + $fault_response); + + //print_r($code); + if ($buildit) + { + $allOK = 0; + eval($results['source'].'$allOK=1;'); + // alternative + //$xmlrpcfuncname = create_function('$m', $innercode); + if($allOK) + { + return $xmlrpcfuncname; + } + else + { + error_log('XML-RPC: could not create function '.$xmlrpcfuncname.' to wrap remote method '.$methodname); + return false; + } + } + else + { + $results['function'] = $xmlrpcfuncname; + return $results; + } + } + } + } + + /** + * Similar to wrap_xmlrpc_method, but will generate a php class that wraps + * all xmlrpc methods exposed by the remote server as own methods. + * For more details see wrap_xmlrpc_method. + * @param xmlrpc_client $client the client obj all set to query the desired server + * @param array $extra_options list of options for wrapped code + * @return mixed false on error, the name of the created class if all ok or an array with code, class name and comments (if the appropriatevoption is set in extra_options) + */ + function wrap_xmlrpc_server($client, $extra_options=array()) + { + $methodfilter = isset($extra_options['method_filter']) ? $extra_options['method_filter'] : ''; + //$signum = isset($extra_options['signum']) ? (int)$extra_options['signum'] : 0; + $timeout = isset($extra_options['timeout']) ? (int)$extra_options['timeout'] : 0; + $protocol = isset($extra_options['protocol']) ? $extra_options['protocol'] : ''; + $newclassname = isset($extra_options['new_class_name']) ? $extra_options['new_class_name'] : ''; + $encode_php_objects = isset($extra_options['encode_php_objs']) ? (bool)$extra_options['encode_php_objs'] : false; + $decode_php_objects = isset($extra_options['decode_php_objs']) ? (bool)$extra_options['decode_php_objs'] : false; + $verbatim_client_copy = isset($extra_options['simple_client_copy']) ? !($extra_options['simple_client_copy']) : true; + $buildit = isset($extra_options['return_source']) ? !($extra_options['return_source']) : true; + $prefix = isset($extra_options['prefix']) ? $extra_options['prefix'] : 'xmlrpc'; + + $msgclass = $prefix.'msg'; + //$valclass = $prefix.'val'; + $decodefunc = 'php_'.$prefix.'_decode'; + + $msg = new $msgclass('system.listMethods'); + $response =& $client->send($msg, $timeout, $protocol); + if($response->faultCode()) + { + error_log('XML-RPC: could not retrieve method list from remote server'); + return false; + } + else + { + $mlist = $response->value(); + if ($client->return_type != 'phpvals') + { + $mlist = $decodefunc($mlist); + } + if(!is_array($mlist) || !count($mlist)) + { + error_log('XML-RPC: could not retrieve meaningful method list from remote server'); + return false; + } + else + { + // pick a suitable name for the new function, avoiding collisions + if($newclassname != '') + { + $xmlrpcclassname = $newclassname; + } + else + { + $xmlrpcclassname = $prefix.'_'.preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'), + array('_', ''), $client->server).'_client'; + } + while($buildit && class_exists($xmlrpcclassname)) + { + $xmlrpcclassname .= 'x'; + } + + /// @todo add function setdebug() to new class, to enable/disable debugging + $source = "class $xmlrpcclassname\n{\nvar \$client;\n\n"; + $source .= "function $xmlrpcclassname()\n{\n"; + $source .= build_client_wrapper_code($client, $verbatim_client_copy, $prefix); + $source .= "\$this->client =& \$client;\n}\n\n"; + $opts = array('simple_client_copy' => 2, 'return_source' => true, + 'timeout' => $timeout, 'protocol' => $protocol, + 'encode_php_objs' => $encode_php_objects, 'prefix' => $prefix, + 'decode_php_objs' => $decode_php_objects + ); + /// @todo build javadoc for class definition, too + foreach($mlist as $mname) + { + if ($methodfilter == '' || preg_match($methodfilter, $mname)) + { + $opts['new_function_name'] = preg_replace(array('/\./', '/[^a-zA-Z0-9_\x7f-\xff]/'), + array('_', ''), $mname); + $methodwrap = wrap_xmlrpc_method($client, $mname, $opts); + if ($methodwrap) + { + if (!$buildit) + { + $source .= $methodwrap['docstring']; + } + $source .= $methodwrap['source']."\n"; + } + else + { + error_log('XML-RPC: will not create class method to wrap remote method '.$mname); + } + } + } + $source .= "}\n"; + if ($buildit) + { + $allOK = 0; + eval($source.'$allOK=1;'); + // alternative + //$xmlrpcfuncname = create_function('$m', $innercode); + if($allOK) + { + return $xmlrpcclassname; + } + else + { + error_log('XML-RPC: could not create class '.$xmlrpcclassname.' to wrap remote server '.$client->server); + return false; + } + } + else + { + return array('class' => $xmlrpcclassname, 'code' => $source, 'docstring' => ''); + } + } + } + } + + /** + * Given the necessary info, build php code that creates a new function to + * invoke a remote xmlrpc method. + * Take care that no full checking of input parameters is done to ensure that + * valid php code is emitted. + * Note: real spaghetti code follows... + * @access private + */ + function build_remote_method_wrapper_code($client, $methodname, $xmlrpcfuncname, + $msig, $mdesc='', $timeout=0, $protocol='', $client_copy_mode=0, $prefix='xmlrpc', + $decode_php_objects=false, $encode_php_objects=false, $decode_fault=false, + $fault_response='') + { + $code = "function $xmlrpcfuncname ("; + if ($client_copy_mode < 2) + { + // client copy mode 0 or 1 == partial / full client copy in emitted code + $innercode = build_client_wrapper_code($client, $client_copy_mode, $prefix); + $innercode .= "\$client->setDebug(\$debug);\n"; + $this_ = ''; + } + else + { + // client copy mode 2 == no client copy in emitted code + $innercode = ''; + $this_ = 'this->'; + } + $innercode .= "\$msg = new {$prefix}msg('$methodname');\n"; + + if ($mdesc != '') + { + // take care that PHP comment is not terminated unwillingly by method description + $mdesc = "/**\n* ".str_replace('*/', '* /', $mdesc)."\n"; + } + else + { + $mdesc = "/**\nFunction $xmlrpcfuncname\n"; + } + + // param parsing + $plist = array(); + $pcount = count($msig); + for($i = 1; $i < $pcount; $i++) + { + $plist[] = "\$p$i"; + $ptype = $msig[$i]; + if($ptype == 'i4' || $ptype == 'int' || $ptype == 'boolean' || $ptype == 'double' || + $ptype == 'string' || $ptype == 'dateTime.iso8601' || $ptype == 'base64' || $ptype == 'null') + { + // only build directly xmlrpcvals when type is known and scalar + $innercode .= "\$p$i = new {$prefix}val(\$p$i, '$ptype');\n"; + } + else + { + if ($encode_php_objects) + { + $innercode .= "\$p$i =& php_{$prefix}_encode(\$p$i, array('encode_php_objs'));\n"; + } + else + { + $innercode .= "\$p$i =& php_{$prefix}_encode(\$p$i);\n"; + } + } + $innercode .= "\$msg->addparam(\$p$i);\n"; + $mdesc .= '* @param '.xmlrpc_2_php_type($ptype)." \$p$i\n"; + } + if ($client_copy_mode < 2) + { + $plist[] = '$debug=0'; + $mdesc .= "* @param int \$debug when 1 (or 2) will enable debugging of the underlying {$prefix} call (defaults to 0)\n"; + } + $plist = implode(', ', $plist); + $mdesc .= '* @return '.xmlrpc_2_php_type($msig[0])." (or an {$prefix}resp obj instance if call fails)\n*/\n"; + + $innercode .= "\$res =& \${$this_}client->send(\$msg, $timeout, '$protocol');\n"; + if ($decode_fault) + { + if (is_string($fault_response) && ((strpos($fault_response, '%faultCode%') !== false) || (strpos($fault_response, '%faultString%') !== false))) + { + $respcode = "str_replace(array('%faultCode%', '%faultString%'), array(\$res->faultCode(), \$res->faultString()), '".str_replace("'", "''", $fault_response)."')"; + } + else + { + $respcode = var_export($fault_response, true); + } + } + else + { + $respcode = '$res'; + } + if ($decode_php_objects) + { + $innercode .= "if (\$res->faultcode()) return $respcode; else return php_{$prefix}_decode(\$res->value(), array('decode_php_objs'));"; + } + else + { + $innercode .= "if (\$res->faultcode()) return $respcode; else return php_{$prefix}_decode(\$res->value());"; + } + + $code = $code . $plist. ") {\n" . $innercode . "\n}\n"; + + return array('source' => $code, 'docstring' => $mdesc); + } + + /** + * Given necessary info, generate php code that will rebuild a client object + * Take care that no full checking of input parameters is done to ensure that + * valid php code is emitted. + * @access private + */ + function build_client_wrapper_code($client, $verbatim_client_copy, $prefix='xmlrpc') + { + $code = "\$client = new {$prefix}_client('".str_replace("'", "\'", $client->path). + "', '" . str_replace("'", "\'", $client->server) . "', $client->port);\n"; + + // copy all client fields to the client that will be generated runtime + // (this provides for future expansion or subclassing of client obj) + if ($verbatim_client_copy) + { + foreach($client as $fld => $val) + { + if($fld != 'debug' && $fld != 'return_type') + { + $val = var_export($val, true); + $code .= "\$client->$fld = $val;\n"; + } + } + } + // only make sure that client always returns the correct data type + $code .= "\$client->return_type = '{$prefix}vals';\n"; + //$code .= "\$client->setDebug(\$debug);\n"; + return $code; + } +?> diff --git a/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/xmlrpcs.inc b/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/xmlrpcs.inc new file mode 100644 index 0000000..6ac3764 --- /dev/null +++ b/helper-php/DTLNSL_helper_scripts/helper/phpxmlrpclib/xmlrpcs.inc @@ -0,0 +1,1206 @@ + +// $Id: xmlrpcs.inc,v 1.71 2008/10/29 23:41:28 ggiunta Exp $ + +// Copyright (c) 1999,2000,2002 Edd Dumbill. +// All rights reserved. +// +// 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 "XML-RPC for PHP" 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 COPYRIGHT HOLDERS AND CONTRIBUTORS +// "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 +// REGENTS OR 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. + + // XML RPC Server class + // requires: xmlrpc.inc + + $GLOBALS['xmlrpcs_capabilities'] = array( + // xmlrpc spec: always supported + 'xmlrpc' => new xmlrpcval(array( + 'specUrl' => new xmlrpcval('http://www.xmlrpc.com/spec', 'string'), + 'specVersion' => new xmlrpcval(1, 'int') + ), 'struct'), + // if we support system.xxx functions, we always support multicall, too... + // Note that, as of 2006/09/17, the following URL does not respond anymore + 'system.multicall' => new xmlrpcval(array( + 'specUrl' => new xmlrpcval('http://www.xmlrpc.com/discuss/msgReader$1208', 'string'), + 'specVersion' => new xmlrpcval(1, 'int') + ), 'struct'), + // introspection: version 2! we support 'mixed', too + 'introspection' => new xmlrpcval(array( + 'specUrl' => new xmlrpcval('http://phpxmlrpc.sourceforge.net/doc-2/ch10.html', 'string'), + 'specVersion' => new xmlrpcval(2, 'int') + ), 'struct') + ); + + /* Functions that implement system.XXX methods of xmlrpc servers */ + $_xmlrpcs_getCapabilities_sig=array(array($GLOBALS['xmlrpcStruct'])); + $_xmlrpcs_getCapabilities_doc='This method lists all the capabilites that the XML-RPC server has: the (more or less standard) extensions to the xmlrpc spec that it adheres to'; + $_xmlrpcs_getCapabilities_sdoc=array(array('list of capabilities, described as structs with a version number and url for the spec')); + function _xmlrpcs_getCapabilities($server, $m=null) + { + $outAr = $GLOBALS['xmlrpcs_capabilities']; + // NIL extension + if ($GLOBALS['xmlrpc_null_extension']) { + $outAr['nil'] = new xmlrpcval(array( + 'specUrl' => new xmlrpcval('http://www.ontosys.com/xml-rpc/extensions.php', 'string'), + 'specVersion' => new xmlrpcval(1, 'int') + ), 'struct'); + } + return new xmlrpcresp(new xmlrpcval($outAr, 'struct')); + } + + // listMethods: signature was either a string, or nothing. + // The useless string variant has been removed + $_xmlrpcs_listMethods_sig=array(array($GLOBALS['xmlrpcArray'])); + $_xmlrpcs_listMethods_doc='This method lists all the methods that the XML-RPC server knows how to dispatch'; + $_xmlrpcs_listMethods_sdoc=array(array('list of method names')); + function _xmlrpcs_listMethods($server, $m=null) // if called in plain php values mode, second param is missing + { + + $outAr=array(); + foreach($server->dmap as $key => $val) + { + $outAr[] = new xmlrpcval($key, 'string'); + } + if($server->allow_system_funcs) + { + foreach($GLOBALS['_xmlrpcs_dmap'] as $key => $val) + { + $outAr[] = new xmlrpcval($key, 'string'); + } + } + return new xmlrpcresp(new xmlrpcval($outAr, 'array')); + } + + $_xmlrpcs_methodSignature_sig=array(array($GLOBALS['xmlrpcArray'], $GLOBALS['xmlrpcString'])); + $_xmlrpcs_methodSignature_doc='Returns an array of known signatures (an array of arrays) for the method name passed. If no signatures are known, returns a none-array (test for type != array to detect missing signature)'; + $_xmlrpcs_methodSignature_sdoc=array(array('list of known signatures, each sig being an array of xmlrpc type names', 'name of method to be described')); + function _xmlrpcs_methodSignature($server, $m) + { + // let accept as parameter both an xmlrpcval or string + if (is_object($m)) + { + $methName=$m->getParam(0); + $methName=$methName->scalarval(); + } + else + { + $methName=$m; + } + if(strpos($methName, "system.") === 0) + { + $dmap=$GLOBALS['_xmlrpcs_dmap']; $sysCall=1; + } + else + { + $dmap=$server->dmap; $sysCall=0; + } + if(isset($dmap[$methName])) + { + if(isset($dmap[$methName]['signature'])) + { + $sigs=array(); + foreach($dmap[$methName]['signature'] as $inSig) + { + $cursig=array(); + foreach($inSig as $sig) + { + $cursig[] = new xmlrpcval($sig, 'string'); + } + $sigs[] = new xmlrpcval($cursig, 'array'); + } + $r = new xmlrpcresp(new xmlrpcval($sigs, 'array')); + } + else + { + // NB: according to the official docs, we should be returning a + // "none-array" here, which means not-an-array + $r = new xmlrpcresp(new xmlrpcval('undef', 'string')); + } + } + else + { + $r = new xmlrpcresp(0,$GLOBALS['xmlrpcerr']['introspect_unknown'], $GLOBALS['xmlrpcstr']['introspect_unknown']); + } + return $r; + } + + $_xmlrpcs_methodHelp_sig=array(array($GLOBALS['xmlrpcString'], $GLOBALS['xmlrpcString'])); + $_xmlrpcs_methodHelp_doc='Returns help text if defined for the method passed, otherwise returns an empty string'; + $_xmlrpcs_methodHelp_sdoc=array(array('method description', 'name of the method to be described')); + function _xmlrpcs_methodHelp($server, $m) + { + // let accept as parameter both an xmlrpcval or string + if (is_object($m)) + { + $methName=$m->getParam(0); + $methName=$methName->scalarval(); + } + else + { + $methName=$m; + } + if(strpos($methName, "system.") === 0) + { + $dmap=$GLOBALS['_xmlrpcs_dmap']; $sysCall=1; + } + else + { + $dmap=$server->dmap; $sysCall=0; + } + if(isset($dmap[$methName])) + { + if(isset($dmap[$methName]['docstring'])) + { + $r = new xmlrpcresp(new xmlrpcval($dmap[$methName]['docstring']), 'string'); + } + else + { + $r = new xmlrpcresp(new xmlrpcval('', 'string')); + } + } + else + { + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['introspect_unknown'], $GLOBALS['xmlrpcstr']['introspect_unknown']); + } + return $r; + } + + $_xmlrpcs_multicall_sig = array(array($GLOBALS['xmlrpcArray'], $GLOBALS['xmlrpcArray'])); + $_xmlrpcs_multicall_doc = 'Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details'; + $_xmlrpcs_multicall_sdoc = array(array('list of response structs, where each struct has the usual members', 'list of calls, with each call being represented as a struct, with members "methodname" and "params"')); + function _xmlrpcs_multicall_error($err) + { + if(is_string($err)) + { + $str = $GLOBALS['xmlrpcstr']["multicall_${err}"]; + $code = $GLOBALS['xmlrpcerr']["multicall_${err}"]; + } + else + { + $code = $err->faultCode(); + $str = $err->faultString(); + } + $struct = array(); + $struct['faultCode'] = new xmlrpcval($code, 'int'); + $struct['faultString'] = new xmlrpcval($str, 'string'); + return new xmlrpcval($struct, 'struct'); + } + + function _xmlrpcs_multicall_do_call($server, $call) + { + if($call->kindOf() != 'struct') + { + return _xmlrpcs_multicall_error('notstruct'); + } + $methName = @$call->structmem('methodName'); + if(!$methName) + { + return _xmlrpcs_multicall_error('nomethod'); + } + if($methName->kindOf() != 'scalar' || $methName->scalartyp() != 'string') + { + return _xmlrpcs_multicall_error('notstring'); + } + if($methName->scalarval() == 'system.multicall') + { + return _xmlrpcs_multicall_error('recursion'); + } + + $params = @$call->structmem('params'); + if(!$params) + { + return _xmlrpcs_multicall_error('noparams'); + } + if($params->kindOf() != 'array') + { + return _xmlrpcs_multicall_error('notarray'); + } + $numParams = $params->arraysize(); + + $msg = new xmlrpcmsg($methName->scalarval()); + for($i = 0; $i < $numParams; $i++) + { + if(!$msg->addParam($params->arraymem($i))) + { + $i++; + return _xmlrpcs_multicall_error(new xmlrpcresp(0, + $GLOBALS['xmlrpcerr']['incorrect_params'], + $GLOBALS['xmlrpcstr']['incorrect_params'] . ": probable xml error in param " . $i)); + } + } + + $result = $server->execute($msg); + + if($result->faultCode() != 0) + { + return _xmlrpcs_multicall_error($result); // Method returned fault. + } + + return new xmlrpcval(array($result->value()), 'array'); + } + + function _xmlrpcs_multicall_do_call_phpvals($server, $call) + { + if(!is_array($call)) + { + return _xmlrpcs_multicall_error('notstruct'); + } + if(!array_key_exists('methodName', $call)) + { + return _xmlrpcs_multicall_error('nomethod'); + } + if (!is_string($call['methodName'])) + { + return _xmlrpcs_multicall_error('notstring'); + } + if($call['methodName'] == 'system.multicall') + { + return _xmlrpcs_multicall_error('recursion'); + } + if(!array_key_exists('params', $call)) + { + return _xmlrpcs_multicall_error('noparams'); + } + if(!is_array($call['params'])) + { + return _xmlrpcs_multicall_error('notarray'); + } + + // this is a real dirty and simplistic hack, since we might have received a + // base64 or datetime values, but they will be listed as strings here... + $numParams = count($call['params']); + $pt = array(); + foreach($call['params'] as $val) + $pt[] = php_2_xmlrpc_type(gettype($val)); + + $result = $server->execute($call['methodName'], $call['params'], $pt); + + if($result->faultCode() != 0) + { + return _xmlrpcs_multicall_error($result); // Method returned fault. + } + + return new xmlrpcval(array($result->value()), 'array'); + } + + function _xmlrpcs_multicall($server, $m) + { + $result = array(); + // let accept a plain list of php parameters, beside a single xmlrpc msg object + if (is_object($m)) + { + $calls = $m->getParam(0); + $numCalls = $calls->arraysize(); + for($i = 0; $i < $numCalls; $i++) + { + $call = $calls->arraymem($i); + $result[$i] = _xmlrpcs_multicall_do_call($server, $call); + } + } + else + { + $numCalls=count($m); + for($i = 0; $i < $numCalls; $i++) + { + $result[$i] = _xmlrpcs_multicall_do_call_phpvals($server, $m[$i]); + } + } + + return new xmlrpcresp(new xmlrpcval($result, 'array')); + } + + $GLOBALS['_xmlrpcs_dmap']=array( + 'system.listMethods' => array( + 'function' => '_xmlrpcs_listMethods', + 'signature' => $_xmlrpcs_listMethods_sig, + 'docstring' => $_xmlrpcs_listMethods_doc, + 'signature_docs' => $_xmlrpcs_listMethods_sdoc), + 'system.methodHelp' => array( + 'function' => '_xmlrpcs_methodHelp', + 'signature' => $_xmlrpcs_methodHelp_sig, + 'docstring' => $_xmlrpcs_methodHelp_doc, + 'signature_docs' => $_xmlrpcs_methodHelp_sdoc), + 'system.methodSignature' => array( + 'function' => '_xmlrpcs_methodSignature', + 'signature' => $_xmlrpcs_methodSignature_sig, + 'docstring' => $_xmlrpcs_methodSignature_doc, + 'signature_docs' => $_xmlrpcs_methodSignature_sdoc), + 'system.multicall' => array( + 'function' => '_xmlrpcs_multicall', + 'signature' => $_xmlrpcs_multicall_sig, + 'docstring' => $_xmlrpcs_multicall_doc, + 'signature_docs' => $_xmlrpcs_multicall_sdoc), + 'system.getCapabilities' => array( + 'function' => '_xmlrpcs_getCapabilities', + 'signature' => $_xmlrpcs_getCapabilities_sig, + 'docstring' => $_xmlrpcs_getCapabilities_doc, + 'signature_docs' => $_xmlrpcs_getCapabilities_sdoc) + ); + + $GLOBALS['_xmlrpcs_occurred_errors'] = ''; + $GLOBALS['_xmlrpcs_prev_ehandler'] = ''; + /** + * Error handler used to track errors that occur during server-side execution of PHP code. + * This allows to report back to the client whether an internal error has occurred or not + * using an xmlrpc response object, instead of letting the client deal with the html junk + * that a PHP execution error on the server generally entails. + * + * NB: in fact a user defined error handler can only handle WARNING, NOTICE and USER_* errors. + * + */ + function _xmlrpcs_errorHandler($errcode, $errstring, $filename=null, $lineno=null, $context=null) + { + // obey the @ protocol + if (error_reporting() == 0) + return; + + //if($errcode != E_NOTICE && $errcode != E_WARNING && $errcode != E_USER_NOTICE && $errcode != E_USER_WARNING) + if($errcode != 2048) // do not use E_STRICT by name, since on PHP 4 it will not be defined + { + $GLOBALS['_xmlrpcs_occurred_errors'] = $GLOBALS['_xmlrpcs_occurred_errors'] . $errstring . "\n"; + } + // Try to avoid as much as possible disruption to the previous error handling + // mechanism in place + if($GLOBALS['_xmlrpcs_prev_ehandler'] == '') + { + // The previous error handler was the default: all we should do is log error + // to the default error log (if level high enough) + if(ini_get('log_errors') && (intval(ini_get('error_reporting')) & $errcode)) + { + error_log($errstring); + } + } + else + { + // Pass control on to previous error handler, trying to avoid loops... + if($GLOBALS['_xmlrpcs_prev_ehandler'] != '_xmlrpcs_errorHandler') + { + // NB: this code will NOT work on php < 4.0.2: only 2 params were used for error handlers + if(is_array($GLOBALS['_xmlrpcs_prev_ehandler'])) + { + // the following works both with static class methods and plain object methods as error handler + call_user_func_array($GLOBALS['_xmlrpcs_prev_ehandler'], array($errcode, $errstring, $filename, $lineno, $context)); + } + else + { + $GLOBALS['_xmlrpcs_prev_ehandler']($errcode, $errstring, $filename, $lineno, $context); + } + } + } + } + + $GLOBALS['_xmlrpc_debuginfo']=''; + + /** + * Add a string to the debug info that can be later seralized by the server + * as part of the response message. + * Note that for best compatbility, the debug string should be encoded using + * the $GLOBALS['xmlrpc_internalencoding'] character set. + * @param string $m + * @access public + */ + function xmlrpc_debugmsg($m) + { + $GLOBALS['_xmlrpc_debuginfo'] .= $m . "\n"; + } + + class xmlrpc_server + { + /// array defining php functions exposed as xmlrpc methods by this server + var $dmap=array(); + /** + * Defines how functions in dmap will be invokde: either using an xmlrpc msg object + * or plain php values. + * valid strings are 'xmlrpcvals', 'phpvals' or 'epivals' + */ + var $functions_parameters_type='xmlrpcvals'; + /// controls wether the server is going to echo debugging messages back to the client as comments in response body. valid values: 0,1,2,3 + var $debug = 1; + /** + * When set to true, it will enable HTTP compression of the response, in case + * the client has declared its support for compression in the request. + */ + var $compress_response = false; + /** + * List of http compression methods accepted by the server for requests. + * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib + */ + var $accepted_compression = array(); + /// shall we serve calls to system.* methods? + var $allow_system_funcs = true; + /// list of charset encodings natively accepted for requests + var $accepted_charset_encodings = array(); + /** + * charset encoding to be used for response. + * NB: if we can, we will convert the generated response from internal_encoding to the intended one. + * can be: a supported xml encoding (only UTF-8 and ISO-8859-1 at present, unless mbstring is enabled), + * null (leave unspecified in response, convert output stream to US_ASCII), + * 'default' (use xmlrpc library default as specified in xmlrpc.inc, convert output stream if needed), + * or 'auto' (use client-specified charset encoding or same as request if request headers do not specify it (unless request is US-ASCII: then use library default anyway). + * NB: pretty dangerous if you accept every charset and do not have mbstring enabled) + */ + var $response_charset_encoding = ''; + /// storage for internal debug info + var $debug_info = ''; + /// extra data passed at runtime to method handling functions. Used only by EPI layer + var $user_data = null; + + /** + * @param array $dispmap the dispatch map withd efinition of exposed services + * @param boolean $servicenow set to false to prevent the server from runnung upon construction + */ + /* + function xmlrpc_server($dispMap=null, $serviceNow=true) + { + __construct($dispMap, $serviceNow); + } + */ + + + function __construct($dispMap=null, $serviceNow=true) + { + // if ZLIB is enabled, let the server by default accept compressed requests, + // and compress responses sent to clients that support them + if(function_exists('gzinflate')) + { + $this->accepted_compression = array('gzip', 'deflate'); + $this->compress_response = true; + } + + // by default the xml parser can support these 3 charset encodings + $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII'); + + // dispMap is a dispatch array of methods + // mapped to function names and signatures + // if a method + // doesn't appear in the map then an unknown + // method error is generated + /* milosch - changed to make passing dispMap optional. + * instead, you can use the class add_to_map() function + * to add functions manually (borrowed from SOAPX4) + */ + if($dispMap) + { + $this->dmap = $dispMap; + if($serviceNow) + { + $this->service(); + } + } + } + + /** + * Set debug level of server. + * @param integer $in debug lvl: determines info added to xmlrpc responses (as xml comments) + * 0 = no debug info, + * 1 = msgs set from user with debugmsg(), + * 2 = add complete xmlrpc request (headers and body), + * 3 = add also all processing warnings happened during method processing + * (NB: this involves setting a custom error handler, and might interfere + * with the standard processing of the php function exposed as method. In + * particular, triggering an USER_ERROR level error will not halt script + * execution anymore, but just end up logged in the xmlrpc response) + * Note that info added at elevel 2 and 3 will be base64 encoded + * @access public + */ + function setDebug($in) + { + $this->debug=$in; + } + + /** + * Return a string with the serialized representation of all debug info + * @param string $charset_encoding the target charset encoding for the serialization + * @return string an XML comment (or two) + */ + function serializeDebug($charset_encoding='') + { + // Tough encoding problem: which internal charset should we assume for debug info? + // It might contain a copy of raw data received from client, ie with unknown encoding, + // intermixed with php generated data and user generated data... + // so we split it: system debug is base 64 encoded, + // user debug info should be encoded by the end user using the INTERNAL_ENCODING + $out = ''; + if ($this->debug_info != '') + { + $out .= "\n"; + } + if($GLOBALS['_xmlrpc_debuginfo']!='') + { + + $out .= "\n"; + // NB: a better solution MIGHT be to use CDATA, but we need to insert it + // into return payload AFTER the beginning tag + //$out .= "', ']_]_>', $GLOBALS['_xmlrpc_debuginfo']) . "\n]]>\n"; + } + return $out; + } + + /** + * Execute the xmlrpc request, printing the response + * @param string $data the request body. If null, the http POST request will be examined + * @return xmlrpcresp the response object (usually not used by caller...) + * @access public + */ + function service($data=null, $return_payload=false) + { + if ($data === null) + { + // workaround for a known bug in php ver. 5.2.2 that broke $HTTP_RAW_POST_DATA + $ver = phpversion(); + if ($ver[0] >= 5) + { + $data = file_get_contents('php://input'); + } + else + { + $data = isset($GLOBALS['HTTP_RAW_POST_DATA']) ? $GLOBALS['HTTP_RAW_POST_DATA'] : ''; + } + } + $raw_data = $data; + + // reset internal debug info + $this->debug_info = ''; + + // Echo back what we received, before parsing it + if($this->debug > 1) + { + $this->debugmsg("+++GOT+++\n" . $data . "\n+++END+++"); + } + + $r = $this->parseRequestHeaders($data, $req_charset, $resp_charset, $resp_encoding); + if (!$r) + { + $r=$this->parseRequest($data, $req_charset); + } + + // save full body of request into response, for more debugging usages + $r->raw_data = $raw_data; + + if($this->debug > 2 && $GLOBALS['_xmlrpcs_occurred_errors']) + { + $this->debugmsg("+++PROCESSING ERRORS AND WARNINGS+++\n" . + $GLOBALS['_xmlrpcs_occurred_errors'] . "+++END+++"); + } + + $payload=$this->xml_header($resp_charset); + if($this->debug > 0) + { + $payload = $payload . $this->serializeDebug($resp_charset); + } + + // G. Giunta 2006-01-27: do not create response serialization if it has + // already happened. Helps building json magic + if (empty($r->payload)) + { + $r->serialize($resp_charset); + } + $payload = $payload . $r->payload; + + if ($return_payload) + { + return $payload; + } + + // if we get a warning/error that has output some text before here, then we cannot + // add a new header. We cannot say we are sending xml, either... + if(!headers_sent()) + { + header('Content-Type: '.$r->content_type); + // we do not know if client actually told us an accepted charset, but if he did + // we have to tell him what we did + header("Vary: Accept-Charset"); + + // http compression of output: only + // if we can do it, and we want to do it, and client asked us to, + // and php ini settings do not force it already + $php_no_self_compress = !ini_get('zlib.output_compression') && (ini_get('output_handler') != 'ob_gzhandler'); + if($this->compress_response && function_exists('gzencode') && $resp_encoding != '' + && $php_no_self_compress) + { + if(strpos($resp_encoding, 'gzip') !== false) + { + $payload = gzencode($payload); + header("Content-Encoding: gzip"); + header("Vary: Accept-Encoding"); + } + elseif (strpos($resp_encoding, 'deflate') !== false) + { + $payload = gzcompress($payload); + header("Content-Encoding: deflate"); + header("Vary: Accept-Encoding"); + } + } + + // do not ouput content-length header if php is compressing output for us: + // it will mess up measurements + if($php_no_self_compress) + { + header('Content-Length: ' . (int)strlen($payload)); + } + } + else + { + error_log('XML-RPC: xmlrpc_server::service: http headers already sent before response is fully generated. Check for php warning or error messages'); + } + + print $payload; + + // return request, in case subclasses want it + return $r; + } + + /** + * Add a method to the dispatch map + * @param string $methodname the name with which the method will be made available + * @param string $function the php function that will get invoked + * @param array $sig the array of valid method signatures + * @param string $doc method documentation + * @param array $sigdoc the array of valid method signatures docs (one string per param, one for return type) + * @access public + */ + function add_to_map($methodname,$function,$sig=null,$doc=false,$sigdoc=false) + { + $this->dmap[$methodname] = array( + 'function' => $function, + 'docstring' => $doc + ); + if ($sig) + { + $this->dmap[$methodname]['signature'] = $sig; + } + if ($sigdoc) + { + $this->dmap[$methodname]['signature_docs'] = $sigdoc; + } + } + + /** + * Verify type and number of parameters received against a list of known signatures + * @param array $in array of either xmlrpcval objects or xmlrpc type definitions + * @param array $sig array of known signatures to match against + * @access private + */ + function verifySignature($in, $sig) + { + // check each possible signature in turn + if (is_object($in)) + { + $numParams = $in->getNumParams(); + } + else + { + $numParams = count($in); + } + foreach($sig as $cursig) + { + if(count($cursig)==$numParams+1) + { + $itsOK=1; + for($n=0; $n<$numParams; $n++) + { + if (is_object($in)) + { + $p=$in->getParam($n); + if($p->kindOf() == 'scalar') + { + $pt=$p->scalartyp(); + } + else + { + $pt=$p->kindOf(); + } + } + else + { + $pt= $in[$n] == 'i4' ? 'int' : $in[$n]; // dispatch maps never use i4... + } + + // param index is $n+1, as first member of sig is return type + if($pt != $cursig[$n+1] && $cursig[$n+1] != $GLOBALS['xmlrpcValue']) + { + $itsOK=0; + $pno=$n+1; + $wanted=$cursig[$n+1]; + $got=$pt; + break; + } + } + if($itsOK) + { + return array(1,''); + } + } + } + if(isset($wanted)) + { + return array(0, "Wanted ${wanted}, got ${got} at param ${pno}"); + } + else + { + return array(0, "No method signature matches number of parameters"); + } + } + + /** + * Parse http headers received along with xmlrpc request. If needed, inflate request + * @return null on success or an xmlrpcresp + * @access private + */ + function parseRequestHeaders(&$data, &$req_encoding, &$resp_encoding, &$resp_compression) + { + // Play nice to PHP 4.0.x: superglobals were not yet invented... + if(!isset($_SERVER)) + { + $_SERVER = $GLOBALS['HTTP_SERVER_VARS']; + } + + if($this->debug > 1) + { + if(function_exists('getallheaders')) + { + $this->debugmsg(''); // empty line + foreach(getallheaders() as $name => $val) + { + $this->debugmsg("HEADER: $name: $val"); + } + } + + } + + if(isset($_SERVER['HTTP_CONTENT_ENCODING'])) + { + $content_encoding = str_replace('x-', '', $_SERVER['HTTP_CONTENT_ENCODING']); + } + else + { + $content_encoding = ''; + } + + // check if request body has been compressed and decompress it + if($content_encoding != '' && strlen($data)) + { + if($content_encoding == 'deflate' || $content_encoding == 'gzip') + { + // if decoding works, use it. else assume data wasn't gzencoded + if(function_exists('gzinflate') && in_array($content_encoding, $this->accepted_compression)) + { + if($content_encoding == 'deflate' && $degzdata = @gzuncompress($data)) + { + $data = $degzdata; + if($this->debug > 1) + { + $this->debugmsg("\n+++INFLATED REQUEST+++[".strlen($data)." chars]+++\n" . $data . "\n+++END+++"); + } + } + elseif($content_encoding == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) + { + $data = $degzdata; + if($this->debug > 1) + $this->debugmsg("+++INFLATED REQUEST+++[".strlen($data)." chars]+++\n" . $data . "\n+++END+++"); + } + else + { + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['server_decompress_fail'], $GLOBALS['xmlrpcstr']['server_decompress_fail']); + return $r; + } + } + else + { + //error_log('The server sent deflated data. Your php install must have the Zlib extension compiled in to support this.'); + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['server_cannot_decompress'], $GLOBALS['xmlrpcstr']['server_cannot_decompress']); + return $r; + } + } + } + + // check if client specified accepted charsets, and if we know how to fulfill + // the request + if ($this->response_charset_encoding == 'auto') + { + $resp_encoding = ''; + if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) + { + // here we should check if we can match the client-requested encoding + // with the encodings we know we can generate. + /// @todo we should parse q=0.x preferences instead of getting first charset specified... + $client_accepted_charsets = explode(',', strtoupper($_SERVER['HTTP_ACCEPT_CHARSET'])); + // Give preference to internal encoding + $known_charsets = array($GLOBALS['xmlrpc_internalencoding'], 'UTF-8', 'ISO-8859-1', 'US-ASCII'); + foreach ($known_charsets as $charset) + { + foreach ($client_accepted_charsets as $accepted) + if (strpos($accepted, $charset) === 0) + { + $resp_encoding = $charset; + break; + } + if ($resp_encoding) + break; + } + } + } + else + { + $resp_encoding = $this->response_charset_encoding; + } + + if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) + { + $resp_compression = $_SERVER['HTTP_ACCEPT_ENCODING']; + } + else + { + $resp_compression = ''; + } + + // 'guestimate' request encoding + /// @todo check if mbstring is enabled and automagic input conversion is on: it might mingle with this check??? + $req_encoding = guess_encoding(isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '', + $data); + + return null; + } + + /** + * Parse an xml chunk containing an xmlrpc request and execute the corresponding + * php function registered with the server + * @param string $data the xml request + * @param string $req_encoding (optional) the charset encoding of the xml request + * @return xmlrpcresp + * @access private + */ + function parseRequest($data, $req_encoding='') + { + // 2005/05/07 commented and moved into caller function code + //if($data=='') + //{ + // $data=$GLOBALS['HTTP_RAW_POST_DATA']; + //} + + // G. Giunta 2005/02/13: we do NOT expect to receive html entities + // so we do not try to convert them into xml character entities + //$data = xmlrpc_html_entity_xlate($data); + + $GLOBALS['_xh']=array(); + $GLOBALS['_xh']['ac']=''; + $GLOBALS['_xh']['stack']=array(); + $GLOBALS['_xh']['valuestack'] = array(); + $GLOBALS['_xh']['params']=array(); + $GLOBALS['_xh']['pt']=array(); + $GLOBALS['_xh']['isf']=0; + $GLOBALS['_xh']['isf_reason']=''; + $GLOBALS['_xh']['method']=false; // so we can check later if we got a methodname or not + $GLOBALS['_xh']['rt']=''; + + // decompose incoming XML into request structure + if ($req_encoding != '') + { + if (!in_array($req_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) + // the following code might be better for mb_string enabled installs, but + // makes the lib about 200% slower... + //if (!is_valid_charset($req_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) + { + error_log('XML-RPC: xmlrpc_server::parseRequest: invalid charset encoding of received request: '.$req_encoding); + $req_encoding = $GLOBALS['xmlrpc_defencoding']; + } + /// @BUG this will fail on PHP 5 if charset is not specified in the xml prologue, + // the encoding is not UTF8 and there are non-ascii chars in the text... + /// @todo use an ampty string for php 5 ??? + $parser = xml_parser_create($req_encoding); + } + else + { + $parser = xml_parser_create(); + } + + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); + // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell + // the xml parser to give us back data in the expected charset + // What if internal encoding is not in one of the 3 allowed? + // we use the broadest one, ie. utf8 + // This allows to send data which is native in various charset, + // by extending xmlrpc_encode_entitites() and setting xmlrpc_internalencoding + if (!in_array($GLOBALS['xmlrpc_internalencoding'], array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) + { + xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8'); + } + else + { + xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']); + } + + if ($this->functions_parameters_type != 'xmlrpcvals') + xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast'); + else + xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); + xml_set_character_data_handler($parser, 'xmlrpc_cd'); + xml_set_default_handler($parser, 'xmlrpc_dh'); + if(!xml_parse($parser, $data, 1)) + { + // return XML error as a faultCode + $r = new xmlrpcresp(0, + $GLOBALS['xmlrpcerrxml']+xml_get_error_code($parser), + sprintf('XML error: %s at line %d, column %d', + xml_error_string(xml_get_error_code($parser)), + xml_get_current_line_number($parser), xml_get_current_column_number($parser))); + xml_parser_free($parser); + } + elseif ($GLOBALS['_xh']['isf']) + { + xml_parser_free($parser); + $r = new xmlrpcresp(0, + $GLOBALS['xmlrpcerr']['invalid_request'], + $GLOBALS['xmlrpcstr']['invalid_request'] . ' ' . $GLOBALS['_xh']['isf_reason']); + } + else + { + xml_parser_free($parser); + if ($this->functions_parameters_type != 'xmlrpcvals') + { + if($this->debug > 1) + { + $this->debugmsg("\n+++PARSED+++\n".var_export($GLOBALS['_xh']['params'], true)."\n+++END+++"); + } + $r = $this->execute($GLOBALS['_xh']['method'], $GLOBALS['_xh']['params'], $GLOBALS['_xh']['pt']); + } + else + { + // build an xmlrpcmsg object with data parsed from xml + $m = new xmlrpcmsg($GLOBALS['_xh']['method']); + // now add parameters in + for($i=0; $iaddParam($GLOBALS['_xh']['params'][$i]); + } + + if($this->debug > 1) + { + $this->debugmsg("\n+++PARSED+++\n".var_export($m, true)."\n+++END+++"); + } + $r = $this->execute($m); + } + } + return $r; + } + + /** + * Execute a method invoked by the client, checking parameters used + * @param mixed $m either an xmlrpcmsg obj or a method name + * @param array $params array with method parameters as php types (if m is method name only) + * @param array $paramtypes array with xmlrpc types of method parameters (if m is method name only) + * @return xmlrpcresp + * @access private + */ + function execute($m, $params=null, $paramtypes=null) + { + if (is_object($m)) + { + $methName = $m->method(); + } + else + { + $methName = $m; + } + $sysCall = $this->allow_system_funcs && (strpos($methName, "system.") === 0); + $dmap = $sysCall ? $GLOBALS['_xmlrpcs_dmap'] : $this->dmap; + + if(!isset($dmap[$methName]['function'])) + { + // No such method + return new xmlrpcresp(0, + $GLOBALS['xmlrpcerr']['unknown_method'], + $GLOBALS['xmlrpcstr']['unknown_method']); + } + + // Check signature + if(isset($dmap[$methName]['signature'])) + { + $sig = $dmap[$methName]['signature']; + if (is_object($m)) + { + list($ok, $errstr) = $this->verifySignature($m, $sig); + } + else + { + list($ok, $errstr) = $this->verifySignature($paramtypes, $sig); + } + if(!$ok) + { + // Didn't match. + return new xmlrpcresp( + 0, + $GLOBALS['xmlrpcerr']['incorrect_params'], + $GLOBALS['xmlrpcstr']['incorrect_params'] . ": ${errstr}" + ); + } + } + + $func = $dmap[$methName]['function']; + // let the 'class::function' syntax be accepted in dispatch maps + if(is_string($func) && strpos($func, '::')) + { + $func = explode('::', $func); + } + // verify that function to be invoked is in fact callable + if(!is_callable($func)) + { + error_log("XML-RPC: xmlrpc_server::execute: function $func registered as method handler is not callable"); + return new xmlrpcresp( + 0, + $GLOBALS['xmlrpcerr']['server_error'], + $GLOBALS['xmlrpcstr']['server_error'] . ": no function matches method" + ); + } + + // If debug level is 3, we should catch all errors generated during + // processing of user function, and log them as part of response + if($this->debug > 2) + { + $GLOBALS['_xmlrpcs_prev_ehandler'] = set_error_handler('_xmlrpcs_errorHandler'); + } + if (is_object($m)) + { + if($sysCall) + { + $r = call_user_func($func, $this, $m); + } + else + { + $r = call_user_func($func, $m); + } + if (!is_a($r, 'xmlrpcresp')) + { + error_log("XML-RPC: xmlrpc_server::execute: function $func registered as method handler does not return an xmlrpcresp object"); + if (is_a($r, 'xmlrpcval')) + { + $r = new xmlrpcresp($r); + } + else + { + $r = new xmlrpcresp( + 0, + $GLOBALS['xmlrpcerr']['server_error'], + $GLOBALS['xmlrpcstr']['server_error'] . ": function does not return xmlrpcresp object" + ); + } + } + } + else + { + // call a 'plain php' function + if($sysCall) + { + array_unshift($params, $this); + $r = call_user_func_array($func, $params); + } + else + { + // 3rd API convention for method-handling functions: EPI-style + if ($this->functions_parameters_type == 'epivals') + { + $r = call_user_func_array($func, array($methName, $params, $this->user_data)); + // mimic EPI behaviour: if we get an array that looks like an error, make it + // an eror response + if (is_array($r) && array_key_exists('faultCode', $r) && array_key_exists('faultString', $r)) + { + $r = new xmlrpcresp(0, (integer)$r['faultCode'], (string)$r['faultString']); + } + else + { + // functions using EPI api should NOT return resp objects, + // so make sure we encode the return type correctly + $r = new xmlrpcresp(php_xmlrpc_encode($r, array('extension_api'))); + } + } + else + { + $r = call_user_func_array($func, $params); + } + } + // the return type can be either an xmlrpcresp object or a plain php value... + if (!is_a($r, 'xmlrpcresp')) + { + // what should we assume here about automatic encoding of datetimes + // and php classes instances??? + $r = new xmlrpcresp(php_xmlrpc_encode($r, array('auto_dates'))); + } + } + if($this->debug > 2) + { + // note: restore the error handler we found before calling the + // user func, even if it has been changed inside the func itself + if($GLOBALS['_xmlrpcs_prev_ehandler']) + { + set_error_handler($GLOBALS['_xmlrpcs_prev_ehandler']); + } + else + { + restore_error_handler(); + } + } + return $r; + } + + /** + * add a string to the 'internal debug message' (separate from 'user debug message') + * @param string $strings + * @access private + */ + function debugmsg($string) + { + $this->debug_info .= $string."\n"; + } + + /** + * @access private + */ + function xml_header($charset_encoding='') + { + if ($charset_encoding != '') + { + return "\n"; + } + else + { + return "\n"; + } + } + + /** + * A debugging routine: just echoes back the input packet as a string value + * DEPRECATED! + */ + function echoInput() + { + $r = new xmlrpcresp(new xmlrpcval( "'Aha said I: '" . $GLOBALS['HTTP_RAW_POST_DATA'], 'string')); + print $r->serialize(); + } + } +?> diff --git a/helper-php/DTLNSL_helper_scripts/helper/xmlgroups.php b/helper-php/DTLNSL_helper_scripts/helper/xmlgroups.php new file mode 100644 index 0000000..29c0a23 --- /dev/null +++ b/helper-php/DTLNSL_helper_scripts/helper/xmlgroups.php @@ -0,0 +1,24 @@ + "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + + Methods that run without errors, but do not have the intended result should return as: + + return array('succeed' => 'false', 'message' => 'No Groups Found', 'params' => var_export($params, TRUE)); + + or if applicable: + + return array('succeed' => 'false', 'message' => 'What went wrong', 'params' => var_export($params, TRUE), 'sql' => $sql); + */ + + + // Modified by Fumi.Iseki '09 5/31 + // Modified by Fumi.Iseki '14 3/4 + // Modified by Fumi.Iseki '16 7/14 + + include(dirname(__FILE__).'/phpxmlrpclib/xmlrpc.inc'); + include(dirname(__FILE__).'/phpxmlrpclib/xmlrpcs.inc'); + + // Global + $osagent = XMLGROUP_ACTIVE_TBL; + $osgroup = XMLGROUP_LIST_TBL; + $osgroupinvite = XMLGROUP_INVITE_TBL; + $osgroupmembership = XMLGROUP_MEMBERSHIP_TBL; + $osgroupnotice = XMLGROUP_NOTICE_TBL; + $osgrouprolemembership = XMLGROUP_ROLE_MEMBER_TBL; + $osrole = XMLGROUP_ROLE_TBL; + + + $groupPowers = array( + 'None' => 0, + /// Can send invitations to groups default role + 'Invite' => 1, + /// Can eject members from group + 'Eject' => 2, + /// Can toggle 'Open Enrollment' and change 'Signup fee' + 'ChangeOptions' => 4, + /// Can create new roles + 'CreateRole' => 8, + /// Can delete existing roles + 'DeleteRole' => 16, + /// Can change Role names, titles and descriptions + 'RoleProperties' => 32, + /// Can assign other members to assigners role + 'AssignMemberLimited' => 64, + /// Can assign other members to any role + 'AssignMember' => 128, + /// Can remove members from roles + 'RemoveMember' => 256, + /// Can assign and remove abilities in roles + 'ChangeActions' => 512, + /// Can change group Charter, Insignia, 'Publish on the web' and which + /// members are publicly visible in group member listings + 'ChangeIdentity' => 1024, + /// Can buy land or deed land to group + 'LandDeed' => 2048, + /// Can abandon group owned land to Governor Linden on mainland, or Estate owner for + /// private estates + 'LandRelease' => 4096, + /// Can set land for-sale information on group owned parcels + 'LandSetSale' => 8192, + /// Can subdivide and join parcels + 'LandDivideJoin' => 16384, + /// Can join group chat sessions + 'JoinChat' => 32768, + /// Can toggle "Show in Find Places" and set search category + 'FindPlaces' => 65536, + /// Can change parcel name, description, and 'Publish on web' settings + 'LandChangeIdentity' => 131072, + /// Can set the landing point and teleport routing on group land + 'SetLandingPoint' => 262144, + /// Can change music and media settings + 'ChangeMedia' => 524288, + /// Can toggle 'Edit Terrain' option in Land settings + 'LandEdit' => 1048576, + /// Can toggle various About Land > Options settings + 'LandOptions' => 2097152, + /// Can always terraform land, even if parcel settings have it turned off + 'AllowEditLand' => 4194304, + /// Can always fly while over group owned land + 'AllowFly' => 8388608, + /// Can always rez objects on group owned land + 'AllowRez' => 16777216, + /// Can always create landmarks for group owned parcels + 'AllowLandmark' => 33554432, + /// Can use voice chat in Group Chat sessions + 'AllowVoiceChat' => 67108864, + /// Can set home location on any group owned parcel + 'AllowSetHome' => 134217728, + /// Can modify public access settings for group owned parcels + 'LandManageAllowed' => 268435456, + /// Can manager parcel ban lists on group owned land + 'LandManageBanned' => 536870912, + /// Can manage pass list sales information + 'LandManagePasses' => 1073741824, + /// Can eject and freeze other avatars on group owned land + 'LandEjectAndFreeze' => 2147483648, + /// Can return objects set to group + 'ReturnGroupSet' => 4294967296, + /// Can return non-group owned/set objects + 'ReturnNonGroup' => 8589934592, + /// Can landscape using Linden plants + 'LandGardening' => 17179869184, + /// Can deed objects to group + 'DeedObject' => 34359738368, + /// Can moderate group chat sessions + 'ModerateChat' => 68719476736, + /// Can move group owned objects + 'ObjectManipulate' => 137438953472, + /// Can set group owned objects for-sale + 'ObjectSetForSale' => 274877906944, + /// Pay group liabilities and receive group dividends + 'Accountable' => 549755813888, + /// Can send group notices + 'SendNotices' => 1099511627776, + /// Can receive group notices + 'ReceiveNotices' => 2199023255552, + /// Can create group proposals + 'StartProposal' => 4398046511104, + /// Can vote on group proposals + 'VoteOnProposal' => 8796093022208, + /// Can return group owned objects + 'ReturnGroupOwned' => 17592186044416 + ); + + + $uuidZero = "00000000-0000-0000-0000-000000000000"; + + $groupDBCon = mysql_connect($XMLGRP_DB_HOST, $XMLGRP_DB_USER, $XMLGRP_DB_PASS); + if (!$groupDBCon) + { + die('Could not connect: ' . mysql_error()); + } + mysql_select_db($XMLGRP_DB_NAME, $groupDBCon); + + // This is filled in by secure() + $requestingAgent = $uuidZero; + + + function test() + { + return array('name' => 'Joe','age' => 27); + } + + // Use a common signature for all the group functions -> struct foo($struct) + $common_sig = array(array($xmlrpcStruct, $xmlrpcStruct)); + + + function createGroup($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params["GroupID"]; + $name = addslashes( $params["Name"] ); + $charter = addslashes( $params["Charter"] ); + $insigniaID = $params["InsigniaID"]; + $founderID = $params["FounderID"]; + $membershipFee = $params["MembershipFee"]; + $openEnrollment = $params["OpenEnrollment"]; + $showInList = $params["ShowInList"]; + $allowPublish = $params["AllowPublish"]; + $maturePublish = $params["MaturePublish"]; + $ownerRoleID = $params["OwnerRoleID"]; + $everyonePowers = $params["EveryonePowers"]; + $ownersPowers = $params["OwnersPowers"]; + + // Create group + $sql = "INSERT INTO $osgroup + (GroupID, Name, Charter, InsigniaID, FounderID, MembershipFee, OpenEnrollment, ShowInList, AllowPublish, MaturePublish, OwnerRoleID) + VALUES + ('$groupID','$name','$charter','$insigniaID','$founderID',$membershipFee,$openEnrollment,$showInList,$allowPublish,$maturePublish,'$ownerRoleID')"; + + if (!mysql_query($sql, $groupDBCon)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + // Create Everyone Role + // NOTE: FIXME: This is a temp fix until the libomv enum for group powers is fixed in OpenSim + $everyonePowers = 8796495740928; + $result = _addRoleToGroup(array('GroupID' => $groupID, 'RoleID' => $uuidZero, 'Name' => 'Everyone', + 'Description' => 'Everyone in the group is in the everyone role.', 'Title' => "Member of $name", 'Powers' => $everyonePowers)); + if( isset($result['error']) ) + { + return $result; + } + + // Create Owner Role + $result = _addRoleToGroup(array('GroupID' => $groupID, 'RoleID' => $ownerRoleID, 'Name' => 'Owners', 'Description' => "Owners of $name", + 'Title' => "Owner of $name", 'Powers' => $ownersPowers)); + if( isset($result['error']) ) + { + return $result; + } + + // Add founder to group, will automatically place them in the Everyone Role, also places them in specified Owner Role + $result = _addAgentToGroup(array('AgentID' => $founderID, 'GroupID' => $groupID, 'RoleID' => $ownerRoleID)); + if( isset($result['error']) ) + { + return $result; + } + + // Select the owner's role for the founder + $result = _setAgentGroupSelectedRole(array('AgentID' => $founderID, 'RoleID' => $ownerRoleID, 'GroupID' => $groupID)); + if( isset($result['error']) ) + { + return $result; + } + + // Set the new group as the founder's active group + $result = _setAgentActiveGroup(array('AgentID' => $founderID, 'GroupID' => $groupID)); + if( isset($result['error']) ) + { + return $result; + } + + + return getGroup(array("GroupID"=>$groupID)); + } + + + // Private method, does not include security, to only be called from places that have already verified security + function _addRoleToGroup($params) + { + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params['GroupID']; + $roleID = $params['RoleID']; + $name = addslashes( $params['Name'] ); + $desc = addslashes( $params['Description'] ); + $title = addslashes( $params['Title'] ); + $powers = $params['Powers']; + + $sql = " INSERT INTO $osrole (GroupID, RoleID, Name, Description, Title, Powers) VALUES " + ." ('$groupID', '$roleID', '$name', '$desc', '$title', $powers)"; + + if (!mysql_query($sql, $groupDBCon)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error() + , 'method' => 'addRoleToGroup' + , 'params' => var_export($params, TRUE)); + } + + return array("success" => "true"); + } + + + function addRoleToGroup($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + $groupID = $params['GroupID']; + + // Verify the requesting agent has permission + if( is_array($error = checkGroupPermission($groupID, $groupPowers['CreateRole'])) ) + { + return $error; + } + + return _addRoleToGroup($params); + } + + + function updateGroupRole($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params['GroupID']; + $roleID = $params['RoleID']; + $name = addslashes( $params['Name'] ); + $desc = addslashes( $params['Description'] ); + $title = addslashes( $params['Title'] ); + $powers = $params['Powers']; + + // Verify the requesting agent has permission + if( is_array($error = checkGroupPermission($groupID, $groupPowers['RoleProperties'])) ) + { + return $error; + } + + + $sql = " UPDATE $osrole SET RoleID = '$roleID' "; + if( isset($params['Name']) ) + { + $sql .= ", Name = '$name'"; + } + if( isset($params['Description']) ) + { + $sql .= ", Description = '$desc'"; + } + if( isset($params['Title']) ) + { + $sql .= ", Title = '$title'"; + } + if( isset($params['Powers']) ) + { + $sql .= ", Powers = $powers"; + } + + $sql .= " WHERE GroupID = '$groupID' AND RoleID = '$roleID'"; + + if (!mysql_query($sql, $groupDBCon)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + return array("success" => "true"); + } + + + function removeRoleFromGroup($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params['GroupID']; + $roleID = $params['RoleID']; + + if( is_array($error = checkGroupPermission($groupID, $groupPowers['RoleProperties'])) ) + { + return $error; + } + + /// 1. Remove all members from Role + /// 2. Set selected Role to uuidZero for anyone that had the role selected + /// 3. Delete roll + + $sql = "DELETE FROM $osgrouprolemembership WHERE GroupID = '$groupID' AND RoleID = '$roleID'"; + if (!mysql_query($sql, $groupDBCon)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + $sql = "UPDATE $osgroupmembership SET SelectedRoleID = '$uuidZero' WHERE GroupID = '$groupID' AND SelectedRoleID = '$roleID'"; + if (!mysql_query($sql, $groupDBCon)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + $sql = "DELETE FROM $osrole WHERE GroupID = '$groupID' AND RoleID = '$roleID'"; + if (!mysql_query($sql, $groupDBCon)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + return array("success" => "true"); + } + + + function getGroup($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + return _getGroup($params); + } + + + function _getGroup($params) + { + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $sql = " SELECT $osgroup.GroupID, $osgroup.Name, Charter, InsigniaID, FounderID, MembershipFee, OpenEnrollment, ShowInList, AllowPublish, MaturePublish, OwnerRoleID" + ." , count($osrole.RoleID) as GroupRolesCount, count($osgroupmembership.AgentID) as GroupMembershipCount " + ." FROM $osgroup " + ." LEFT JOIN $osrole ON ($osgroup.GroupID = $osrole.GroupID)" + ." LEFT JOIN $osgroupmembership ON ($osgroup.GroupID = $osgroupmembership.GroupID)" + ." WHERE "; + if( isset($params['GroupID']) ) + { + $sql .= "$osgroup.GroupID = '".$params['GroupID']."'"; + + } else if( isset($params['Name']) ) + { + $sql .= "$osgroup.Name = '".addslashes($params['Name'])."'"; + } else { + return array("error" => "Must specify GroupID or Name"); + } + + $sql .= " GROUP BY $osgroup.GroupID, $osgroup.name, charter, insigniaID, founderID, membershipFee, openEnrollment, showInList, allowPublish, maturePublish, ownerRoleID"; + + $result = mysql_query($sql, $groupDBCon); + + if (!$result) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + if (mysql_num_rows($result) == 0) + { + return array('succeed' => 'false', 'error' => 'Group Not Found', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + + return mysql_fetch_assoc($result); + } + + + function updateGroup($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params["GroupID"]; + $charter = addslashes( $params["Charter"] ); + $insigniaID = $params["InsigniaID"]; + $membershipFee = $params["MembershipFee"]; + $openEnrollment = $params["OpenEnrollment"]; + $showInList = $params["ShowInList"]; + $allowPublish = $params["AllowPublish"]; + $maturePublish = $params["MaturePublish"]; + + if( is_array($error = checkGroupPermission($groupID, $groupPowers['ChangeOptions'])) ) + { + return $error; + } + + // Create group + $sql = "UPDATE $osgroup + SET + Charter = '$charter' + , InsigniaID = '$insigniaID' + , MembershipFee = $membershipFee + , OpenEnrollment= $openEnrollment + , ShowInList = $showInList + , AllowPublish = $allowPublish + , MaturePublish = $maturePublish + WHERE + GroupID = '$groupID'"; + + if (!mysql_query($sql, $groupDBCon)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + return array('success' => 'true'); + } + + + function findGroups($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $search = addslashes( $params['Search'] ); + + // FULLTEXT indexes is not supported in InnoDB :( + $sql = " SELECT $osgroup.GroupID, $osgroup.Name, count($osgroupmembership.AgentID) as Members " + ." FROM $osgroup LEFT JOIN $osgroupmembership ON ($osgroup.GroupID = $osgroupmembership.GroupID) " + ." WHERE " + // ." ( MATCH ($osgroup.name) AGAINST ('$search' IN BOOLEAN MODE)" + // ." OR $osgroup.name LIKE '%$search%'" + // ." OR $osgroup.name REGEXP '$search'" + // ." ) AND ShowInList = 1" + ." ( $osgroup.name LIKE '%$search%'" + ." OR $osgroup.name REGEXP '$search'" + ." ) AND ShowInList = 1" + ." GROUP BY $osgroup.GroupID, $osgroup.Name"; + + $result = mysql_query($sql, $groupDBCon); + + if (!$result) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + if( mysql_num_rows($result) == 0 ) + { + return array('succeed' => 'false', 'error' => 'No groups found.', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + + $results = array(); + + while ($row = mysql_fetch_assoc($result)) + { + $groupID = $row['GroupID']; + $results[$groupID] = $row; + } + + return array('results' => $results, 'success' => TRUE); + } + + + function _setAgentActiveGroup($params) + { + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $agentID = $params['AgentID']; + $groupID = $params['GroupID']; + + $sql = " UPDATE $osagent " + ." SET ActiveGroupID = '$groupID'" + ." WHERE AgentID = '$agentID'"; + + if (!mysql_query($sql, $groupDBCon)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + if( mysql_affected_rows() == 0 ) + { + $sql = " INSERT INTO $osagent (ActiveGroupID, AgentID) VALUES " + ." ('$groupID', '$agentID')"; + + if (!mysql_query($sql, $groupDBCon)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + } + + return array("success" => "true"); + } + + + function setAgentActiveGroup($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + $agentID = $params['AgentID']; + $groupID = $params['GroupID']; + + if( isset($requestingAgent) && ($requestingAgent != $uuidZero) && ($requestingAgent != $agentID) ) + { + return array('error' => "Agent can only change their own Selected Group Role", 'params' => var_export($params, TRUE)); + } + + return _setAgentActiveGroup($params); + } + + + function addAgentToGroup($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params["GroupID"]; + $agentID = $params["AgentID"]; + + if( is_array($error = checkGroupPermission($groupID, $groupPowers['AssignMember'])) ) + { + // If they don't have direct permission, check to see if the group is marked for open enrollment + $groupInfo = _getGroup( array ('GroupID'=>$groupID) ); + + if( isset($groupInfo['error'])) + { + return $groupInfo; + } + + if($groupInfo['OpenEnrollment'] != 1) + { + // Group is not open enrollment, check if the specified agentid has an invite + $sql = " SELECT GroupID, RoleID, AgentID FROM $osgroupinvite" + ." WHERE $osgroupinvite.AgentID = '$agentID' AND $osgroupinvite.GroupID = '$groupID'"; + + $results = mysql_query($sql, $groupDBCon); + if (!$results) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + if( mysql_num_rows($results) == 1 ) + { + // if there is an invite, make sure we're adding the user to the role specified in the invite + $inviteInfo = mysql_fetch_assoc($results); + $params['RoleID'] = $inviteInfo['RoleID']; + } else { + // Not openenrollment, not invited, return permission denied error + return $error; + } + + } + } + + return _addAgentToGroup($params); + } + + + // Private method, does not include security, to only be called from places that have already verified security + function _addAgentToGroup($params) + { + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $agentID = $params["AgentID"]; + $groupID = $params["GroupID"]; + + $roleID = $uuidZero; + if( isset($params["RoleID"]) ) + { + $roleID = $params["RoleID"]; + } + + // Check if agent already a member + $sql = " SELECT count(AgentID) as isMember FROM $osgroupmembership WHERE AgentID = '$agentID' AND GroupID = '$groupID'"; + $result = mysql_query($sql, $groupDBCon); + if (!$result) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + // If not a member, add membership, select role (defaults to uuidZero, or everyone role) + if( mysql_result($result, 0) == 0 ) + { + $sql = " INSERT INTO $osgroupmembership (GroupID, AgentID, Contribution, ListInProfile, AcceptNotices, SelectedRoleID) VALUES " + ."('$groupID','$agentID', 0, 1, 1,'$roleID')"; + + if (!mysql_query($sql, $groupDBCon)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + } + + // Make sure they're in the Everyone role + $result = _addAgentToGroupRole(array("GroupID" => $groupID, "RoleID" => $uuidZero, "AgentID" => $agentID)); + if( isset($result['error']) ) + { + return $result; + } + + // Make sure they're in specified role, if they were invited + if( $roleID != $uuidZero ) + { + $result = _addAgentToGroupRole(array("GroupID" => $groupID, "RoleID" => $roleID, "AgentID" => $agentID)); + if( isset($result['error']) ) + { + return $result; + } + } + + return array("success" => "true"); + } + + + function removeAgentFromGroup($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $agentID = $params["AgentID"]; + $groupID = $params["GroupID"]; + + if( is_array($error = checkGroupPermission($groupID, $groupPowers['RemoveMember'])) ) + { + return $error; + } + + // 1. If group is agent's active group, change active group to uuidZero + // 2. Remove Agent from group (osgroupmembership) + // 3. Remove Agent from all of the groups roles (osgrouprolemembership) + + $sql = " UPDATE $osagent " + ." SET ActiveGroupID = '$uuidZero'" + ." WHERE AgentID = '$agentID' AND ActiveGroupID = '$groupID'"; + if (!mysql_query($sql, $groupDBCon)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + $sql = " DELETE FROM $osgroupmembership " + ." WHERE AgentID = '$agentID' AND GroupID = '$groupID'"; + if (!mysql_query($sql, $groupDBCon)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + $sql = " DELETE FROM $osgrouprolemembership " + ." WHERE AgentID = '$agentID' AND GroupID = '$groupID'"; + if (!mysql_query($sql, $groupDBCon)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + return array("success" => "true"); + } + + + function _addAgentToGroupRole($params) + { + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $agentID = $params["AgentID"]; + $groupID = $params["GroupID"]; + $roleID = $params["RoleID"]; + + // Check if agent already a member + $sql = " SELECT count(AgentID) as isMember FROM $osgrouprolemembership WHERE AgentID = '$agentID' AND RoleID = '$roleID' AND GroupID = '$groupID'"; + $result = mysql_query($sql, $groupDBCon); + if (!$result) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + if( mysql_result($result, 0) == 0 ) + { + $sql = " INSERT INTO $osgrouprolemembership (GroupID, RoleID, AgentID) VALUES " + ."('$groupID', '$roleID', '$agentID')"; + + if (!mysql_query($sql, $groupDBCon)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + } + + return array("success" => "true"); + } + + + function addAgentToGroupRole($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $agentID = $params["AgentID"]; + $groupID = $params["GroupID"]; + $roleID = $params["RoleID"]; + + // Check if being assigned to Owners role, assignments to an owners role can only be requested by owners. + $sql = " SELECT OwnerRoleID, AgentID " + ." FROM $osgroup LEFT JOIN $osgrouprolemembership ON ($osgroup.GroupID = $osgrouprolemembership.GroupID AND $osgroup.OwnerRoleID = $osgrouprolemembership.RoleID) " + ." WHERE $osgrouprolemembership.AgentID = '$agentID' AND $osgroup.GroupID = '$groupID'"; + + $results = mysql_query($sql, $groupDBCon); + if (!$results) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + if( mysql_num_rows($results) != 0 ) + { + $ownerRoleInfo = mysql_fetch_assoc($results); + if( ($ownerRoleInfo['OwnerRoleID'] == $roleID) && ($ownerRoleInfo['AgentID'] != $requestingAgent) ) + { + return array('error' => "Requesting agent $requestingAgent is not a member of the Owners Role and cannot add members to the owners role.", + 'params' => var_export($params, TRUE)); + } + } + + if( is_array($error = checkGroupPermission($groupID, $groupPowers['AssignMember'])) ) + { + return $error; + } + + return _addAgentToGroupRole($params); + } + + + function removeAgentFromGroupRole($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $agentID = $params["AgentID"]; + $groupID = $params["GroupID"]; + $roleID = $params["RoleID"]; + + if( is_array($error = checkGroupPermission($groupID, $groupPowers['AssignMember'])) ) + { + return $error; + } + + // If agent has this role selected, change their selection to everyone (uuidZero) role + $sql = " UPDATE $osgroupmembership SET SelectedRoleID = '$uuidZero' WHERE AgentID = '$agentID' AND GroupID = '$groupID' AND SelectedRoleID = '$roleID'"; + $result = mysql_query($sql, $groupDBCon); + if (!$result) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + $sql = " DELETE FROM $osgrouprolemembership WHERE AgentID = '$agentID' AND GroupID = '$groupID' AND RoleID = '$roleID'"; + + if (!mysql_query($sql, $groupDBCon)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + return array("success" => "true"); + } + + + function _setAgentGroupSelectedRole($params) + { + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $agentID = $params["AgentID"]; + $groupID = $params["GroupID"]; + $roleID = $params["RoleID"]; + + $sql = " UPDATE $osgroupmembership SET SelectedRoleID = '$roleID' WHERE AgentID = '$agentID' AND GroupID = '$groupID'"; + $result = mysql_query($sql, $groupDBCon); + if (!$result) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + return array('success' => 'true'); + } + + + function setAgentGroupSelectedRole($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + $agentID = $params["AgentID"]; + $groupID = $params["GroupID"]; + $roleID = $params["RoleID"]; + + if( isset($requestingAgent) && ($requestingAgent != $uuidZero) && ($requestingAgent != $agentID) ) + { + return array('error' => "Agent can only change their own Selected Group Role", 'params' => var_export($params, TRUE)); + } + + return _setAgentGroupSelectedRole($params); + } + + + function getAgentGroupMembership($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params['GroupID']; + $agentID = $params['AgentID']; + + $sql = " SELECT $osgroup.GroupID, $osgroup.Name as GroupName, $osgroup.Charter, $osgroup.InsigniaID, $osgroup.FounderID" + ." , $osgroup.MembershipFee, $osgroup.OpenEnrollment, $osgroup.ShowInList, $osgroup.AllowPublish, $osgroup.MaturePublish" + ." , $osgroupmembership.Contribution, $osgroupmembership.ListInProfile, $osgroupmembership.AcceptNotices" + ." , $osgroupmembership.SelectedRoleID, $osrole.Title" + ." , $osagent.ActiveGroupID " + ." FROM $osgroup JOIN $osgroupmembership ON ($osgroup.GroupID = $osgroupmembership.GroupID)" + ." JOIN $osrole ON ($osgroupmembership.SelectedRoleID = $osrole.RoleID AND $osgroupmembership.GroupID = $osrole.GroupID)" + ." JOIN $osagent ON ($osagent.AgentID = $osgroupmembership.AgentID)" + ." WHERE $osgroup.GroupID = '$groupID' AND $osgroupmembership.AgentID = '$agentID'"; + + $groupmembershipResult = mysql_query($sql, $groupDBCon); + if (!$groupmembershipResult) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + if( mysql_num_rows($groupmembershipResult) == 0 ) + { + return array('succeed' => 'false', 'error' => 'None Found', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + + $groupMembershipInfo = mysql_fetch_assoc($groupmembershipResult); + + $sql = " SELECT BIT_OR($osrole.Powers) AS GroupPowers" + ." FROM $osgrouprolemembership JOIN $osrole ON ($osgrouprolemembership.GroupID = $osrole.GroupID AND $osgrouprolemembership.RoleID = $osrole.RoleID)" + ." WHERE $osgrouprolemembership.GroupID = '$groupID' AND $osgrouprolemembership.AgentID = '$agentID'"; + $groupPowersResult = mysql_query($sql, $groupDBCon); + if (!$groupPowersResult) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + $groupPowersInfo = mysql_fetch_assoc($groupPowersResult); + + return array_merge($groupMembershipInfo, $groupPowersInfo); + } + + + function getAgentGroupMemberships($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + $agentID = $params['AgentID']; + + $sql = " SELECT $osgroup.GroupID, $osgroup.Name as GroupName, $osgroup.Charter, $osgroup.InsigniaID, $osgroup.FounderID" + ." , $osgroup.MembershipFee, $osgroup.OpenEnrollment, $osgroup.ShowInList, $osgroup.AllowPublish, $osgroup.MaturePublish" + ." , $osgroupmembership.Contribution, $osgroupmembership.ListInProfile, $osgroupmembership.AcceptNotices" + ." , $osgroupmembership.SelectedRoleID, $osrole.Title" + ." , IFNULL($osagent.ActiveGroupID, '$uuidZero') AS ActiveGroupID" + ." FROM $osgroup JOIN $osgroupmembership ON ($osgroup.GroupID = $osgroupmembership.GroupID)" + ." JOIN $osrole ON ($osgroupmembership.SelectedRoleID = $osrole.RoleID AND $osgroupmembership.GroupID = $osrole.GroupID)" + ." LEFT JOIN $osagent ON ($osagent.AgentID = $osgroupmembership.AgentID)" + ." WHERE $osgroupmembership.AgentID = '$agentID'"; + + $groupmembershipResults = mysql_query($sql, $groupDBCon); + if (!$groupmembershipResults) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + if( mysql_num_rows($groupmembershipResults) == 0 ) + { + return array('succeed' => 'false', 'error' => 'No Memberships', 'params' => var_export($params, TRUE), 'sql' => $sql); + + } + + $groupResults = array(); + while($groupMembershipInfo = mysql_fetch_assoc($groupmembershipResults)) + { + $groupID = $groupMembershipInfo['GroupID']; + $sql = " SELECT BIT_OR($osrole.Powers) AS GroupPowers" + ." FROM $osgrouprolemembership JOIN $osrole ON ($osgrouprolemembership.GroupID = $osrole.GroupID AND $osgrouprolemembership.RoleID = $osrole.RoleID)" + ." WHERE $osgrouprolemembership.GroupID = '$groupID' AND $osgrouprolemembership.AgentID = '$agentID'"; + $groupPowersResult = mysql_query($sql, $groupDBCon); + if (!$groupPowersResult) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + $groupPowersInfo = mysql_fetch_assoc($groupPowersResult); + $groupResults[$groupID] = array_merge($groupMembershipInfo, $groupPowersInfo); + } + + return $groupResults; + } + + + function getGroupMembers($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params['GroupID']; + + $sql = " SELECT $osgroupmembership.AgentID" + ." , $osgroupmembership.Contribution, $osgroupmembership.ListInProfile, $osgroupmembership.AcceptNotices" + ." , $osgroupmembership.SelectedRoleID, $osrole.Title" + ." , CASE WHEN OwnerRoleMembership.AgentID IS NOT NULL THEN 1 ELSE 0 END AS IsOwner" + ." FROM $osgroup JOIN $osgroupmembership ON ($osgroup.GroupID = $osgroupmembership.GroupID)" + ." JOIN $osrole ON ($osgroupmembership.SelectedRoleID = $osrole.RoleID AND $osgroupmembership.GroupID = $osrole.GroupID)" + ." JOIN $osrole AS OwnerRole ON ($osgroup.OwnerRoleID = OwnerRole.RoleID AND $osgroup.GroupID = OwnerRole.GroupID)" + ." LEFT JOIN $osgrouprolemembership AS OwnerRoleMembership ON ($osgroup.OwnerRoleID = OwnerRoleMembership.RoleID + AND ($osgroup.GroupID = OwnerRoleMembership.GroupID) + AND ($osgroupmembership.AgentID = OwnerRoleMembership.AgentID))" + ." WHERE $osgroup.GroupID = '$groupID'"; + + $groupmemberResults = mysql_query($sql, $groupDBCon); + if (!$groupmemberResults) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + if (mysql_num_rows($groupmemberResults) == 0) + { + return array('succeed' => 'false', 'error' => 'No Group Members found', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + + $memberResults = array(); + while($memberInfo = mysql_fetch_assoc($groupmemberResults)) + { + $agentID = $memberInfo['AgentID']; + $sql = " SELECT BIT_OR($osrole.Powers) AS AgentPowers" + ." FROM $osgrouprolemembership JOIN $osrole ON ($osgrouprolemembership.GroupID = $osrole.GroupID AND $osgrouprolemembership.RoleID = $osrole.RoleID)" + ." WHERE $osgrouprolemembership.GroupID = '$groupID' AND $osgrouprolemembership.AgentID = '$agentID'"; + $memberPowersResult = mysql_query($sql, $groupDBCon); + if (!$memberPowersResult) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + if (mysql_num_rows($groupmemberResults) == 0) + { + $memberResults[$agentID] = array_merge($memberInfo, array('AgentPowers' => 0)); + } else { + $memberPowersInfo = mysql_fetch_assoc($memberPowersResult); + $memberResults[$agentID] = array_merge($memberInfo, $memberPowersInfo); + } + } + + return $memberResults; + } + + + function getAgentActiveMembership($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + secureRequest($params, FALSE); + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $agentID = $params['AgentID']; + + $sql = " SELECT $osgroup.GroupID, $osgroup.Name as GroupName, $osgroup.Charter, $osgroup.InsigniaID, $osgroup.FounderID" + ." , $osgroup.MembershipFee, $osgroup.OpenEnrollment, $osgroup.ShowInList, $osgroup.AllowPublish, $osgroup.MaturePublish" + ." , $osgroupmembership.Contribution, $osgroupmembership.ListInProfile, $osgroupmembership.AcceptNotices" + ." , $osgroupmembership.SelectedRoleID, $osrole.Title" + ." , $osagent.ActiveGroupID " + ." FROM $osagent JOIN $osgroup ON ($osgroup.GroupID = $osagent.ActiveGroupID)" + ." JOIN $osgroupmembership ON ($osgroup.GroupID = $osgroupmembership.GroupID AND $osagent.AgentID = $osgroupmembership.AgentID)" + ." JOIN $osrole ON ($osgroupmembership.SelectedRoleID = $osrole.RoleID AND $osgroupmembership.GroupID = $osrole.GroupID)" + ." WHERE $osagent.AgentID = '$agentID'"; + + $groupmembershipResult = mysql_query($sql, $groupDBCon); + if (!$groupmembershipResult) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + if (mysql_num_rows($groupmembershipResult) == 0) + { + return array('succeed' => 'false', 'error' => 'No Active Group Specified', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + $groupMembershipInfo = mysql_fetch_assoc($groupmembershipResult); + + $groupID = $groupMembershipInfo['GroupID']; + $sql = " SELECT BIT_OR($osrole.Powers) AS GroupPowers" + ." FROM $osgrouprolemembership JOIN $osrole ON ($osgrouprolemembership.GroupID = $osrole.GroupID AND $osgrouprolemembership.RoleID = $osrole.RoleID)" + ." WHERE $osgrouprolemembership.GroupID = '$groupID' AND $osgrouprolemembership.AgentID = '$agentID'"; + $groupPowersResult = mysql_query($sql, $groupDBCon); + if (!$groupPowersResult) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + $groupPowersInfo = mysql_fetch_assoc($groupPowersResult); + + return array_merge($groupMembershipInfo, $groupPowersInfo); + } + + + function getAgentRoles($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $agentID = $params['AgentID']; + + $sql = " SELECT " + ." $osrole.RoleID, $osrole.GroupID, $osrole.Title, $osrole.Name, $osrole.Description, $osrole.Powers" + ." , CASE WHEN $osgroupmembership.SelectedRoleID = $osrole.RoleID THEN 1 ELSE 0 END AS Selected" + ." FROM $osgroupmembership JOIN $osgrouprolemembership ON ($osgroupmembership.GroupID = $osgrouprolemembership.GroupID " + ." AND $osgroupmembership.AgentID = $osgrouprolemembership.AgentID)" + ." JOIN $osrole ON ( $osgrouprolemembership.RoleID = $osrole.RoleID AND $osgrouprolemembership.GroupID = $osrole.GroupID)" + ." LEFT JOIN $osagent ON ($osagent.AgentID = $osgroupmembership.AgentID)" + ." WHERE $osgroupmembership.AgentID = '$agentID'"; + + if( isset($params['GroupID']) ) + { + $groupID = $params['GroupID']; + $sql .= " AND $osgroupmembership.GroupID = '$groupID'"; + } + + $roleResults = mysql_query($sql, $groupDBCon); + if (!$roleResults) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + if( mysql_num_rows($roleResults) == 0 ) + { + return array('succeed' => 'false', 'error' => 'None found', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + + $roles = array(); + while($role = mysql_fetch_assoc($roleResults)) + { + $ID = $role['GroupID'].$role['RoleID']; + $roles[$ID] = $role; + } + + return $roles; + } + + + function getGroupRoles($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params['GroupID']; + + $sql = " SELECT " + ." $osrole.RoleID, $osrole.Name, $osrole.Title, $osrole.Description, $osrole.Powers, count($osgrouprolemembership.AgentID) as Members" + ." FROM $osrole LEFT JOIN $osgrouprolemembership ON ($osrole.GroupID = $osgrouprolemembership.GroupID AND $osrole.RoleID = $osgrouprolemembership.RoleID)" + ." WHERE $osrole.GroupID = '$groupID'" + ." GROUP BY $osrole.RoleID, $osrole.Name, $osrole.Title, $osrole.Description, $osrole.Powers"; + + $roleResults = mysql_query($sql, $groupDBCon); + if (!$roleResults) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + if( mysql_num_rows($roleResults) == 0 ) + { + return array('succeed' => 'false', 'error' => 'No roles found for group', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + + $roles = array(); + while($role = mysql_fetch_assoc($roleResults)) + { + $RoleID = $role['RoleID']; + $roles[$RoleID] = $role; + } + + return $roles; + } + + + function getGroupRoleMembers($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params['GroupID']; + // $roleID = $params['RoleID']; + + $sql = " SELECT " + ." $osrole.RoleID, $osgrouprolemembership.AgentID" + ." FROM $osrole JOIN $osgrouprolemembership ON ($osrole.GroupID = $osgrouprolemembership.GroupID AND $osrole.RoleID = $osgrouprolemembership.RoleID)" + ." WHERE $osrole.GroupID = '$groupID'"; +// ." AND $osrole.RoleID = '$roleID'"; + + $memberResults = mysql_query($sql, $groupDBCon); + if (!$memberResults) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + $members = array(); + while($member = mysql_fetch_assoc($memberResults)) + { + $Key = $member['AgentID'] . $member['RoleID']; + $members[$Key ] = $member; + } + + return $members; + } + + + function setAgentGroupInfo($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + if (isset($params['AgentID'])) { + $agentID = $params['AgentID']; + } else { + $agentID = ""; + } + if (isset($params['GroupID'])) { + $groupID = $params['GroupID']; + } else { + $groupID = ""; + } + if (isset($params['SelectedRoleID'])) { + $roleID = $params['SelectedRoleID']; + } else { + $roleID = ""; + } + if (isset($params['AcceptNotice'])) { + $acceptNotices = $params['AcceptNotices']; + } else { + $acceptNotices = ""; + } + if (isset($params['ListInProfile'])) { + $listInProfile = $params['ListInProfile']; + } else { + $listInProfile = ""; + } + + if( isset($requestingAgent) && ($requestingAgent != $uuidZero) && ($requestingAgent != $agentID) ) + { + return array('error' => "Agent can only change their own group info", 'params' => var_export($params, TRUE)); + } + + $sql = " UPDATE " + ." $osgroupmembership" + ." SET " + ." AgentID = '$agentID'"; + + if( isset($params['SelectedRoleID']) ) + { + $sql .=" , SelectedRoleID = '$roleID'"; + } + if( isset($params['AcceptNotices']) ) + { + $sql .=" , AcceptNotices = '$acceptNotices'"; + } + if( isset($params['ListInProfile']) ) + { + $sql .=" , ListInProfile = '$listInProfile'"; + } + + $sql .=" WHERE $osgroupmembership.GroupID = '$groupID' AND $osgroupmembership.AgentID = '$agentID'"; + + $memberResults = mysql_query($sql, $groupDBCon); + if (!$memberResults) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + return array('success'=> 'true'); + } + + + function getGroupNotices($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params['GroupID']; + + + $sql = " SELECT " + ." GroupID, NoticeID, Timestamp, FromName, Subject, Message, BinaryBucket" + ." FROM $osgroupnotice" + ." WHERE $osgroupnotice.GroupID = '$groupID'"; + + $results = mysql_query($sql, $groupDBCon); + if (!$results) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + if( mysql_num_rows($results) == 0 ) + { + return array('succeed' => 'false', 'error' => 'No Notices', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + + $notices = array(); + while($notice = mysql_fetch_assoc($results)) + { + $NoticeID = $notice['NoticeID']; + $notices[$NoticeID] = $notice; + } + + return $notices; + } + + + function getGroupNotice($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $noticeID = $params['NoticeID']; + + + $sql = " SELECT " + ." GroupID, NoticeID, Timestamp, FromName, Subject, Message, BinaryBucket" + ." FROM $osgroupnotice" + ." WHERE $osgroupnotice.NoticeID = '$noticeID'"; + + $results = mysql_query($sql, $groupDBCon); + if (!$results) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + if( mysql_num_rows($results) == 0 ) + { + return array('succeed' => 'false', 'error' => 'Group Notice Not Found', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + + return mysql_fetch_assoc($results); + } + + + function addGroupNotice($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params['GroupID']; + $noticeID = $params['NoticeID']; + $fromName = addslashes($params['FromName']); + $subject = addslashes($params['Subject']); + $binaryBucket = $params['BinaryBucket']; + $message = addslashes($params['Message']); + $timeStamp = $params['TimeStamp']; + + if( is_array($error = checkGroupPermission($groupID, $groupPowers['SendNotices'])) ) + { + return $error; + } + + $sql = " INSERT INTO $osgroupnotice" + ." (GroupID, NoticeID, Timestamp, FromName, Subject, Message, BinaryBucket)" + ." VALUES " + ." ('$groupID', '$noticeID', $timeStamp, '$fromName', '$subject', '$message', '$binaryBucket')"; + + $results = mysql_query($sql, $groupDBCon); + if (!$results) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + return array('success' => 'true'); + } + + + function addAgentToGroupInvite($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $inviteID = $params['InviteID']; + $groupID = $params['GroupID']; + $roleID = $params['RoleID']; + $agentID = $params['AgentID']; + + if( is_array($error = checkGroupPermission($groupID, $groupPowers['AssignMember'])) ) + { + return $error; + } + + // Remove any existing invites for this agent to this group + $sql = " DELETE FROM $osgroupinvite" + ." WHERE $osgroupinvite.AgentID = '$agentID' AND $osgroupinvite.GroupID = '$groupID'"; + + $results = mysql_query($sql, $groupDBCon); + if (!$results) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + // Add new invite for this agent to this group for the specifide role + $sql = " INSERT INTO $osgroupinvite" + ." (InviteID, GroupID, RoleID, AgentID) VALUES ('$inviteID', '$groupID', '$roleID', '$agentID')"; + + $results = mysql_query($sql, $groupDBCon); + if (!$results) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + return array('success' => 'true'); + } + + + function getAgentToGroupInvite($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $inviteID = $params['InviteID']; + + + $sql = " SELECT GroupID, RoleID, AgentID FROM $osgroupinvite" + ." WHERE $osgroupinvite.InviteID = '$inviteID'"; + + $results = mysql_query($sql, $groupDBCon); + if (!$results) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + if( mysql_num_rows($results) == 1 ) + { + $inviteInfo = mysql_fetch_assoc($results); + $groupID = $inviteInfo['GroupID']; + $roleID = $inviteInfo['RoleID']; + $agentID = $inviteInfo['AgentID']; + + return array('success' => 'true', 'GroupID'=>$groupID, 'RoleID'=>$roleID, 'AgentID'=>$agentID); + } else { + return array('succeed' => 'false', 'error' => 'Invitation not found', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + } + + + function removeAgentToGroupInvite($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $inviteID = $params['InviteID']; + + $sql = " DELETE FROM $osgroupinvite" + ." WHERE $osgroupinvite.InviteID = '$inviteID'"; + + $results = mysql_query($sql, $groupDBCon); + if (!$results) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysql_error(), 'params' => var_export($params, TRUE)); + } + + return array('success' => 'true'); + } + + + function secureRequest($params, $write = FALSE) + { + global $GroupWriteKey, $GroupReadKey, $VerifiedReadKey, $VerifiedWriteKey, $GroupRequireAgentAuthForWrite, $requestingAgent; + + if( isset($GroupReadKey) && ($GroupReadKey != '') && (!isset($VerifiedReadKey) || ($VerifiedReadKey !== TRUE)) ) + { + if( !isset($params['ReadKey']) || ($params['ReadKey'] != $GroupReadKey ) ) + { + return array('error' => "Invalid (or No) Read Key Specified", 'params' => var_export($params, TRUE)); + } else { + $VerifiedReadKey = TRUE; + } + } + + if( ($write == TRUE) && isset($GroupWriteKey) && ($GroupWriteKey != '') && (!isset($VerifiedWriteKey) || ($VerifiedWriteKey !== TRUE)) ) + { + if( !isset($params['WriteKey']) || ($params['WriteKey'] != $GroupWriteKey ) ) + { + return array('error' => "Invalid (or No) Write Key Specified", 'params' => var_export($params, TRUE)); + } else { + $VerifiedWriteKey = TRUE; + } + } + + if( ($write == TRUE) && isset($GroupRequireAgentAuthForWrite) && ($GroupRequireAgentAuthForWrite == TRUE) ) + { + // Note: my brain can't do boolean logic this morning, so just putting this here instead of integrating with line above. + // If the write key has already been verified for this request, don't check it again. + // This comes into play with methods that call other methods, such as CreateGroup() which calls Addrole() + if( isset($VerifiedWriteKey) && ($VerifiedWriteKey !== TRUE)) + { + return TRUE; + } + + if( !isset($params['RequestingAgentID']) + || !isset($params['RequestingAgentUserService']) + || !isset($params['RequestingSessionID']) + ) + { + return array('error' => "Requesting AgentID and SessionID must be specified", 'params' => var_export($params, TRUE)); + } + + $requestingAgent = $params['RequestingAgentID']; + + // NOTE: an AgentID and SessionID of $uuidZero will likely be a region making a request, that is not tied to a specific agent making the request. + + $client = new xmlrpc_client($params['RequestingAgentUserService']); + $client->return_type = 'phpvals'; + + $verifyParams = new xmlrpcval(array('avatar_uuid' => new xmlrpcval($params['RequestingAgentID'], 'string') + ,'session_id' => new xmlrpcval($params['RequestingSessionID'], 'string')), 'struct'); + + $message = new xmlrpcmsg("check_auth_session", array($verifyParams)); + $resp = $client->send($message, 5); + if ($resp->faultCode()) + { + return array('error' => "Error validating AgentID and SessionID" + , 'xmlrpcerror'=> $resp->faultString() + , 'params' => var_export($params, TRUE)); + } + + $verifyReturn = $resp->value(); + + if( !isset($verifyReturn['auth_session']) || ($verifyReturn['auth_session'] != 'TRUE') ) + { + return array('error' => "UserService.check_auth_session() did not return TRUE" + , 'userservice' => var_export($verifyReturn, TRUE) + , 'params' => var_export($params, TRUE)); + + } + } + + return TRUE; + } + + + function checkGroupPermission($GroupID, $Permission) + { + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + + // If it isn't set to true, then always return true, otherwise verify they have perms + if( !isset($GroupEnforceGroupPerms) || ($GroupEnforceGroupPerms != TRUE) ) + { + return true; + } + + if( !isset($requestingAgent) || ($requestingAgent == $uuidZero) ) + { + return array('error' => 'Requesting agent was either not specified or not validated.' + , 'params' => var_export($params, TRUE)); + } + + $params = array('AgentID' => $requestingAgent, 'GroupID' => $GroupID); + $reqAgentMembership = getAgentGroupMembership($params); + + if( isset($reqAgentMembership['error'] ) ) + { + return array('error' => 'Could not get agent membership for group' + , 'params' => var_export($params, TRUE) + , 'nestederror' => $reqAgentMembership['error']); + } + + if( $reqAgentMembership['GroupPowers'] & $Permission != $Permission ) + { + return array('error' => 'Agent does not have group power to $Permission' + , 'params' => var_export($params, TRUE)); + } + } + + $s = new xmlrpc_server(array( + "test" => array("function" => "test") + , "groups.createGroup" => array("function" => "createGroup", "signature" => $common_sig) + , "groups.updateGroup" => array("function" => "updateGroup", "signature" => $common_sig) + , "groups.getGroup" => array("function" => "getGroup", "signature" => $common_sig) + , "groups.findGroups" => array("function" => "findGroups", "signature" => $common_sig) + + , "groups.getGroupRoles" => array("function" => "getGroupRoles", "signature" => $common_sig) + , "groups.addRoleToGroup" => array("function" => "addRoleToGroup", "signature" => $common_sig) + , "groups.removeRoleFromGroup" => array("function" => "removeRoleFromGroup", "signature" => $common_sig) + , "groups.updateGroupRole" => array("function" => "updateGroupRole", "signature" => $common_sig) + , "groups.getGroupRoleMembers" => array("function" => "getGroupRoleMembers", "signature" => $common_sig) + + , "groups.setAgentGroupSelectedRole" => array("function" => "setAgentGroupSelectedRole", "signature" => $common_sig) + , "groups.addAgentToGroupRole" => array("function" => "addAgentToGroupRole", "signature" => $common_sig) + , "groups.removeAgentFromGroupRole" => array("function" => "removeAgentFromGroupRole", "signature" => $common_sig) + + , "groups.getGroupMembers" => array("function" => "getGroupMembers", "signature" => $common_sig) + , "groups.addAgentToGroup" => array("function" => "addAgentToGroup", "signature" => $common_sig) + , "groups.removeAgentFromGroup" => array("function" => "removeAgentFromGroup", "signature" => $common_sig) + , "groups.setAgentGroupInfo" => array("function" => "setAgentGroupInfo", "signature" => $common_sig) + + , "groups.addAgentToGroupInvite" => array("function" => "addAgentToGroupInvite", "signature" => $common_sig) + , "groups.getAgentToGroupInvite" => array("function" => "getAgentToGroupInvite", "signature" => $common_sig) + , "groups.removeAgentToGroupInvite" => array("function" => "removeAgentToGroupInvite", "signature" => $common_sig) + + , "groups.setAgentActiveGroup" => array("function" => "setAgentActiveGroup", "signature" => $common_sig) + , "groups.getAgentGroupMembership" => array("function" => "getAgentGroupMembership", "signature" => $common_sig) + , "groups.getAgentGroupMemberships" => array("function" => "getAgentGroupMemberships", "signature" => $common_sig) + , "groups.getAgentActiveMembership" => array("function" => "getAgentActiveMembership", "signature" => $common_sig) + , "groups.getAgentRoles" => array("function" => "getAgentRoles", "signature" => $common_sig) + + , "groups.getGroupNotices" => array("function" => "getGroupNotices", "signature" => $common_sig) + , "groups.getGroupNotice" => array("function" => "getGroupNotice", "signature" => $common_sig) + , "groups.addGroupNotice" => array("function" => "addGroupNotice", "signature" => $common_sig) + + ), false); + + $s->functions_parameters_type = 'phpvals'; + if (isset($debugXMLRPC) && $debugXMLRPC > 0 && isset($debugXMLRPCFile) && $debugXMLRPCFile != "") + { + $s->setDebug($debugXMLRPC); + } + $s->service(); + + if (isset($debugXMLRPC) && $debugXMLRPC > 0 && isset($debugXMLRPCFile) && $debugXMLRPCFile != "") + { + $f = fopen($debugXMLRPCFile,"a"); + fwrite($f,"\n----- " . date("Y-m-d H:i:s") . " -----\n"); + $debugInfo = $s->serializeDebug(); + //$debugInfo = split("\n",$debugInfo); + $debugInfo = explode("\n",$debugInfo); + unset($debugInfo[0]); + unset($debugInfo[count($debugInfo) -1]); + $debugInfo = join("\n",$debugInfo); + fwrite($f,base64_decode($debugInfo)); + fclose($f); + } + + mysql_close($groupDBCon); + diff --git a/helper-php/DTLNSL_helper_scripts/helper/xmlrpci.php b/helper-php/DTLNSL_helper_scripts/helper/xmlrpci.php new file mode 100644 index 0000000..1c25b76 --- /dev/null +++ b/helper-php/DTLNSL_helper_scripts/helper/xmlrpci.php @@ -0,0 +1,1688 @@ + "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + + Methods that run without errors, but do not have the intended result should return as: + + return array('succeed' => 'false', 'message' => 'No Groups Found', 'params' => var_export($params, TRUE)); + + or if applicable: + + return array('succeed' => 'false', 'message' => 'What went wrong', 'params' => var_export($params, TRUE), 'sql' => $sql); + */ + + + // Modified by Fumi.Iseki '09 5/31 + // Modified by Fumi.Iseki '14 3/4 + // Modified by Fumi.Iseki '14 5/15 for MySQLi + // Modified by Fumi.Iseki '16 7/14 + + include(dirname(__FILE__).'/phpxmlrpclib/xmlrpc.inc'); + include(dirname(__FILE__).'/phpxmlrpclib/xmlrpcs.inc'); + + // Global + $osagent = XMLGROUP_ACTIVE_TBL; + $osgroup = XMLGROUP_LIST_TBL; + $osgroupinvite = XMLGROUP_INVITE_TBL; + $osgroupmembership = XMLGROUP_MEMBERSHIP_TBL; + $osgroupnotice = XMLGROUP_NOTICE_TBL; + $osgrouprolemembership = XMLGROUP_ROLE_MEMBER_TBL; + $osrole = XMLGROUP_ROLE_TBL; + + + $groupPowers = array( + 'None' => 0, + /// Can send invitations to groups default role + 'Invite' => 1, + /// Can eject members from group + 'Eject' => 2, + /// Can toggle 'Open Enrollment' and change 'Signup fee' + 'ChangeOptions' => 4, + /// Can create new roles + 'CreateRole' => 8, + /// Can delete existing roles + 'DeleteRole' => 16, + /// Can change Role names, titles and descriptions + 'RoleProperties' => 32, + /// Can assign other members to assigners role + 'AssignMemberLimited' => 64, + /// Can assign other members to any role + 'AssignMember' => 128, + /// Can remove members from roles + 'RemoveMember' => 256, + /// Can assign and remove abilities in roles + 'ChangeActions' => 512, + /// Can change group Charter, Insignia, 'Publish on the web' and which + /// members are publicly visible in group member listings + 'ChangeIdentity' => 1024, + /// Can buy land or deed land to group + 'LandDeed' => 2048, + /// Can abandon group owned land to Governor Linden on mainland, or Estate owner for + /// private estates + 'LandRelease' => 4096, + /// Can set land for-sale information on group owned parcels + 'LandSetSale' => 8192, + /// Can subdivide and join parcels + 'LandDivideJoin' => 16384, + /// Can join group chat sessions + 'JoinChat' => 32768, + /// Can toggle "Show in Find Places" and set search category + 'FindPlaces' => 65536, + /// Can change parcel name, description, and 'Publish on web' settings + 'LandChangeIdentity' => 131072, + /// Can set the landing point and teleport routing on group land + 'SetLandingPoint' => 262144, + /// Can change music and media settings + 'ChangeMedia' => 524288, + /// Can toggle 'Edit Terrain' option in Land settings + 'LandEdit' => 1048576, + /// Can toggle various About Land > Options settings + 'LandOptions' => 2097152, + /// Can always terraform land, even if parcel settings have it turned off + 'AllowEditLand' => 4194304, + /// Can always fly while over group owned land + 'AllowFly' => 8388608, + /// Can always rez objects on group owned land + 'AllowRez' => 16777216, + /// Can always create landmarks for group owned parcels + 'AllowLandmark' => 33554432, + /// Can use voice chat in Group Chat sessions + 'AllowVoiceChat' => 67108864, + /// Can set home location on any group owned parcel + 'AllowSetHome' => 134217728, + /// Can modify public access settings for group owned parcels + 'LandManageAllowed' => 268435456, + /// Can manager parcel ban lists on group owned land + 'LandManageBanned' => 536870912, + /// Can manage pass list sales information + 'LandManagePasses' => 1073741824, + /// Can eject and freeze other avatars on group owned land + 'LandEjectAndFreeze' => 2147483648, + /// Can return objects set to group + 'ReturnGroupSet' => 4294967296, + /// Can return non-group owned/set objects + 'ReturnNonGroup' => 8589934592, + /// Can landscape using Linden plants + 'LandGardening' => 17179869184, + /// Can deed objects to group + 'DeedObject' => 34359738368, + /// Can moderate group chat sessions + 'ModerateChat' => 68719476736, + /// Can move group owned objects + 'ObjectManipulate' => 137438953472, + /// Can set group owned objects for-sale + 'ObjectSetForSale' => 274877906944, + /// Pay group liabilities and receive group dividends + 'Accountable' => 549755813888, + /// Can send group notices + 'SendNotices' => 1099511627776, + /// Can receive group notices + 'ReceiveNotices' => 2199023255552, + /// Can create group proposals + 'StartProposal' => 4398046511104, + /// Can vote on group proposals + 'VoteOnProposal' => 8796093022208, + /// Can return group owned objects + 'ReturnGroupOwned' => 17592186044416 + ); + + + $uuidZero = "00000000-0000-0000-0000-000000000000"; + + $groupDBCon = mysqli_connect($XMLGRP_DB_HOST, $XMLGRP_DB_USER, $XMLGRP_DB_PASS, $XMLGRP_DB_NAME); + if (!$groupDBCon) + { + die('Could not connect: ' . mysqli_connect_error()); + } + + // This is filled in by secure() + $requestingAgent = $uuidZero; + + + function test() + { + return array('name' => 'Joe','age' => 27); + } + + // Use a common signature for all the group functions -> struct foo($struct) + $common_sig = array(array($xmlrpcStruct, $xmlrpcStruct)); + + + function createGroup($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params["GroupID"]; + $name = addslashes( $params["Name"] ); + $charter = addslashes( $params["Charter"] ); + $insigniaID = $params["InsigniaID"]; + $founderID = $params["FounderID"]; + $membershipFee = $params["MembershipFee"]; + $openEnrollment = $params["OpenEnrollment"]; + $showInList = $params["ShowInList"]; + $allowPublish = $params["AllowPublish"]; + $maturePublish = $params["MaturePublish"]; + $ownerRoleID = $params["OwnerRoleID"]; + $everyonePowers = $params["EveryonePowers"]; + $ownersPowers = $params["OwnersPowers"]; + + // Create group + $sql = "INSERT INTO $osgroup + (GroupID, Name, Charter, InsigniaID, FounderID, MembershipFee, OpenEnrollment, ShowInList, AllowPublish, MaturePublish, OwnerRoleID) + VALUES + ('$groupID','$name','$charter','$insigniaID','$founderID',$membershipFee,$openEnrollment,$showInList,$allowPublish,$maturePublish,'$ownerRoleID')"; + + if (!mysqli_query($groupDBCon, $sql)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + // Create Everyone Role + // NOTE: FIXME: This is a temp fix until the libomv enum for group powers is fixed in OpenSim + $everyonePowers = 8796495740928; + $result = _addRoleToGroup(array('GroupID' => $groupID, 'RoleID' => $uuidZero, 'Name' => 'Everyone', + 'Description' => 'Everyone in the group is in the everyone role.', 'Title' => "Member of $name", 'Powers' => $everyonePowers)); + if( isset($result['error']) ) + { + return $result; + } + + // Create Owner Role + $result = _addRoleToGroup(array('GroupID' => $groupID, 'RoleID' => $ownerRoleID, 'Name' => 'Owners', + 'Description' => "Owners of $name", 'Title' => "Owner of $name", 'Powers' => $ownersPowers)); + if( isset($result['error']) ) + { + return $result; + } + + // Add founder to group, will automatically place them in the Everyone Role, also places them in specified Owner Role + $result = _addAgentToGroup(array('AgentID' => $founderID, 'GroupID' => $groupID, 'RoleID' => $ownerRoleID)); + if( isset($result['error']) ) + { + return $result; + } + + // Select the owner's role for the founder + $result = _setAgentGroupSelectedRole(array('AgentID' => $founderID, 'RoleID' => $ownerRoleID, 'GroupID' => $groupID)); + if( isset($result['error']) ) + { + return $result; + } + + // Set the new group as the founder's active group + $result = _setAgentActiveGroup(array('AgentID' => $founderID, 'GroupID' => $groupID)); + if( isset($result['error']) ) + { + return $result; + } + + + return getGroup(array("GroupID"=>$groupID)); + } + + + // Private method, does not include security, to only be called from places that have already verified security + function _addRoleToGroup($params) + { + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params['GroupID']; + $roleID = $params['RoleID']; + $name = addslashes( $params['Name'] ); + $desc = addslashes( $params['Description'] ); + $title = addslashes( $params['Title'] ); + $powers = $params['Powers']; + + $sql = " INSERT INTO $osrole (GroupID, RoleID, Name, Description, Title, Powers) VALUES " + ." ('$groupID', '$roleID', '$name', '$desc', '$title', $powers)"; + + if (!mysqli_query($groupDBCon, $sql)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon) + , 'method' => 'addRoleToGroup' + , 'params' => var_export($params, TRUE)); + } + + return array("success" => "true"); + } + + + function addRoleToGroup($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + $groupID = $params['GroupID']; + + // Verify the requesting agent has permission + if( is_array($error = checkGroupPermission($groupID, $groupPowers['CreateRole'])) ) + { + return $error; + } + + return _addRoleToGroup($params); + } + + + function updateGroupRole($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params['GroupID']; + $roleID = $params['RoleID']; + $name = addslashes( $params['Name'] ); + $desc = addslashes( $params['Description'] ); + $title = addslashes( $params['Title'] ); + $powers = $params['Powers']; + + // Verify the requesting agent has permission + if( is_array($error = checkGroupPermission($groupID, $groupPowers['RoleProperties'])) ) + { + return $error; + } + + + $sql = " UPDATE $osrole SET RoleID = '$roleID' "; + if( isset($params['Name']) ) + { + $sql .= ", Name = '$name'"; + } + if( isset($params['Description']) ) + { + $sql .= ", Description = '$desc'"; + } + if( isset($params['Title']) ) + { + $sql .= ", Title = '$title'"; + } + if( isset($params['Powers']) ) + { + $sql .= ", Powers = $powers"; + } + + $sql .= " WHERE GroupID = '$groupID' AND RoleID = '$roleID'"; + + if (!mysqli_query($groupDBCon, $sql)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + return array("success" => "true"); + } + + + function removeRoleFromGroup($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params['GroupID']; + $roleID = $params['RoleID']; + + if( is_array($error = checkGroupPermission($groupID, $groupPowers['RoleProperties'])) ) + { + return $error; + } + + /// 1. Remove all members from Role + /// 2. Set selected Role to uuidZero for anyone that had the role selected + /// 3. Delete roll + + $sql = "DELETE FROM $osgrouprolemembership WHERE GroupID = '$groupID' AND RoleID = '$roleID'"; + if (!mysqli_query($groupDBCon, $sql)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + $sql = "UPDATE $osgroupmembership SET SelectedRoleID = '$uuidZero' WHERE GroupID = '$groupID' AND SelectedRoleID = '$roleID'"; + if (!mysqli_query($groupDBCon, $sql)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + $sql = "DELETE FROM $osrole WHERE GroupID = '$groupID' AND RoleID = '$roleID'"; + if (!mysqli_query($groupDBCon, $sql)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + return array("success" => "true"); + } + + + function getGroup($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + return _getGroup($params); + } + + + function _getGroup($params) + { + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $sql = " SELECT $osgroup.GroupID, $osgroup.Name, Charter, InsigniaID, FounderID, MembershipFee, OpenEnrollment, ShowInList, AllowPublish, MaturePublish, OwnerRoleID" + ." , count($osrole.RoleID) as GroupRolesCount, count($osgroupmembership.AgentID) as GroupMembershipCount " + ." FROM $osgroup " + ." LEFT JOIN $osrole ON ($osgroup.GroupID = $osrole.GroupID)" + ." LEFT JOIN $osgroupmembership ON ($osgroup.GroupID = $osgroupmembership.GroupID)" + ." WHERE "; + if( isset($params['GroupID']) ) + { + $sql .= "$osgroup.GroupID = '".$params['GroupID']."'"; + + } else if( isset($params['Name']) ) + { + $sql .= "$osgroup.Name = '".addslashes($params['Name'])."'"; + } else { + return array("error" => "Must specify GroupID or Name"); + } + + $sql .= " GROUP BY $osgroup.GroupID, $osgroup.name, charter, insigniaID, founderID, membershipFee, openEnrollment, showInList, allowPublish, maturePublish, ownerRoleID"; + + $result = mysqli_query($groupDBCon, $sql); + + if (!$result) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + if (mysqli_num_rows($result) == 0) + { + return array('succeed' => 'false', 'error' => 'Group Not Found', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + + return mysqli_fetch_assoc($result); + } + + + function updateGroup($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params["GroupID"]; + $charter = addslashes( $params["Charter"] ); + $insigniaID = $params["InsigniaID"]; + $membershipFee = $params["MembershipFee"]; + $openEnrollment = $params["OpenEnrollment"]; + $showInList = $params["ShowInList"]; + $allowPublish = $params["AllowPublish"]; + $maturePublish = $params["MaturePublish"]; + + if( is_array($error = checkGroupPermission($groupID, $groupPowers['ChangeOptions'])) ) + { + return $error; + } + + // Create group + $sql = "UPDATE $osgroup + SET + Charter = '$charter' + , InsigniaID = '$insigniaID' + , MembershipFee = $membershipFee + , OpenEnrollment= $openEnrollment + , ShowInList = $showInList + , AllowPublish = $allowPublish + , MaturePublish = $maturePublish + WHERE + GroupID = '$groupID'"; + + if (!mysqli_query($groupDBCon, $sql)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + return array('success' => 'true'); + } + + + function findGroups($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $search = addslashes( $params['Search'] ); + + // FULLTEXT indexes is not supported in InnoDB :( + $sql = " SELECT $osgroup.GroupID, $osgroup.Name, count($osgroupmembership.AgentID) as Members " + ." FROM $osgroup LEFT JOIN $osgroupmembership ON ($osgroup.GroupID = $osgroupmembership.GroupID) " + ." WHERE " + // ." ( MATCH ($osgroup.name) AGAINST ('$search' IN BOOLEAN MODE)" + // ." OR $osgroup.name LIKE '%$search%'" + // ." OR $osgroup.name REGEXP '$search'" + // ." ) AND ShowInList = 1" + ." ( $osgroup.name LIKE '%$search%'" + ." OR $osgroup.name REGEXP '$search'" + ." ) AND ShowInList = 1" + ." GROUP BY $osgroup.GroupID, $osgroup.Name"; + + $result = mysqli_query($groupDBCon, $sql); + + if (!$result) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + if( mysqli_num_rows($result) == 0 ) + { + return array('succeed' => 'false', 'error' => 'No groups found.', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + + $results = array(); + + while ($row = mysqli_fetch_assoc($result)) + { + $groupID = $row['GroupID']; + $results[$groupID] = $row; + } + + return array('results' => $results, 'success' => TRUE); + } + + + function _setAgentActiveGroup($params) + { + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $agentID = $params['AgentID']; + $groupID = $params['GroupID']; + + $sql = " UPDATE $osagent " + ." SET ActiveGroupID = '$groupID'" + ." WHERE AgentID = '$agentID'"; + + if (!mysqli_query($groupDBCon, $sql)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + if( mysqli_affected_rows($groupDBCon) == 0 ) + { + $sql = " INSERT INTO $osagent (ActiveGroupID, AgentID) VALUES " + ." ('$groupID', '$agentID')"; + + if (!mysqli_query($groupDBCon, $sql)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + } + + return array("success" => "true"); + } + + + function setAgentActiveGroup($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + $agentID = $params['AgentID']; + $groupID = $params['GroupID']; + + if( isset($requestingAgent) && ($requestingAgent != $uuidZero) && ($requestingAgent != $agentID) ) + { + return array('error' => "Agent can only change their own Selected Group Role", 'params' => var_export($params, TRUE)); + } + + return _setAgentActiveGroup($params); + } + + + function addAgentToGroup($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params["GroupID"]; + $agentID = $params["AgentID"]; + + if( is_array($error = checkGroupPermission($groupID, $groupPowers['AssignMember'])) ) + { + // If they don't have direct permission, check to see if the group is marked for open enrollment + $groupInfo = _getGroup( array ('GroupID'=>$groupID) ); + + if( isset($groupInfo['error'])) + { + return $groupInfo; + } + + if($groupInfo['OpenEnrollment'] != 1) + { + // Group is not open enrollment, check if the specified agentid has an invite + $sql = " SELECT GroupID, RoleID, AgentID FROM $osgroupinvite" + ." WHERE $osgroupinvite.AgentID = '$agentID' AND $osgroupinvite.GroupID = '$groupID'"; + + $results = mysqli_query($groupDBCon, $sql); + if (!$results) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + if( mysqli_num_rows($results) == 1 ) + { + // if there is an invite, make sure we're adding the user to the role specified in the invite + $inviteInfo = mysqli_fetch_assoc($results); + $params['RoleID'] = $inviteInfo['RoleID']; + } else { + // Not openenrollment, not invited, return permission denied error + return $error; + } + + } + } + + return _addAgentToGroup($params); + } + + + // Private method, does not include security, to only be called from places that have already verified security + function _addAgentToGroup($params) + { + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $agentID = $params["AgentID"]; + $groupID = $params["GroupID"]; + + $roleID = $uuidZero; + if( isset($params["RoleID"]) ) + { + $roleID = $params["RoleID"]; + } + + // Check if agent already a member + $sql = " SELECT count(AgentID) as isMember FROM $osgroupmembership WHERE AgentID = '$agentID' AND GroupID = '$groupID'"; + $result = mysqli_query($groupDBCon, $sql); + if (!$result) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + // If not a member, add membership, select role (defaults to uuidZero, or everyone role) + //if( mysql_result($result, 0) == 0 ) + $row = mysqli_fetch_row($result); + if(is_array($row) and $row[0]==0) + { + $sql = " INSERT INTO $osgroupmembership (GroupID, AgentID, Contribution, ListInProfile, AcceptNotices, SelectedRoleID) VALUES " + ."('$groupID','$agentID', 0, 1, 1,'$roleID')"; + + if (!mysqli_query($groupDBCon, $sql)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + } + + // Make sure they're in the Everyone role + $result = _addAgentToGroupRole(array("GroupID" => $groupID, "RoleID" => $uuidZero, "AgentID" => $agentID)); + if( isset($result['error']) ) + { + return $result; + } + + // Make sure they're in specified role, if they were invited + if( $roleID != $uuidZero ) + { + $result = _addAgentToGroupRole(array("GroupID" => $groupID, "RoleID" => $roleID, "AgentID" => $agentID)); + if( isset($result['error']) ) + { + return $result; + } + } + + return array("success" => "true"); + } + + + function removeAgentFromGroup($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $agentID = $params["AgentID"]; + $groupID = $params["GroupID"]; + + if( is_array($error = checkGroupPermission($groupID, $groupPowers['RemoveMember'])) ) + { + return $error; + } + + // 1. If group is agent's active group, change active group to uuidZero + // 2. Remove Agent from group (osgroupmembership) + // 3. Remove Agent from all of the groups roles (osgrouprolemembership) + + $sql = " UPDATE $osagent " + ." SET ActiveGroupID = '$uuidZero'" + ." WHERE AgentID = '$agentID' AND ActiveGroupID = '$groupID'"; + if (!mysqli_query($groupDBCon, $sql)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + $sql = " DELETE FROM $osgroupmembership " + ." WHERE AgentID = '$agentID' AND GroupID = '$groupID'"; + if (!mysqli_query($groupDBCon, $sql)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + $sql = " DELETE FROM $osgrouprolemembership " + ." WHERE AgentID = '$agentID' AND GroupID = '$groupID'"; + if (!mysqli_query($groupDBCon, $sql)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + return array("success" => "true"); + } + + + function _addAgentToGroupRole($params) + { + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $agentID = $params["AgentID"]; + $groupID = $params["GroupID"]; + $roleID = $params["RoleID"]; + + // Check if agent already a member + $sql = " SELECT count(AgentID) as isMember FROM $osgrouprolemembership WHERE AgentID = '$agentID' AND RoleID = '$roleID' AND GroupID = '$groupID'"; + $result = mysqli_query($groupDBCon, $sql); + if (!$result) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + //if( mysql_result($result, 0) == 0 ) + $row = mysqli_fetch_row($result); + if(is_array($row) and $row[0]==0) + { + $sql = " INSERT INTO $osgrouprolemembership (GroupID, RoleID, AgentID) VALUES " + ."('$groupID', '$roleID', '$agentID')"; + + if (!mysqli_query($groupDBCon, $sql)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + } + + return array("success" => "true"); + } + + + function addAgentToGroupRole($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $agentID = $params["AgentID"]; + $groupID = $params["GroupID"]; + $roleID = $params["RoleID"]; + + // Check if being assigned to Owners role, assignments to an owners role can only be requested by owners. + $sql = " SELECT OwnerRoleID, AgentID " + ." FROM $osgroup LEFT JOIN $osgrouprolemembership ON ($osgroup.GroupID = $osgrouprolemembership.GroupID AND $osgroup.OwnerRoleID = $osgrouprolemembership.RoleID) " + ." WHERE $osgrouprolemembership.AgentID = '$agentID' AND $osgroup.GroupID = '$groupID'"; + + $results = mysqli_query($groupDBCon, $sql); + if (!$results) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + if( mysqli_num_rows($results) != 0 ) + { + $ownerRoleInfo = mysqli_fetch_assoc($results); + if( ($ownerRoleInfo['OwnerRoleID'] == $roleID) && ($ownerRoleInfo['AgentID'] != $requestingAgent) ) + { + return array('error' => "Requesting agent $requestingAgent is not a member of the Owners Role and cannot add members to the owners role.", + 'params' => var_export($params, TRUE)); + } + } + + if( is_array($error = checkGroupPermission($groupID, $groupPowers['AssignMember'])) ) + { + return $error; + } + + return _addAgentToGroupRole($params); + } + + + function removeAgentFromGroupRole($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $agentID = $params["AgentID"]; + $groupID = $params["GroupID"]; + $roleID = $params["RoleID"]; + + if( is_array($error = checkGroupPermission($groupID, $groupPowers['AssignMember'])) ) + { + return $error; + } + + // If agent has this role selected, change their selection to everyone (uuidZero) role + $sql = " UPDATE $osgroupmembership SET SelectedRoleID = '$uuidZero' WHERE AgentID = '$agentID' AND GroupID = '$groupID' AND SelectedRoleID = '$roleID'"; + $result = mysqli_query($groupDBCon, $sql); + if (!$result) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + $sql = " DELETE FROM $osgrouprolemembership WHERE AgentID = '$agentID' AND GroupID = '$groupID' AND RoleID = '$roleID'"; + + if (!mysqli_query($groupDBCon, $sql)) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + return array("success" => "true"); + } + + + function _setAgentGroupSelectedRole($params) + { + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $agentID = $params["AgentID"]; + $groupID = $params["GroupID"]; + $roleID = $params["RoleID"]; + + $sql = " UPDATE $osgroupmembership SET SelectedRoleID = '$roleID' WHERE AgentID = '$agentID' AND GroupID = '$groupID'"; + $result = mysqli_query($groupDBCon, $sql); + if (!$result) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + return array('success' => 'true'); + } + + + function setAgentGroupSelectedRole($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + $agentID = $params["AgentID"]; + $groupID = $params["GroupID"]; + $roleID = $params["RoleID"]; + + if( isset($requestingAgent) && ($requestingAgent != $uuidZero) && ($requestingAgent != $agentID) ) + { + return array('error' => "Agent can only change their own Selected Group Role", 'params' => var_export($params, TRUE)); + } + + return _setAgentGroupSelectedRole($params); + } + + + function getAgentGroupMembership($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params['GroupID']; + $agentID = $params['AgentID']; + + $sql = " SELECT $osgroup.GroupID, $osgroup.Name as GroupName, $osgroup.Charter, $osgroup.InsigniaID, $osgroup.FounderID" + ." , $osgroup.MembershipFee, $osgroup.OpenEnrollment, $osgroup.ShowInList, $osgroup.AllowPublish, $osgroup.MaturePublish" + ." , $osgroupmembership.Contribution, $osgroupmembership.ListInProfile, $osgroupmembership.AcceptNotices" + ." , $osgroupmembership.SelectedRoleID, $osrole.Title" + ." , $osagent.ActiveGroupID " + ." FROM $osgroup JOIN $osgroupmembership ON ($osgroup.GroupID = $osgroupmembership.GroupID)" + ." JOIN $osrole ON ($osgroupmembership.SelectedRoleID = $osrole.RoleID AND $osgroupmembership.GroupID = $osrole.GroupID)" + ." JOIN $osagent ON ($osagent.AgentID = $osgroupmembership.AgentID)" + ." WHERE $osgroup.GroupID = '$groupID' AND $osgroupmembership.AgentID = '$agentID'"; + + $groupmembershipResult = mysqli_query($groupDBCon, $sql); + if (!$groupmembershipResult) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + if( mysqli_num_rows($groupmembershipResult) == 0 ) + { + return array('succeed' => 'false', 'error' => 'None Found', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + + $groupMembershipInfo = mysqli_fetch_assoc($groupmembershipResult); + + $sql = " SELECT BIT_OR($osrole.Powers) AS GroupPowers" + ." FROM $osgrouprolemembership JOIN $osrole ON ($osgrouprolemembership.GroupID = $osrole.GroupID AND $osgrouprolemembership.RoleID = $osrole.RoleID)" + ." WHERE $osgrouprolemembership.GroupID = '$groupID' AND $osgrouprolemembership.AgentID = '$agentID'"; + $groupPowersResult = mysqli_query($groupDBCon, $sql); + if (!$groupPowersResult) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + $groupPowersInfo = mysqli_fetch_assoc($groupPowersResult); + + return array_merge($groupMembershipInfo, $groupPowersInfo); + } + + + function getAgentGroupMemberships($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + $agentID = $params['AgentID']; + + $sql = " SELECT $osgroup.GroupID, $osgroup.Name as GroupName, $osgroup.Charter, $osgroup.InsigniaID, $osgroup.FounderID" + ." , $osgroup.MembershipFee, $osgroup.OpenEnrollment, $osgroup.ShowInList, $osgroup.AllowPublish, $osgroup.MaturePublish" + ." , $osgroupmembership.Contribution, $osgroupmembership.ListInProfile, $osgroupmembership.AcceptNotices" + ." , $osgroupmembership.SelectedRoleID, $osrole.Title" + ." , IFNULL($osagent.ActiveGroupID, '$uuidZero') AS ActiveGroupID" + ." FROM $osgroup JOIN $osgroupmembership ON ($osgroup.GroupID = $osgroupmembership.GroupID)" + ." JOIN $osrole ON ($osgroupmembership.SelectedRoleID = $osrole.RoleID AND $osgroupmembership.GroupID = $osrole.GroupID)" + ." LEFT JOIN $osagent ON ($osagent.AgentID = $osgroupmembership.AgentID)" + ." WHERE $osgroupmembership.AgentID = '$agentID'"; + + $groupmembershipResults = mysqli_query($groupDBCon, $sql); + if (!$groupmembershipResults) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + if( mysqli_num_rows($groupmembershipResults) == 0 ) + { + return array('succeed' => 'false', 'error' => 'No Memberships', 'params' => var_export($params, TRUE), 'sql' => $sql); + + } + + $groupResults = array(); + while($groupMembershipInfo = mysqli_fetch_assoc($groupmembershipResults)) + { + $groupID = $groupMembershipInfo['GroupID']; + $sql = " SELECT BIT_OR($osrole.Powers) AS GroupPowers" + ." FROM $osgrouprolemembership JOIN $osrole ON ($osgrouprolemembership.GroupID = $osrole.GroupID AND $osgrouprolemembership.RoleID = $osrole.RoleID)" + ." WHERE $osgrouprolemembership.GroupID = '$groupID' AND $osgrouprolemembership.AgentID = '$agentID'"; + $groupPowersResult = mysqli_query($groupDBCon, $sql); + if (!$groupPowersResult) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + $groupPowersInfo = mysqli_fetch_assoc($groupPowersResult); + $groupResults[$groupID] = array_merge($groupMembershipInfo, $groupPowersInfo); + } + + return $groupResults; + } + + + function getGroupMembers($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params['GroupID']; + + $sql = " SELECT $osgroupmembership.AgentID" + ." , $osgroupmembership.Contribution, $osgroupmembership.ListInProfile, $osgroupmembership.AcceptNotices" + ." , $osgroupmembership.SelectedRoleID, $osrole.Title" + ." , CASE WHEN OwnerRoleMembership.AgentID IS NOT NULL THEN 1 ELSE 0 END AS IsOwner" + ." FROM $osgroup JOIN $osgroupmembership ON ($osgroup.GroupID = $osgroupmembership.GroupID)" + ." JOIN $osrole ON ($osgroupmembership.SelectedRoleID = $osrole.RoleID AND $osgroupmembership.GroupID = $osrole.GroupID)" + ." JOIN $osrole AS OwnerRole ON ($osgroup.OwnerRoleID = OwnerRole.RoleID AND $osgroup.GroupID = OwnerRole.GroupID)" + ." LEFT JOIN $osgrouprolemembership AS OwnerRoleMembership ON ($osgroup.OwnerRoleID = OwnerRoleMembership.RoleID + AND ($osgroup.GroupID = OwnerRoleMembership.GroupID) + AND ($osgroupmembership.AgentID = OwnerRoleMembership.AgentID))" + ." WHERE $osgroup.GroupID = '$groupID'"; + + $groupmemberResults = mysqli_query($groupDBCon, $sql); + if (!$groupmemberResults) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + if (mysqli_num_rows($groupmemberResults) == 0) + { + return array('succeed' => 'false', 'error' => 'No Group Members found', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + + $memberResults = array(); + while($memberInfo = mysqli_fetch_assoc($groupmemberResults)) + { + $agentID = $memberInfo['AgentID']; + $sql = " SELECT BIT_OR($osrole.Powers) AS AgentPowers" + ." FROM $osgrouprolemembership JOIN $osrole ON ($osgrouprolemembership.GroupID = $osrole.GroupID AND $osgrouprolemembership.RoleID = $osrole.RoleID)" + ." WHERE $osgrouprolemembership.GroupID = '$groupID' AND $osgrouprolemembership.AgentID = '$agentID'"; + $memberPowersResult = mysqli_query($groupDBCon, $sql); + if (!$memberPowersResult) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + if (mysqli_num_rows($groupmemberResults) == 0) + { + $memberResults[$agentID] = array_merge($memberInfo, array('AgentPowers' => 0)); + } else { + $memberPowersInfo = mysqli_fetch_assoc($memberPowersResult); + $memberResults[$agentID] = array_merge($memberInfo, $memberPowersInfo); + } + } + + return $memberResults; + } + + + function getAgentActiveMembership($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + secureRequest($params, FALSE); + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $agentID = $params['AgentID']; + + $sql = " SELECT $osgroup.GroupID, $osgroup.Name as GroupName, $osgroup.Charter, $osgroup.InsigniaID, $osgroup.FounderID" + ." , $osgroup.MembershipFee, $osgroup.OpenEnrollment, $osgroup.ShowInList, $osgroup.AllowPublish, $osgroup.MaturePublish" + ." , $osgroupmembership.Contribution, $osgroupmembership.ListInProfile, $osgroupmembership.AcceptNotices" + ." , $osgroupmembership.SelectedRoleID, $osrole.Title" + ." , $osagent.ActiveGroupID " + ." FROM $osagent JOIN $osgroup ON ($osgroup.GroupID = $osagent.ActiveGroupID)" + ." JOIN $osgroupmembership ON ($osgroup.GroupID = $osgroupmembership.GroupID AND $osagent.AgentID = $osgroupmembership.AgentID)" + ." JOIN $osrole ON ($osgroupmembership.SelectedRoleID = $osrole.RoleID AND $osgroupmembership.GroupID = $osrole.GroupID)" + ." WHERE $osagent.AgentID = '$agentID'"; + + $groupmembershipResult = mysqli_query($groupDBCon, $sql); + if (!$groupmembershipResult) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + if (mysqli_num_rows($groupmembershipResult) == 0) + { + return array('succeed' => 'false', 'error' => 'No Active Group Specified', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + $groupMembershipInfo = mysqli_fetch_assoc($groupmembershipResult); + + $groupID = $groupMembershipInfo['GroupID']; + $sql = " SELECT BIT_OR($osrole.Powers) AS GroupPowers" + ." FROM $osgrouprolemembership JOIN $osrole ON ($osgrouprolemembership.GroupID = $osrole.GroupID AND $osgrouprolemembership.RoleID = $osrole.RoleID)" + ." WHERE $osgrouprolemembership.GroupID = '$groupID' AND $osgrouprolemembership.AgentID = '$agentID'"; + $groupPowersResult = mysqli_query($groupDBCon, $sql); + if (!$groupPowersResult) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + $groupPowersInfo = mysqli_fetch_assoc($groupPowersResult); + + return array_merge($groupMembershipInfo, $groupPowersInfo); + } + + + function getAgentRoles($params=null) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $agentID = $params['AgentID']; + + $sql = " SELECT " + ." $osrole.RoleID, $osrole.GroupID, $osrole.Title, $osrole.Name, $osrole.Description, $osrole.Powers" + ." , CASE WHEN $osgroupmembership.SelectedRoleID = $osrole.RoleID THEN 1 ELSE 0 END AS Selected" + ." FROM $osgroupmembership JOIN $osgrouprolemembership ON ($osgroupmembership.GroupID = $osgrouprolemembership.GroupID" + ." AND $osgroupmembership.AgentID = $osgrouprolemembership.AgentID)" + ." JOIN $osrole ON ( $osgrouprolemembership.RoleID = $osrole.RoleID AND $osgrouprolemembership.GroupID = $osrole.GroupID)" + ." LEFT JOIN $osagent ON ($osagent.AgentID = $osgroupmembership.AgentID)" + ." WHERE $osgroupmembership.AgentID = '$agentID'"; + + if( isset($params['GroupID']) ) + { + $groupID = $params['GroupID']; + $sql .= " AND $osgroupmembership.GroupID = '$groupID'"; + } + + $roleResults = mysqli_query($groupDBCon, $sql); + if (!$roleResults) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + if( mysqli_num_rows($roleResults) == 0 ) + { + return array('succeed' => 'false', 'error' => 'None found', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + + $roles = array(); + while($role = mysqli_fetch_assoc($roleResults)) + { + $ID = $role['GroupID'].$role['RoleID']; + $roles[$ID] = $role; + } + + return $roles; + } + + + function getGroupRoles($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params['GroupID']; + + $sql = " SELECT " + ." $osrole.RoleID, $osrole.Name, $osrole.Title, $osrole.Description, $osrole.Powers, count($osgrouprolemembership.AgentID) as Members" + ." FROM $osrole LEFT JOIN $osgrouprolemembership ON ($osrole.GroupID = $osgrouprolemembership.GroupID AND $osrole.RoleID = $osgrouprolemembership.RoleID)" + ." WHERE $osrole.GroupID = '$groupID'" + ." GROUP BY $osrole.RoleID, $osrole.Name, $osrole.Title, $osrole.Description, $osrole.Powers"; + + $roleResults = mysqli_query($groupDBCon, $sql); + if (!$roleResults) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + if( mysqli_num_rows($roleResults) == 0 ) + { + return array('succeed' => 'false', 'error' => 'No roles found for group', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + + $roles = array(); + while($role = mysqli_fetch_assoc($roleResults)) + { + $RoleID = $role['RoleID']; + $roles[$RoleID] = $role; + } + + return $roles; + } + + + function getGroupRoleMembers($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params['GroupID']; + // $roleID = $params['RoleID']; + + $sql = " SELECT " + ." $osrole.RoleID, $osgrouprolemembership.AgentID" + ." FROM $osrole JOIN $osgrouprolemembership ON ($osrole.GroupID = $osgrouprolemembership.GroupID AND $osrole.RoleID = $osgrouprolemembership.RoleID)" + ." WHERE $osrole.GroupID = '$groupID'"; +// ." AND $osrole.RoleID = '$roleID'"; + + $memberResults = mysqli_query($groupDBCon, $sql); + if (!$memberResults) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + $members = array(); + while($member = mysqli_fetch_assoc($memberResults)) + { + $Key = $member['AgentID'] . $member['RoleID']; + $members[$Key ] = $member; + } + + return $members; + } + + + function setAgentGroupInfo($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + if (isset($params['AgentID'])) { + $agentID = $params['AgentID']; + } else { + $agentID = ""; + } + if (isset($params['GroupID'])) { + $groupID = $params['GroupID']; + } else { + $groupID = ""; + } + if (isset($params['SelectedRoleID'])) { + $roleID = $params['SelectedRoleID']; + } else { + $roleID = ""; + } + if (isset($params['AcceptNotice'])) { + $acceptNotices = $params['AcceptNotices']; + } else { + $acceptNotices = ""; + } + if (isset($params['ListInProfile'])) { + $listInProfile = $params['ListInProfile']; + } else { + $listInProfile = ""; + } + + if( isset($requestingAgent) && ($requestingAgent != $uuidZero) && ($requestingAgent != $agentID) ) + { + return array('error' => "Agent can only change their own group info", 'params' => var_export($params, TRUE)); + } + + $sql = " UPDATE " + ." $osgroupmembership" + ." SET " + ." AgentID = '$agentID'"; + + if( isset($params['SelectedRoleID']) ) + { + $sql .=" , SelectedRoleID = '$roleID'"; + } + if( isset($params['AcceptNotices']) ) + { + $sql .=" , AcceptNotices = '$acceptNotices'"; + } + if( isset($params['ListInProfile']) ) + { + $sql .=" , ListInProfile = '$listInProfile'"; + } + + $sql .=" WHERE $osgroupmembership.GroupID = '$groupID' AND $osgroupmembership.AgentID = '$agentID'"; + + $memberResults = mysqli_query($groupDBCon, $sql); + if (!$memberResults) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + return array('success'=> 'true'); + } + + + function getGroupNotices($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params['GroupID']; + + + $sql = " SELECT " + ." GroupID, NoticeID, Timestamp, FromName, Subject, Message, BinaryBucket" + ." FROM $osgroupnotice" + ." WHERE $osgroupnotice.GroupID = '$groupID'"; + + $results = mysqli_query($groupDBCon, $sql); + if (!$results) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + if( mysqli_num_rows($results) == 0 ) + { + return array('succeed' => 'false', 'error' => 'No Notices', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + + $notices = array(); + while($notice = mysqli_fetch_assoc($results)) + { + $NoticeID = $notice['NoticeID']; + $notices[$NoticeID] = $notice; + } + + return $notices; + } + + + function getGroupNotice($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $noticeID = $params['NoticeID']; + + + $sql = " SELECT " + ." GroupID, NoticeID, Timestamp, FromName, Subject, Message, BinaryBucket" + ." FROM $osgroupnotice" + ." WHERE $osgroupnotice.NoticeID = '$noticeID'"; + + $results = mysqli_query($groupDBCon, $sql); + if (!$results) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + if( mysqli_num_rows($results) == 0 ) + { + return array('succeed' => 'false', 'error' => 'Group Notice Not Found', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + + return mysqli_fetch_assoc($results); + } + + + function addGroupNotice($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $groupID = $params['GroupID']; + $noticeID = $params['NoticeID']; + $fromName = addslashes($params['FromName']); + $subject = addslashes($params['Subject']); + $binaryBucket = $params['BinaryBucket']; + $message = addslashes($params['Message']); + $timeStamp = $params['TimeStamp']; + + if( is_array($error = checkGroupPermission($groupID, $groupPowers['SendNotices'])) ) + { + return $error; + } + + $sql = " INSERT INTO $osgroupnotice" + ." (GroupID, NoticeID, Timestamp, FromName, Subject, Message, BinaryBucket)" + ." VALUES " + ." ('$groupID', '$noticeID', $timeStamp, '$fromName', '$subject', '$message', '$binaryBucket')"; + + $results = mysqli_query($groupDBCon, $sql); + if (!$results) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + return array('success' => 'true'); + } + + + function addAgentToGroupInvite($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $inviteID = $params['InviteID']; + $groupID = $params['GroupID']; + $roleID = $params['RoleID']; + $agentID = $params['AgentID']; + //$tmStamp = time(); + $tmStamp = 0; + + if( is_array($error = checkGroupPermission($groupID, $groupPowers['AssignMember'])) ) + { + return $error; + } + + // Remove any existing invites for this agent to this group + $sql = " DELETE FROM $osgroupinvite" + ." WHERE $osgroupinvite.AgentID = '$agentID' AND $osgroupinvite.GroupID = '$groupID'"; + + $results = mysqli_query($groupDBCon, $sql); + if (!$results) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + // Add new invite for this agent to this group for the specifide role + $sql = " INSERT INTO $osgroupinvite" + ." (InviteID, GroupID, RoleID, AgentID, tmstamp) VALUES ('$inviteID', '$groupID', '$roleID', '$agentID', '$tmStamp')"; + + $results = mysqli_query($groupDBCon, $sql); + if (!$results) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + return array('success' => 'true'); + } + + + function getAgentToGroupInvite($params) + { + if( is_array($error = secureRequest($params, FALSE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $inviteID = $params['InviteID']; + + + $sql = " SELECT GroupID, RoleID, AgentID FROM $osgroupinvite" + ." WHERE $osgroupinvite.InviteID = '$inviteID'"; + + $results = mysqli_query($groupDBCon, $sql); + if (!$results) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + if( mysqli_num_rows($results) == 1 ) + { + $inviteInfo = mysqli_fetch_assoc($results); + $groupID = $inviteInfo['GroupID']; + $roleID = $inviteInfo['RoleID']; + $agentID = $inviteInfo['AgentID']; + + return array('success' => 'true', 'GroupID'=>$groupID, 'RoleID'=>$roleID, 'AgentID'=>$agentID); + } else { + return array('succeed' => 'false', 'error' => 'Invitation not found', 'params' => var_export($params, TRUE), 'sql' => $sql); + } + } + + + function removeAgentToGroupInvite($params) + { + if( is_array($error = secureRequest($params, TRUE)) ) + { + return $error; + } + + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + global $osagent, $osgroup, $osgroupinvite, $osgroupmembership, $osgroupnotice, $osgrouprolemembership, $osrole; + + $inviteID = $params['InviteID']; + + $sql = " DELETE FROM $osgroupinvite" + ." WHERE $osgroupinvite.InviteID = '$inviteID'"; + + $results = mysqli_query($groupDBCon, $sql); + if (!$results) + { + return array('error' => "Could not successfully run query ($sql) from DB: " . mysqli_error($groupDBCon), 'params' => var_export($params, TRUE)); + } + + return array('success' => 'true'); + } + + + function secureRequest($params, $write = FALSE) + { + global $GroupWriteKey, $GroupReadKey, $VerifiedReadKey, $VerifiedWriteKey, $GroupRequireAgentAuthForWrite, $requestingAgent; + + if( isset($GroupReadKey) && ($GroupReadKey != '') && (!isset($VerifiedReadKey) || ($VerifiedReadKey !== TRUE)) ) + { + if( !isset($params['ReadKey']) || ($params['ReadKey'] != $GroupReadKey ) ) + { + return array('error' => "Invalid (or No) Read Key Specified", 'params' => var_export($params, TRUE)); + } else { + $VerifiedReadKey = TRUE; + } + } + + if( ($write == TRUE) && isset($GroupWriteKey) && ($GroupWriteKey != '') && (!isset($VerifiedWriteKey) || ($VerifiedWriteKey !== TRUE)) ) + { + if( !isset($params['WriteKey']) || ($params['WriteKey'] != $GroupWriteKey ) ) + { + return array('error' => "Invalid (or No) Write Key Specified", 'params' => var_export($params, TRUE)); + } else { + $VerifiedWriteKey = TRUE; + } + } + + if( ($write == TRUE) && isset($GroupRequireAgentAuthForWrite) && ($GroupRequireAgentAuthForWrite == TRUE) ) + { + // Note: my brain can't do boolean logic this morning, so just putting this here instead of integrating with line above. + // If the write key has already been verified for this request, don't check it again. + // This comes into play with methods that call other methods, such as CreateGroup() which calls Addrole() + if( isset($VerifiedWriteKey) && ($VerifiedWriteKey !== TRUE)) + { + return TRUE; + } + + if( !isset($params['RequestingAgentID']) + || !isset($params['RequestingAgentUserService']) + || !isset($params['RequestingSessionID']) + ) + { + return array('error' => "Requesting AgentID and SessionID must be specified", 'params' => var_export($params, TRUE)); + } + + $requestingAgent = $params['RequestingAgentID']; + + // NOTE: an AgentID and SessionID of $uuidZero will likely be a region making a request, that is not tied to a specific agent making the request. + + $client = new xmlrpc_client($params['RequestingAgentUserService']); + $client->return_type = 'phpvals'; + + $verifyParams = new xmlrpcval(array('avatar_uuid' => new xmlrpcval($params['RequestingAgentID'], 'string') + ,'session_id' => new xmlrpcval($params['RequestingSessionID'], 'string')), 'struct'); + + $message = new xmlrpcmsg("check_auth_session", array($verifyParams)); + $resp = $client->send($message, 5); + if ($resp->faultCode()) + { + return array('error' => "Error validating AgentID and SessionID" + , 'xmlrpcerror'=> $resp->faultString() + , 'params' => var_export($params, TRUE)); + } + + $verifyReturn = $resp->value(); + + if( !isset($verifyReturn['auth_session']) || ($verifyReturn['auth_session'] != 'TRUE') ) + { + return array('error' => "UserService.check_auth_session() did not return TRUE" + , 'userservice' => var_export($verifyReturn, TRUE) + , 'params' => var_export($params, TRUE)); + + } + } + + return TRUE; + } + + + function checkGroupPermission($GroupID, $Permission) + { + global $GroupEnforceGroupPerms, $requestingAgent, $uuidZero, $groupDBCon, $groupPowers; + + // If it isn't set to true, then always return true, otherwise verify they have perms + if( !isset($GroupEnforceGroupPerms) || ($GroupEnforceGroupPerms != TRUE) ) + { + return true; + } + + if( !isset($requestingAgent) || ($requestingAgent == $uuidZero) ) + { + return array('error' => 'Requesting agent was either not specified or not validated.' + , 'params' => var_export($params, TRUE)); + } + + $params = array('AgentID' => $requestingAgent, 'GroupID' => $GroupID); + $reqAgentMembership = getAgentGroupMembership($params); + + if( isset($reqAgentMembership['error'] ) ) + { + return array('error' => 'Could not get agent membership for group' + , 'params' => var_export($params, TRUE) + , 'nestederror' => $reqAgentMembership['error']); + } + + if( $reqAgentMembership['GroupPowers'] & $Permission != $Permission ) + { + return array('error' => 'Agent does not have group power to $Permission' + , 'params' => var_export($params, TRUE)); + } + } + + $s = new xmlrpc_server(array( + "test" => array("function" => "test") + , "groups.createGroup" => array("function" => "createGroup", "signature" => $common_sig) + , "groups.updateGroup" => array("function" => "updateGroup", "signature" => $common_sig) + , "groups.getGroup" => array("function" => "getGroup", "signature" => $common_sig) + , "groups.findGroups" => array("function" => "findGroups", "signature" => $common_sig) + + , "groups.getGroupRoles" => array("function" => "getGroupRoles", "signature" => $common_sig) + , "groups.addRoleToGroup" => array("function" => "addRoleToGroup", "signature" => $common_sig) + , "groups.removeRoleFromGroup" => array("function" => "removeRoleFromGroup", "signature" => $common_sig) + , "groups.updateGroupRole" => array("function" => "updateGroupRole", "signature" => $common_sig) + , "groups.getGroupRoleMembers" => array("function" => "getGroupRoleMembers", "signature" => $common_sig) + + , "groups.setAgentGroupSelectedRole" => array("function" => "setAgentGroupSelectedRole", "signature" => $common_sig) + , "groups.addAgentToGroupRole" => array("function" => "addAgentToGroupRole", "signature" => $common_sig) + , "groups.removeAgentFromGroupRole" => array("function" => "removeAgentFromGroupRole", "signature" => $common_sig) + + , "groups.getGroupMembers" => array("function" => "getGroupMembers", "signature" => $common_sig) + , "groups.addAgentToGroup" => array("function" => "addAgentToGroup", "signature" => $common_sig) + , "groups.removeAgentFromGroup" => array("function" => "removeAgentFromGroup", "signature" => $common_sig) + , "groups.setAgentGroupInfo" => array("function" => "setAgentGroupInfo", "signature" => $common_sig) + + , "groups.addAgentToGroupInvite" => array("function" => "addAgentToGroupInvite", "signature" => $common_sig) + , "groups.getAgentToGroupInvite" => array("function" => "getAgentToGroupInvite", "signature" => $common_sig) + , "groups.removeAgentToGroupInvite" => array("function" => "removeAgentToGroupInvite", "signature" => $common_sig) + + , "groups.setAgentActiveGroup" => array("function" => "setAgentActiveGroup", "signature" => $common_sig) + , "groups.getAgentGroupMembership" => array("function" => "getAgentGroupMembership", "signature" => $common_sig) + , "groups.getAgentGroupMemberships" => array("function" => "getAgentGroupMemberships", "signature" => $common_sig) + , "groups.getAgentActiveMembership" => array("function" => "getAgentActiveMembership", "signature" => $common_sig) + , "groups.getAgentRoles" => array("function" => "getAgentRoles", "signature" => $common_sig) + + , "groups.getGroupNotices" => array("function" => "getGroupNotices", "signature" => $common_sig) + , "groups.getGroupNotice" => array("function" => "getGroupNotice", "signature" => $common_sig) + , "groups.addGroupNotice" => array("function" => "addGroupNotice", "signature" => $common_sig) + + ), false); + + $s->functions_parameters_type = 'phpvals'; + if (isset($debugXMLRPC) && $debugXMLRPC > 0 && isset($debugXMLRPCFile) && $debugXMLRPCFile != "") + { + $s->setDebug($debugXMLRPC); + } + $s->service(); + + if (isset($debugXMLRPC) && $debugXMLRPC > 0 && isset($debugXMLRPCFile) && $debugXMLRPCFile != "") + { + $f = fopen($debugXMLRPCFile,"a"); + fwrite($f,"\n----- " . date("Y-m-d H:i:s") . " -----\n"); + $debugInfo = $s->serializeDebug(); + //$debugInfo = split("\n",$debugInfo); + $debugInfo = explode("\n",$debugInfo); + unset($debugInfo[0]); + unset($debugInfo[count($debugInfo) -1]); + $debugInfo = join("\n",$debugInfo); + fwrite($f,base64_decode($debugInfo)); + fclose($f); + } + + mysqli_close($groupDBCon); + diff --git a/helper-php/DTLNSL_helper_scripts/include/config.php b/helper-php/DTLNSL_helper_scripts/include/config.php new file mode 100644 index 0000000..2eb3052 --- /dev/null +++ b/helper-php/DTLNSL_helper_scripts/include/config.php @@ -0,0 +1,78 @@ +history.go(-1); diff --git a/helper-php/DTLNSL_helper_scripts/include/jbxl_tools.php b/helper-php/DTLNSL_helper_scripts/include/jbxl_tools.php new file mode 100644 index 0000000..7714852 --- /dev/null +++ b/helper-php/DTLNSL_helper_scripts/include/jbxl_tools.php @@ -0,0 +1,309 @@ + SubnetMask + $cider = $sub[0]; + $nbyte = (int)($cider/8); + $nbit = $cider - $nbyte*8; + for ($i=0; $i<$nbyte; $i++) { + $sub[$i] = 255; + } + if ($nbyte!=4) { + $nsub = 0; + $base = 128; + for ($i=0; $i<$nbit; $i++) { + $nsub += $base; + $base = $base/2; + } + $sub[$nbyte] = $nsub; + } + } + + for ($i=0; $i<4; $i++) { + if (!empty($ips[$i])) $return[$index]['ipaddr'][$i] = (int)$ips[$i]; + else $return[$index]['ipaddr'][$i] = (int)0; + if (!empty($sub[$i])) $return[$index]['subnet'][$i] = (int)$sub[$i]; + else $return[$index]['subnet'][$i] = (int)0; + } + $index++; + } + + return $return; +} + + +// +// $ip が $ipaddr_subnetsの中に含まれるか検査する. +// $ipaddr_subnets は jbxl_to_subnetformats()が出力したものを使用すること. +// $ip の内容の形式はチェックしない.これは呼び出し側の責任. +// +function jbxl_match_ipaddr($ip, array $ipaddr_subnets) +{ + $ipa = explode('.', $ip); + if (empty($ipa)) return false; + + for ($i=1; $i<4; $i++) { + if (empty($ipa[$i])) $ipa[$i] = 0; + } + + foreach($ipaddr_subnets as $ipaddr_subnet) { + $ips = $ipaddr_subnet['ipaddr']; + $sub = $ipaddr_subnet['subnet']; + + $match_f = true; + for ($i=0; $i<4; $i++) { + $check1 = $ipa[$i] & $sub[$i]; + $check2 = $ips[$i] & $sub[$i]; + if ($check1 != $check2) { + $match_f = false; + break; + } + } + + if ($match_f) { + //print_r($ips); + //print_r($sub); + return true; + } + } + return false; +} + + + +$JBXLBaseChar = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + + +function jbxl_randstr($len=8, $lowcase=false) +{ + global $JBXLBaseChar; + + if ($lowcase) $rndmax = 25; + else $rndmax = strlen($JBXLBaseChar) - 1; + + $return = ""; + for($i=0; $i<$len; $i++) { + $return .= $JBXLBaseChar{mt_rand(0, $rndmax)}; + } + return $return; +} + + +function jbxl_get_ipresolv_url($ip, $region='APNIC') +{ + if (!preg_match('/(^\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $ip, $match)) return ''; + + if ($match[1]>255 or $match[2]>255 or $match[3]>255 or $match[4]>255) return ''; + if ($match[1]=='127' or $match[1]=='10') return ''; + if ($match[1]=='172' and $match[2]>='16' and $match[2]<='31') return ''; + if ($match[1]=='192' and $match[2]=='168') return ''; + + if ($region=='JPNIC') { + $url = 'http://whois.nic.ad.jp/cgi-bin/whois_gw?type=NET&key='.$ip; // JPNIC + } + else { + $url = 'http://wq.apnic.net/apnic-bin/whois.pl?searchtext='.$ip; // APNIC + } + + return $url; +} + + +function jbxl_get_url_params_array($urlstr) +{ + $strs = explode('?', $urlstr); + $parmstr = $strs[0]; + if (array_key_exists(1, $strs)) $paramstr = $strs[1]; + + $ret = array(); + $params = explode('&', $paramstr); + foreach($params as $param) { + if (substr($param, 0, 4)=='amp;') $param = substr($param, 5); + $temps = explode('=', $param); + $ret[$temps[0]] = ''; + if (array_key_exists(1, $temps)) $ret[$temps[0]] = $temps[1]; + } + + return $ret; +} + + +// +// $params: パラメータの入っている配列 +// $amp: 先頭文字を '&' にするか? false の場合は 先頭文字は '?' +// +function jbxl_get_url_params_str($params, $amp=false) +{ + $ret = ''; + if (!is_array($params)) return $ret; + + $no = 0; + foreach($params as $key => $param) { + if ($no==0 and !$amp) { + $ret .= '?'.$key.'='.$param; + } + else { + $ret .= '&'.$key.'='.$param; + } + $no++; + } + return $ret; +} + + +// +// 入力された FSDN, URL に対して http(s)://ABC.EFG:#/ の形を生成する +// +function jbxl_make_url($serverURI, $portnum=0) +{ + $url = ''; + $host = 'localhost'; + $port = 80; + $protocol = 'http'; + + if ($serverURI!=null) { + $uri = preg_split("/[:\/]/", $serverURI); + + // with http:// or https:// + if (array_key_exists(3, $uri)) { + $protocol = $uri[0]; + $host = $uri[3]; + // + if (array_key_exists(4, $uri)) { + $port = $uri[4]; + } + else { + if ($portnum!=0) { + $port = $portnum; + } + else { + if ($uri[0]=='http') $port = 80; + else if ($uri[0]=='https') $port = 443; + else if ($uri[0]=='ftp') $port = 21; + // else if .... + } + } + } + + // with no http:// and https:// + else { + $host = $uri[0]; + if (array_key_exists(1, $uri)) { + $port = $uri[1]; + } + else { + if ($portnum!=0) { + $port = $portnum; + } + else { + $port = 80; + } + } + } + + // + if ($port==443) { + $url = 'https://'.$host.':'.$port.'/'; + $protocol = 'https'; + } + else if ($port==80) { + $url = 'http://'.$host.'/'; + $protocol = 'http'; + } + else if ($port==21) { + $url = 'ftp://'.$host.'/'; + $protocol = 'ftp'; + } + else { + $url = $protocol.'://'.$host.':'.$port.'/'; + } + } + + $server['url'] = $url; + $server['host'] = $host; + $server['port'] = $port; + $server['porotocol'] = $protocol; + + return $server; +} + + +} // !defined('JBXL_TOOLS_VER') diff --git a/helper-php/DTLNSL_helper_scripts/include/mysql.func.php b/helper-php/DTLNSL_helper_scripts/include/mysql.func.php new file mode 100644 index 0000000..40f61ba --- /dev/null +++ b/helper-php/DTLNSL_helper_scripts/include/mysql.func.php @@ -0,0 +1,382 @@ +Host = $dbhost; + $this->Database = $dbname; + $this->User = $dbuser; + $this->Password = $dbpass; + + $this->UseMySQLi= $usemysqli; + $this->Timeout = $timeout; + ini_set('mysql.connect_timeout', $timeout); + } + + + function set_DB($dbhost, $dbname, $dbuser, $dbpass, $usemysqli=false) + { + $this->Host = $dbhost; + $this->Database = $dbname; + $this->User = $dbuser; + $this->Password = $dbpass; + $this->UseMySQLi= $usemysqli; + } + + + function halt($msg) + { + echo "DB ERROR : $msg
\n"; + echo "MySQL ERROR: $this->Error ($this->Errno)
\n"; + die('Session Halted.'); + } + + + function connect() + { + if ($this->Link_ID==null) { + // + if (!$this->UseMySQLi) { + $this->Link_ID = mysql_connect($this->Host, $this->User, $this->Password); + if (!$this->Link_ID) { + $this->Errno = 999; + //error_log('cannot select database. 1/2'); + return false; + } + mysql_set_charset('utf8'); + $SelectResult = mysql_select_db($this->Database, $this->Link_ID); + if (!$SelectResult) { + $this->Errno = mysql_errno($this->Link_ID); + $this->Error = mysql_error($this->Link_ID); + $this->Link_ID = null; + //$this->halt('cannot select database '.$this->Database.''); + //error_log('cannot select database. 2/2'); + return false; + } + } + // + else { + $this->Link_ID = mysqli_connect($this->Host, $this->User, $this->Password, $this->Database); + if (!$this->Link_ID) { + $this->Errno = 999; + $this->Error = mysqli_connect_error(); + //$this->halt('cannot select database '.$this->Database.''); + //error_log('cannot select database.'); + return false; + } + mysqli_set_charset($this->Link_ID, 'utf8'); + } + } + return true; + } + + + function escape($String) + { + $this->connect(); + + if (!$this->UseMySQLi) return mysql_real_escape_string($String); + return mysqli_real_escape_string($this->Link_ID, $String); + } + + + function query($Query_String) + { + $this->connect(); + if ($this->Errno!=0) return 0; + + if (!$this->UseMySQLi) { + $this->Query_ID = mysql_query($Query_String, $this->Link_ID); + $this->Errno = mysql_errno($this->Link_ID); + $this->Error = mysql_error($this->Link_ID); + } + else { + $this->Query_ID = mysqli_query($this->Link_ID, $Query_String); + $this->Errno = mysqli_errno($this->Link_ID); + $this->Error = mysqli_error($this->Link_ID); + } + $this->Row = 0; + // + if (!$this->Query_ID) { + $this->halt('Invalid SQL: '.$Query_String); + } + return $this->Query_ID; + } + + + function next_record() + { + if (!$this->UseMySQLi) { + $this->Record = @mysql_fetch_array($this->Query_ID); + $this->Row += 1; + $this->Errno = mysql_errno($this->Link_ID); + $this->Error = mysql_error($this->Link_ID); + $stat = is_array($this->Record); + if (!$stat) { + @mysql_free_result($this->Query_ID); + $this->Query_ID = null; + } + } + else { + $this->Record = @mysqli_fetch_array($this->Query_ID); + $this->Row += 1; + $this->Errno = mysqli_errno($this->Link_ID); + $this->Error = mysqli_error($this->Link_ID); + $stat = is_array($this->Record); + if (!$stat) { + @mysqli_free_result($this->Query_ID); + $this->Query_ID = null; + } + } + + return $this->Record; + } + + + function insert_record($table, $params) + { + if (!is_array($params)) return false; + + $num = 0; + $keys = ''; + $vals = ''; + foreach ($params as $key => $value) { + if ($num==0) { + $keys = $key; + $vals = "'".$value."'"; + } + else { + $keys .= ','.$key; + $vals .= ",'".$value."'"; + } + $num++; + } + + $this->query('INSERT INTO '.$table.' ('.$keys.') VALUES ('.$vals.')'); + + if ($this->Errno==0) return true; + return false; + } + + + // params配列の一番最初の要素が キー + function update_record($table, $params) + { + if (!is_array($params)) return false; + + $num = 0; + $where = ''; + $setval = ''; + foreach ($params as $key => $value) { + if ($num==0) { + $where = $key."='".$value."'"; + } + else if ($num==1) { + $setval = $key."='".$value."'"; + } + else { + $setval .= ','.$key."='".$value."'"; + } + $num++; + } + + $this->query('UPDATE '.$table.' SET '.$setval.' WHERE '.$where); + + if ($this->Errno==0) return true; + return false; + } + + + function num_rows() + { + if (!$this->UseMySQLi) return mysql_num_rows($this->Query_ID); + return mysqli_num_rows($this->Query_ID); + } + + + function affected_rows() + { + if (!$this->UseMySQLi) return mysql_affected_rows($this->Link_ID); + return mysqli_affected_rows($this->Link_ID); + } + + + function optimize($tbl_name) + { + $this->connect(); + if ($this->Errno!=0) return; + + if (!$this->UseMySQLi) { + $this->Query_ID = @mysql_query('OPTIMIZE TABLE '.$tbl_name, $this->Link_ID); + } + else { + $this->Query_ID = @mysqli_query($this->Link_ID, 'OPTIMIZE TABLE '.$tbl_name); + } + } + + + function clean_results() + { + if ($this->Query_ID!=null) { + if (!$this->UseMySQLi) { + mysql_freeresult($this->Query_ID); + } + else { + mysqli_freeresult($this->Query_ID); + } + $this->Query_ID = null; + } + } + + + function close() + { + /* + if ($this->Link_ID) { + if (!$this->UseMySQLi) mysql_close($this->Link_ID); + mysqli_close($this->Link_ID); + $this->Link_ID = null; + } + */ + } + + + function exist_table($table, $lower_case=true) + { + $ret = false; + + if ($lower_case) $table = strtolower($table); + + $this->query('SHOW TABLES'); + if ($this->Errno==0) { + while (list($db_tbl) = $this->next_record()) { + if ($lower_case) $db_tbl = strtolower($db_tbl); + if ($db_tbl==$table) { + $ret = true; + break; + } + } + } + + return $ret; + } + + + function exist_field($table, $field, $lower_case=true) + { + $ret1 = false; + $ret2 = false; + + if ($lower_case) $cmp_table = strtolower($table); + else $cmp_table = $table; + + $this->query('SHOW TABLES'); + if ($this->Errno==0) { + while (list($db_tbl) = $this->next_record()) { + if ($lower_case) $db_tbl = strtolower($db_tbl); + if ($db_tbl==$cmp_table) { + $ret1 = true; + break; + } + } + } + + if ($ret1) { + $this->query('SHOW COLUMNS FROM '.$table); + if ($this->Errno==0) { + while (list($db_fld) = $this->next_record()) { + if ($db_fld==$field) { + $ret2 = true; + break; + } + } + } + } + + return $ret2; + } + + + // + // InnoDB では Update_time は NULL になる! + // + function get_update_time($table, $unixtime=true) + { + $update = ''; + if ($unixtime) $update = 0; + + $this->query("SHOW TABLE STATUS WHERE name='$table'"); + + if ($this->Errno==0) { + $table_status = $this->next_record(); + $update = $table_status['Update_time']; + if ($unixtime) { + if ($update!='') $update = strtotime($update); + else $update = 0; + } + } + + return $update; + } + + + // + // Lock + // + function lock_table($table, $mode='write') + { + $this->query("LOCK TABLES ".$table." ".$mode); + } + + + function unlock_table() + { + $this->query("UNLOCK TABLES"); + } + + + // + // Timeout + // + function set_default_timeout($tm) + { + ini_set('mysql.connect_timeout', $tm); + $this->Timeout = $tm; + } + + + function set_temp_timeout($tm) + { + ini_set('mysql.connect_timeout', $tm); + } + + + function reset_timeout() + { + ini_set('mysql.connect_timeout', $this->Timeout); + } +} diff --git a/helper-php/DTLNSL_helper_scripts/include/opensim.mysql.php b/helper-php/DTLNSL_helper_scripts/include/opensim.mysql.php new file mode 100644 index 0000000..6eafaba --- /dev/null +++ b/helper-php/DTLNSL_helper_scripts/include/opensim.mysql.php @@ -0,0 +1,2764 @@ +get_update_time('UserAccounts'); + return $utime; +} + + +// +// Check and get Basic Data of DB +// +function opensim_check_db(&$db=null) +{ + $ret['grid_status'] = false; + $ret['now_online'] = 0; + $ret['hg_online'] = 0; + $ret['lastmonth_online'] = 0; + $ret['user_count'] = 0; + $ret['region_count'] = 0; + + if (!is_object($db)) $db = opensim_new_db(3); + if ($db==null) return $ret; + + if ($db->exist_table('regions')) { + $db->query('SELECT COUNT(*) FROM regions'); + if ($db->Errno==0) { + list($ret['region_count']) = $db->next_record(); + } + } + + $db->query('SELECT COUNT(*) FROM UserAccounts'); // Local User + list($ret['user_count']) = $db->next_record(); + // + if ($db->exist_table('Presence')) { + $db->query("SELECT COUNT(DISTINCT Presence.UserID) FROM GridUser,Presence ". + //"WHERE GridUser.UserID=Presence.UserID AND Online='True' AND RegionID!='".UUID_ZERO."'"); + "WHERE GridUser.UserID=Presence.UserID AND RegionID!='".UUID_ZERO."'"); + list($loc_user) = $db->next_record(); + + $db->query("SELECT COUNT(*) FROM Presence WHERE RegionID!='".UUID_ZERO."'"); + list($all_user) = $db->next_record(); + + $ret['now_online'] = $all_user; + $ret['hg_online'] = $all_user - $loc_user; + } + // Standalone + else { + $db->query("SELECT COUNT(*) FROM GridUser WHERE Online='True' AND UserID NOT LIKE '%;%'"); + list($loc_user) = $db->next_record(); + + $db->query("SELECT COUNT(*) FROM GridUser WHERE Online='True'"); + list($all_user) = $db->next_record(); + + $ret['now_online'] = $all_user; + $ret['hg_online'] = $all_user - $loc_user; + } + // + $db->query('SELECT COUNT(*) FROM GridUser WHERE Login>unix_timestamp(now())-2592000'); // Local and HG User + list($ret['lastmonth_online']) = $db->next_record(); + // + $ret['grid_status'] = true; + + return $ret; +} + + +function opensim_is_standalone(&$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + + if ($db->exist_table('Presence')) return false; + return true; +} + + + +///////////////////////////////////////////////////////////////////////////////////// +// +// for Avatar +// + +// +// ローカルなアバター数 +// +function opensim_get_avatars_num($condition='', &$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + if ($condition!='') $condition = 'WHERE '.$condition; + + $num = 0; + $db->query('SELECT COUNT(*) FROM UserAccounts '.$condition); + list($num) = $db->next_record(); + + return $num; +} + + +// +// HGユーザも含めた全アバター数 +// +function opensim_allavatars_count_records(&$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + + $count = 0; + $db->query('SELECT COUNT(*) FROM GridUser'); + list($count) = $db->next_record(); + + return $count; +} + + +function opensim_get_avatar_name($uuid, $hguser=true, &$db=null) +{ + $name = array(); + + if (!isGUID($uuid)) return $name; + + if ($uuid==UUID_ZERO) { + $name['firstname'] = 'System'; + $name['lastname'] = ''; + $name['fullname'] = 'System'; + return $name; + } + + if (!is_object($db)) $db = opensim_new_db(); + + $firstname = null; + $lastname = null; + $fullname = null; + + $db->query("SELECT FirstName,LastName FROM UserAccounts WHERE PrincipalID='$uuid'"); + list($firstname, $lastname) = $db->next_record(); + $fullname = $firstname.' '.$lastname; + if ($fullname==' ') { + $fullname = null; + $firstname = null; + $lastname = null; + } + + // HG + if ($hguser and $fullname==null) { + $db->query("SELECT UserID FROM GridUser WHERE UserID LIKE '".$uuid.";%' ORDER BY Login DESC"); + list($hg_uuid) = $db->next_record(); + $uuids = explode(';', $hg_uuid); + // + if (array_key_exists(2, $uuids)) { + $fullname = $uuids[2]; + $firstname = $uuids[2]; + $lastname = null; + $hg_name = explode(' ', $fullname); + $firstname = $hg_name[0]; + if (array_key_exists(1, $hg_name)) $lastname = $hg_name[1]; + } + } + + $name['firstname'] = $firstname; + $name['lastname'] = $lastname; + $name['fullname'] = $fullname; + + return $name; +} + + +function opensim_get_avatar_uuid($name, $hguser=true, &$db=null) +{ + if (!isAlphabetNumericSpecial($name)) return null; + if (!is_object($db)) $db = opensim_new_db(); + if (mb_strtolower($name)=='system') return UUID_ZERO; + + $avatar_name = preg_split("/ /", $name, 0, PREG_SPLIT_NO_EMPTY); + $firstname = $avatar_name[0]; + $lastname = 'Resident'; + if (array_key_exists(1, $avatar_name)) $lastname = $avatar_name[1]; + if ($firstname=='') return null; + + $uuid = null; + $db->query("SELECT PrincipalID FROM UserAccounts WHERE FirstName='$firstname' AND LastName='$lastname'"); + list($uuid) = $db->next_record(); + + // HG + if ($hguser and $uuid==null) { + $db->query("SELECT UserID FROM GridUser WHERE UserID LIKE '%;%;".$firstname.' '.$lastname."' ORDER BY Login DESC"); + list($hg_uuid) = $db->next_record(); + $uuids = explode(';', $hg_uuid); + $uuid = $uuids[0]; + } + + return $uuid; +} + + +function opensim_get_avatar_session($uuid, &$db=null) +{ + if (!isGUID($uuid)) return null; + if (!is_object($db)) $db = opensim_new_db(); + + $avssn = array(); + // + if ($db->exist_table('Presence')) { + $sql = "SELECT RegionID,SessionID,SecureSessionID FROM Presence WHERE UserID='".$uuid."'"; + $db->query($sql); + list($RegionID, $SessionID, $SecureSessionID) = $db->next_record(); + // + $avssn['regionID'] = $RegionID; + $avssn['sessionID'] = $SessionID; + $avssn['secureID'] = $SecureSessionID; + } + // Standalone + else { + $sql = "SELECT LastRegionID FROM GridUser WHERE UserID='".$uuid."'"; + $db->query($sql); + list($RegionID) = $db->next_record(); + // + $avssn['regionID'] = $RegionID; + $avssn['sessionID'] = UUID_ZERO; + $avssn['secureID'] = UUID_ZERO; + } + + return $avssn; +} + + +/* + return: + $avinfo['UUID'] + $avinfo['firstname'] + $avinfo['lastname'] + $avinfo['fullname'] + $avinfo['created'] + $avinfo['lastlogin'] + $avinfo['regionUUID'] + $avinfo['regionName'] + $avinfo['serverIP'] + $avinfo['serverHttpPort'] + $avinfo['serverPort'] + $avinfo['serverURI'] + $avinfo['serverName'] + // + $avinfo['hgURI'] + $avinfo['hgName'] + // + $avinfo['profileText'] + $avinfo['profileImage'] + $avinfo['firstText'] + $avinfo['firstImage'] + $avinfo['partner'] +*/ +function opensim_get_avatar_info($uuid, $hguser=true, &$db=null) +{ + if (!isGUID($uuid)) return null; + if (!is_object($db)) $db = opensim_new_db(); + + $created = 0; + $regionName = null; + $serverIP = null; + $serverHttpPort = null; + $serverURI = null; + $hgURI = null; + + $profileText = null; + $profileImage = null; + $firstText = null; + $firstImage = null; + $partner = null; + + $db->query('SELECT PrincipalID,FirstName,LastName,HomeRegionID,Created,Login FROM UserAccounts'. + " LEFT JOIN GridUser ON PrincipalID=UserID WHERE PrincipalID='$uuid'"); + list($UUID, $firstname, $lastname, $regionUUID, $created, $lastlogin) = $db->next_record(); + $fullname = $firstname.' '.$lastname; + if ($fullname==' ') { + $fullname = null; + $firstname = null; + $lastname = null; + } + + if ($fullname!=null) { + $db->query("SELECT regionName,serverIP,serverHttpPort,serverURI FROM regions WHERE uuid='$regionUUID'"); + list($regionName, $serverIP, $serverHttpPort, $serverURI) = $db->next_record(); + } + // HG + else if ($hguser) { + $db->query("SELECT UserID,HomeRegionID,Login FROM GridUser WHERE UserID LIKE '".$uuid.";%' ORDER BY Login DESC"); + list($hg_uuid, $regionUUID, $lastlogin) = $db->next_record(); + $uuids = explode(';', $hg_uuid); + $UUID = $uuids[0]; + if (array_key_exists(1, $uuids)) $hgURI = $uuids[1]; + if (array_key_exists(2, $uuids)) { + $fullname = $uuids[2]; + $hg_name = explode(' ', $fullname); + $firstname = $hg_name[0]; + if (array_key_exists(1, $hg_name)) $lastname = $hg_name[1]; + } + } + + $avinfo['UUID'] = $UUID; + $avinfo['firstname'] = $firstname; + $avinfo['lastname'] = $lastname; + $avinfo['fullname'] = $fullname; + $avinfo['created'] = $created; + $avinfo['lastlogin'] = $lastlogin; + $avinfo['regionUUID'] = $regionUUID; + $avinfo['regionName'] = $regionName; + $avinfo['serverIP'] = $serverIP; + $avinfo['serverHttpPort'] = $serverHttpPort; + $avinfo['serverPort'] = $serverHttpPort; + $avinfo['serverURI'] = $serverURI; + $avinfo['serverName'] = ''; + $avinfo['hgURI'] = $hgURI; + $avinfo['hgName'] = ''; + // + $avinfo['profileText'] = $profileText; + $avinfo['profileImage'] = $profileImage; + $avinfo['firstText'] = $firstText; + $avinfo['firstImage'] = $firstImage; + $avinfo['partner'] = $partner; + + $uri = preg_split("/[:\/]/", $serverURI); + if (array_key_exists(3, $uri)) { + $avinfo['serverName'] = $uri[3]; +// $avinfo['serverIP2'] = gethostbyname($uri[3]); + } + // + $uri = preg_split("/[:\/]/", $hgURI); + if (array_key_exists(3, $uri)) { + $avinfo['hgName'] = $uri[3]; + } + + return $avinfo; +} + + +/* + HG avatars are not supported. + + return: + $avinfos[$UUID]['UUID'] ... UUID + $avinfos[$UUID]['firstname'] ... first name + $avinfos[$UUID]['lastname'] ... lasti name + $avinfos[$UUID]['created'] ... created time + $avinfos[$UUID]['lastlogin'] ... lastlogin time + $avinfos[$UUID]['hmregion'] ... uuid of home region +*/ +function opensim_get_avatars_infos($condition='', $order='', $limit='', &$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + if ($condition!='') $condition = 'WHERE '.$condition; + if ($order!='') $order = ' ORDER BY '.$order; + if ($limit!='') $limit = ' LIMIT '. $limit; + + $avinfos = array(); + $query_str = 'SELECT PrincipalID,FirstName,LastName,Created,Login,homeRegionID FROM UserAccounts '. + 'LEFT JOIN GridUser ON PrincipalID=UserID '.$condition.$order.$limit; + $db->query($query_str); + + if ($db->Errno==0) { + while (list($UUID,$firstname,$lastname,$created,$lastlogin,$hmregion) = $db->next_record()) { + $avinfos[$UUID]['UUID'] = $UUID; + $avinfos[$UUID]['firstname'] = $firstname; + $avinfos[$UUID]['lastname'] = $lastname; + $avinfos[$UUID]['created'] = $created; + $avinfos[$UUID]['lastlogin'] = $lastlogin; + $avinfos[$UUID]['hmregion_id'] = $hmregion; + } + } + + return $avinfos; +} + + +/* + return: + $avinfos[$UUID]['UUID'] ... UUID + $avinfos[$UUID]['firstname'] ... first name + $avinfos[$UUID]['lastname'] ... lasti name + $avinfos[$UUID]['created'] ... always 0 + $avinfos[$UUID]['lastlogin'] ... lastlogin time + $avinfos[$UUID]['hgURI'] ... Hyper Grid URI + $avinfos[$UUID]['hgName'] ... Hyper Grid name +*/ +function opensim_get_hg_avatars_infos($condition='', $order='', $limit='', &$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + if ($condition!='') $condition = '('.$condition.') AND'; + if ($order!='') $order = ' ORDER BY '.$order; + if ($limit!='') $limit = ' LIMIT '. $limit; + + $avinfos = array(); + $query_str = 'SELECT UserID,Login FROM GridUser WHERE '.$condition." UserID LIKE '%;%;%' ".$order.$limit; + $db->query($query_str); + + if ($db->Errno==0) { + while (list($hg_uuid, $lastlogin) = $db->next_record()) { + $hgURI = ''; + $fullname = ''; + $firstname = ''; + $lastname = ''; + + $uuids = explode(';', $hg_uuid); + $UUID = $uuids[0]; + if (array_key_exists(1, $uuids)) $hgURI = $uuids[1]; + if (array_key_exists(2, $uuids)) { + $fullname = $uuids[2]; + $hg_name = explode(' ', $fullname); + $firstname = $hg_name[0]; + if (array_key_exists(1, $hg_name)) $lastname = $hg_name[1]; + } + // + if (!array_key_exists($UUID, $avinfos)) { + $avinfos[$UUID]['UUID'] = $UUID; + $avinfos[$UUID]['firstname'] = $firstname; + $avinfos[$UUID]['lastname'] = $lastname; + $avinfos[$UUID]['created'] = 0; + $avinfos[$UUID]['lastlogin'] = $lastlogin; + $avinfos[$UUID]['hgURI'] = $hgURI; + $avinfos[$UUID]['hgName'] = ''; + // + $uri = preg_split("/[:\/]/", $hgURI); + if (array_key_exists(3, $uri)) { + $avinfos[$UUID]['hgName'] = $uri[3]; + } + } + } + } + + return $avinfos; +} + + +/* + return: + $ret['online'] + $ret['regionUUID'] + $ret['regionName'] + $ret['timeStamp'] +*/ +function opensim_get_avatar_online($uuid, &$db=null) +{ + if (!isGUID($uuid)) return null; + if (!is_object($db)) $db = opensim_new_db(); + + $online = false; + $timestamp = 0; + $region = UUID_ZERO; + $rgn_name = ''; + + if ($db->exist_table('Presence')) { + $query_str = "SELECT RegionID,unix_timestamp(LastSeen) FROM Presence,GridUser WHERE Presence.UserID='$uuid'". + " AND RegionID!='".UUID_ZERO."' AND GridUser.UserID LIKE '".$uuid."%'"; + $db->query($query_str); + if ($db->Errno==0) { + list($region, $timestamp) = $db->next_record(); + if ($region!='') { + $rgn_name = opensim_get_region_name($region); + if ($rgn_name!='') $online = true; + else opensim_set_avatar_offline($uuid); + } + } + } + // Standalone + else { + $query_str = "SELECT LastRegionID,unix_timestamp(Login) FROM GridUser WHERE UserID LIKE '".$uuid."%' AND Online='True'"; + $db->query($query_str); + if ($db->Errno==0) { + list($region, $timestamp) = $db->next_record(); + if ($region!='') { + $rgn_name = opensim_get_region_name($region); + if ($rgn_name!='') $online = true; + else opensim_set_avatar_offline($uuid); + } + } + } + + $ret['online'] = $online; + $ret['regionUUID'] = $region; + $ret['regionName'] = $rgn_name; + $ret['timeStamp'] = $timestamp; + + return $ret; +} + + +/* + return: + $ret[$UUID]['UUID'] + $ret[$UUID]['online'] + $ret[$UUID]['regionUUID'] + $ret[$UUID]['regionName'] + $ret[$UUID]['timeStamp'] +*/ +function opensim_get_avatars_online($condition='', $order='', $limit='', $hg_avatar=true, &$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + if ($condition!='') $condition = 'AND ('.$condition.')'; + if ($order!='') $order = ' ORDER BY '.$order; + if ($limit!='') $limit = ' LIMIT '. $limit; + + $ret = array(); + + $condition .= $order.$limit; + if ($db->exist_table('Presence')) { + if ($hg_avatar) { + $query_str = 'SELECT UserID,RegionID,unix_timestamp(LastSeen) FROM Presence '. + " WHERE RegionID!='".UUID_ZERO."'".$condition; + } + else { + $query_str = 'SELECT Presence.UserID,RegionID,unix_timestamp(LastSeen) FROM Presence,GridUser '. + " WHERE Presence.UserID=GridUser.UserID AND GridUser.UserID NOT LIKE '%;%' AND RegionID!='".UUID_ZERO."'".$condition; + } + + $db->query($query_str); + + if ($db->Errno==0) { + while (list($UUID, $region, $lastlogin) = $db->next_record()) { + $ret[$UUID]['UUID'] = $UUID; + $ret[$UUID]['online'] = true; + $ret[$UUID]['regionUUID'] = $region; + $ret[$UUID]['timeStamp'] = $lastlogin; + // + $rgn_name = opensim_get_region_name($region); + if ($rgn_name!='') $ret[$UUID]['regionName'] = $rgn_name; + else opensim_set_avatar_offline($UUID); + } + } + } + // Standalone + else { + if (!$hg_avatar) $condition = " AND UserID NOT LIKE '%;%'".$condition; + $query_str = 'SELECT UserID,LastRegionID,Login FROM GridUser '. + " WHERE Online='True' AND LastRegionID!='".UUID_ZERO."'".$condition; + $db->query($query_str); + + if ($db->Errno==0) { + while (list($UUID, $region, $lastlogin) = $db->next_record()) { + $uuids = explode(';', $UUID); + $ret[$UUID]['UUID'] = $uuids[0]; + $ret[$UUID]['online'] = true; + $ret[$UUID]['regionUUID'] = $region; + $ret[$UUID]['timeStamp'] = $lastlogin; + // + $rgn_name = opensim_get_region_name($region); + if ($rgn_name!='') $ret[$UUID]['regionName'] = $rgn_name; + else opensim_set_avatar_offline($uuids[0]); + } + } + } + + return $ret; +} + + +function opensim_get_avatars_online_num($hg_avatar=true, &$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + + if ($db->exist_table('Presence')) { + if ($hg_avatar) { + $query_str = "SELECT COUNT(*) FROM Presence WHERE RegionID!='".UUID_ZERO."'"; + } + else { + $query_str = "SELECT COUNT(*) FROM Presence,GridUser WHERE Presence.UserID=GridUser.UserID ". + "AND GridUser.UserID NOT LIKE '%;%' AND RegionID!='".UUID_ZERO."'"; + } + } + // Standalone + else { + $query_str = "SELECT COUNT(*) FROM GridUser WHERE Online='True' AND LastRegionID!='".UUID_ZERO."'"; + if (!$hg_avatar) $query_str .= " AND UserID NOT LIKE '%;%'"; + } + + $num = 0; + $db->query($query_str); + list($num) = $db->next_record(); + + return $num; +} + + +function opensim_get_avatar_flags($uuid, &$db=null) +{ + if (!isGUID($uuid)) return null; + if (!is_object($db)) $db = opensim_new_db(); + + $db->query("SELECT UserFlags FROM UserAccounts WHERE PrincipalID='$uuid'"); + if ($db->Errno==0) { + list($flags) = $db->next_record(); + return $flags; + } + + return 0; +} + + +function opensim_set_avatar_flags($uuid, $flags=0, &$db=null) +{ + if (!isGUID($uuid)) return false; + if (!isNumeric($flags)) return false; + if (!is_object($db)) $db = opensim_new_db(); + + $query_str = "UPDATE UserAccounts SET UserFlags='$flags' WHERE PrincipalID='$uuid'"; + $db->query($query_str); + if ($db->Errno==0) return true; + + return false; +} + + +function opensim_set_avatar_offline($uuid, &$db=null) +{ + if (!isGUID($uuid)) return; + if (!is_object($db)) $db = opensim_new_db(); + + if ($db->exist_table('Presence')) { + $db->query("DELETE FROM Presence WHERE UserID='".$uuid."'"); + } + $db->query("UPDATE GridUser SET Online='False' WHERE UserID LIKE '". $uuid."%'"); + + return; +} + + +function opensim_create_avatar($UUID, $firstname, $lastname, $passwd, $homeregion, $base_avatar=UUID_ZERO, &$db=null) +{ + if (!isGUID($UUID)) return false; + if (!isAlphabetNumericSpecial($firstname)) return false; + if (!isAlphabetNumericSpecial($lastname)) return false; + if (!isAlphabetNumericSpecial($passwd)) return false; + if (!isAlphabetNumericSpecial($homeregion)) return false; + if (!is_object($db)) $db = opensim_new_db(); + + $nulluuid = UUID_ZERO; + $passwdsalt = make_random_hash(); + $passwdhash = md5(md5($passwd).":".$passwdsalt); + + $db->query("SELECT uuid,regionHandle FROM regions WHERE regionName='$homeregion'"); + $errno = $db->Errno; + if ($errno==0) { + list($regionID,$regionHandle) = $db->next_record(); + + $serviceURLs = 'HomeURI= GatekeeperURI= InventoryServerURI= AssetServerURI='; + $db->query('INSERT INTO UserAccounts (PrincipalID,ScopeID,FirstName,LastName,Email,ServiceURLs,Created,UserLevel,UserFlags,UserTitle) '. + "VALUES ('$UUID','$nulluuid','$firstname','$lastname','','$serviceURLs','".time()."','0','0','')"); + $errno = $db->Errno; + if ($errno==0) { + $db->query('INSERT INTO GridUser (UserID,HomeRegionID,HomePosition,HomeLookAt,LastRegionID,LastPosition,LastLookAt,Online,Login,Logout) '. + "VALUES ('$UUID','$regionID','<128,128,0>','<0,0,0>','$regionID','<128,128,0>','<0,0,0>','false','0','0')"); + $errno = $db->Errno; + } + if ($errno==0) { + $db->query('INSERT INTO auth (UUID,passwordHash,passwordSalt,webLoginKey,accountType) '. + "VALUES ('$UUID','$passwdhash','$passwdsalt','$nulluuid','UserAccount')"); + $errno = $db->Errno; + } + // + if ($errno==0) { + opensim_create_avatar_inventory($UUID, $base_avatar, $db); + } + else { + $db->query("DELETE FROM UserAccounts WHERE PrincipalID='$UUID'"); + $db->query("DELETE FROM auth WHERE UUID='$UUID'"); + $db->query("DELETE FROM inventoryfolders WHERE agentID='$UUID'"); + $db->query("DELETE FROM GridUser WHERE UserID='$UUID'"); + } + } + + if ($errno!=0) return false; + return true; +} + + +// +// データベースからアバタ情報を削除する. +// +function opensim_delete_avatar($uuid, &$db=null) +{ + if (!isGUID($uuid)) return false; + if (!is_object($db)) $db = opensim_new_db(); + + $db->query("DELETE FROM UserAccounts WHERE PrincipalID='$uuid'"); + $db->query("DELETE FROM auth WHERE UUID='$uuid'"); + $db->query("DELETE FROM Avatars WHERE PrincipalID='$uuid'"); + $db->query("DELETE FROM Friends WHERE PrincipalID='$uuid'"); + $db->query("DELETE FROM tokens WHERE UUID='$uuid'"); + $db->query("DELETE FROM GridUser WHERE UserID='$uuid'"); + if ($db->exist_table('Presence')) $db->query("DELETE FROM Presence WHERE UserID='$uuid'"); + if ($db->exist_table('Avatars')) $db->query("DELETE FROM Avatars WHERE PrincipalID='$uuid'"); + + $db->query("DELETE FROM estate_managers WHERE uuid='$uuid'"); + $db->query("DELETE FROM estate_users WHERE uuid='$uuid'"); + $db->query("DELETE FROM estateban WHERE bannedUUID='$uuid'"); + $db->query("DELETE FROM inventoryfolders WHERE agentID='$uuid'"); + $db->query("DELETE FROM inventoryitems WHERE avatarID='$uuid'"); + $db->query("DELETE FROM landaccesslist WHERE AccessUUID='$uuid'"); + $db->query("DELETE FROM regionban WHERE bannedUUID='$uuid'"); + + // for DTL Money Server + if ($db->exist_table('balances')) { + $db->query("DELETE FROM balances WHERE user='$uuid'"); + $db->query("DELETE FROM userinfo WHERE user='$uuid'"); + } + + return true; +} + + + + +///////////////////////////////////////////////////////////////////////////////////// +// +// for Region +// + +function opensim_get_regions_num($hg=false, $condition='', &$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + if ($condition!='') { + $condition = 'WHERE '.$condition; + if (!$hg) $condition .= " AND (regionName NOT LIKE 'http://%' and regionName NOT LIKE 'https://%') "; + } + else { + if (!$hg) $condition = " WHERE (regionName NOT LIKE 'http://%' and regionName NOT LIKE 'https://%') "; + } + + $num = 0; + $db->query('SELECT COUNT(*) FROM regions '.$condition); + list($num) = $db->next_record(); + + return $num; +} + + +function opensim_get_region_uuid($name, &$db=null) +{ +// $name = addslashes($name); +// if (!isAlphabetNumericSpecial($name)) return false; + if (!is_object($db)) $db = opensim_new_db(); + + $uuid = ''; + if ($name!='') { + $query = "SELECT uuid FROM regions WHERE regionName='$name'"; + $db->query($query); + list($uuid) = $db->next_record(); + } + + return $uuid; +} + + +function opensim_get_regions_uuid($hg=false, &$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + + $uuids = array(); + $query = "SELECT uuid FROM regions"; + if (!$hg) $query .= " WHERE (regionName NOT LIKE 'http://%' and regionName NOT LIKE 'https://%') "; // for Standalone + + $db->query($query); + if ($db->Errno==0) { + while (list($UUID) = $db->next_record()) $uuids[$UUID] = $UUID; + } + + return $uuids; +} + + +function opensim_get_region_name($id, &$db=null) +{ + if (!isGUID($id) and !isNumeric($id)) return null; + if (!is_object($db)) $db = opensim_new_db(); + + if (isGUID($id)) { + $db->query("SELECT regionName FROM regions WHERE uuid='$id'"); + list($regionName) = $db->next_record(); + } + else { + $db->query("SELECT regionName FROM regions WHERE regionHandle='$id'"); + list($regionName) = $db->next_record(); + } + + return $regionName; +} + + +// +function opensim_get_regions_names($hg=false, $condition='', $order='', $limit='', &$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + if ($condition!='') { + $condition = 'WHERE '.$condition; + if (!$hg) $condition .= " AND (regionName NOT LIKE 'http://%' and regionName NOT LIKE 'https://%') "; + } + else { + if (!$hg) $condition = " WHERE (regionName NOT LIKE 'http://%' and regionName NOT LIKE 'https://%') "; + } + if ($order!='') $order = ' ORDER BY '.$order; + if ($limit!='') $limit = ' LIMIT '. $limit; + + $regions = array(); + $db->query('SELECT regionName FROM regions '.$condition.$order.$limit); + while ($db->Errno==0 and list($region)=$db->next_record()) { + $regions[] = $region; + } + + return $regions; +} + + +/* + return: + $rginfo[firstname] + $rginfo[lastname] + $rginfo[fullname] + $rginfo[owner_uuid] + $rginfo[estate_id] + $rginfo[estate_owner] + $rginfo[estate_name] + $rginfo[regionHandle] + $rginfo[regionName] + $rginfo[regionSecret] + $rginfo[serverIP] + $rginfo[serverHttpPort] + $rginfo[serverPort] + $rginfo[serverURI] + $rginfo[locX] + $rginfo[locY] + $rginfo[sizeX] + $rginfo[sizeY] + $rginfo[serverName] +*/ +function opensim_get_region_info($region, &$db=null) +{ + if (!isGUID($region)) return null; + if ($region==UUID_ZERO) return null; + if (!is_object($db)) $db = opensim_new_db(); + + $sql = "SELECT regionHandle,regionName,regionSecret,serverIP,serverHttpPort,serverURI,owner_uuid,locX,locY,sizeX,sizeY FROM regions WHERE uuid='$region'"; + $db->query($sql); + list($regionHandle, $regionName, $regionSecret, $serverIP, $serverHttpPort, $serverURI, $owner_uuid, $locX, $locY, $sizeX, $sizeY) = $db->next_record(); + + $rginfo = opensim_get_estate_info($region, $db); + $rginfo['regionHandle'] = $regionHandle; + $rginfo['regionName'] = $regionName; + $rginfo['regionSecret'] = $regionSecret; + $rginfo['serverIP'] = $serverIP; + $rginfo['serverHttpPort'] = $serverHttpPort; + $rginfo['serverPort'] = $serverHttpPort; + $rginfo['serverURI'] = $serverURI; + $rginfo['locX'] = $locX; + $rginfo['locY'] = $locY; + $rginfo['sizeX'] = $sizeX; + $rginfo['sizeY'] = $sizeY; + // + $uri = preg_split("/[:\/]/", $serverURI); + if (array_key_exists(3, $uri)) { + $rginfo['serverName'] = $uri[3]; + //$rginfo['serverIP2'] = gethostbyname($uri[3]); + } + else { + $rginfo['serverName'] = ''; + //$rginfo['serverIP2'] = ''; + } + + if ($rginfo['owner_uuid']=='') $rginfo['owner_uuid'] = $owner_uuid; + + return $rginfo; +} + + +/* + return: + $rginfos[$UUID]['UUID'] ... UUID + $rginfos[$UUID]['regionName'] ... name of region + $rginfos[$UUID]['locX'] ... location X + $rginfos[$UUID]['locY'] ... location Y + $rginfos[$UUID]['sizeX'] ... size X + $rginfos[$UUID]['sizeY'] ... size Y + $rginfos[$UUID]['serverIP'] ... IP address of server + $rginfos[$UUID]['serverIP2'] ... IP address of server + $rginfos[$UUID]['serveName'] ... Name of server + $rginfos[$UUID]['serverPort'] ... port num of server + $rginfos[$UUID]['serverURI'] ... URI of server + $rginfos[$UUID]['owner_uuid'] ... UUID of region owner + $rginfos[$UUID]['estate_id'] ... ID of estate + $rginfos[$UUID]['estate_owner'] ... UUID of estate owner + $rginfos[$UUID]['estate_name'] ... estate name + $rginfos[$UUID]['est_firstname'] ... first name + $rginfos[$UUID]['est_lastname'] ... last name + $rginfos[$UUID]['est_fullname'] ... full name +*/ +function opensim_get_regions_infos($hg=false, $condition='', $order='', $limit='', &$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + if ($condition!='') { + $condition = 'WHERE '.$condition; + if (!$hg) $condition .= " AND (regionName NOT LIKE 'http://%' and regionName NOT LIKE 'https://%') "; + } + else { + if (!$hg) $condition = " WHERE (regionName NOT LIKE 'http://%' and regionName NOT LIKE 'https://%') "; + } + if ($order!='') $order = ' ORDER BY '.$order; + if ($limit!='') $limit = ' LIMIT '. $limit; + + $rginfos = array(); + + $items = ' regions.uuid,regionName,locX,locY,sizeX,sizeY,serverIP,serverURI,serverHttpPort,owner_uuid,estate_map.EstateID,EstateOwner,EstateName,'; + $uname = ' FirstName,LastName '; + $from = ' FROM regions'; + $join1 = ' LEFT JOIN estate_map ON RegionID=regions.uuid '; + $join2 = ' LEFT JOIN estate_settings ON estate_map.EstateID=estate_settings.EstateID '; + $join3 = ' LEFT JOIN UserAccounts ON owner_uuid=UserAccounts.PrincipalID '; + $frmwh = ' FROM UserAccounts WHERE UserAccounts.PrincipalID='; + + $query_str = 'SELECT '.$items.$uname.$from.$join1.$join2.$join3.$condition.$order.$limit; + $db->query($query_str); + if ($db->Errno==0) { + while (list($UUID,$regionName,$locX,$locY,$sizeX,$sizeY,$serverIP,$serverURI,$serverPort, + $owneruuid,$estateid,$estateowner,$estatename,$firstname,$lastname) = $db->next_record()) { + $rginfos[$UUID]['UUID'] = $UUID; + $rginfos[$UUID]['regionName'] = $regionName; + $rginfos[$UUID]['locX'] = $locX; + $rginfos[$UUID]['locY'] = $locY; + $rginfos[$UUID]['sizeX'] = $sizeX; + $rginfos[$UUID]['sizeY'] = $sizeY; + $rginfos[$UUID]['serverIP'] = $serverIP; + $rginfos[$UUID]['serverPort'] = $serverPort; + $rginfos[$UUID]['serverURI'] = $serverURI; + $rginfos[$UUID]['owner_uuid'] = $owneruuid; + $rginfos[$UUID]['estate_id'] = $estateid; + $rginfos[$UUID]['estate_owner'] = $estateowner; + $rginfos[$UUID]['estate_name'] = $estatename; + $rginfos[$UUID]['est_firstname']= $firstname; + $rginfos[$UUID]['est_lastname'] = $lastname; + $rginfos[$UUID]['est_fullname'] = null; + // + if ($rginfos[$UUID]['estate_owner']==null) $rginfos[$UUID]['estate_owner'] = $owneruuid; + + $uri = preg_split("/[:\/]/", $serverURI); + if (array_key_exists(3, $uri)) { + $rginfos[$UUID]['serverName'] = $uri[3]; + //$rginfos[$UUID]['serverIP2'] = gethostbyname($uri[3]); + } + else { + $rginfo['serverName'] = ''; + //$rginfo['serverIP2'] = ''; + } + // + $fullname = $firstname.' '.$lastname; + if ($fullname!=' ') $rginfos[$UUID]['est_fullname'] = $fullname; + } + } + + // Region Owner + foreach($rginfos as $region) { + $rginfos[$region['UUID']]['rgn_firstname'] = null; + $rginfos[$region['UUID']]['rgn_lastname'] = null; + $rginfos[$region['UUID']]['rgn_fullname'] = null; + + if ($region['owner_uuid']!=null) { + $db->query('SELECT '.$uname.$frmwh."'".$region['owner_uuid']."'"); + list($firstname,$lastname) = $db->next_record(); + $rginfos[$region['UUID']]['rgn_firstname'] = $firstname; + $rginfos[$region['UUID']]['rgn_lastname'] = $lastname; + $fullname = $firstname.' '.$lastname; + if ($fullname!=' ') $rginfos[$region['UUID']]['rgn_fullname'] = $fullname; + } + } + + return $rginfos; +} + + +function opensim_set_current_region($uuid, $regionid, &$db=null) +{ + if (!isGUID($uuid) or !isGUID($regionid)) return false; + if (!is_object($db)) $db = opensim_new_db(); + + if ($db->exist_table('Presence')) { + $sql = "UPDATE Presence SET RegionID='".$regionid."' WHERE UserID='". $uuid."'"; + } + // Standalone + else { + $sql = "UPDATE GridUser SET LastRegionID='".$regionid."' WHERE UserID='". $uuid."'"; + } + + $db->query($sql); + if ($db->Errno!=0) return false; + $db->next_record(); + + return true; +} + + +function opensim_delete_region($uuid, &$db=null) +{ + if (!isGUID($uuid)) return false; + if (!is_object($db)) $db = opensim_new_db(); + + $db->query("DELETE FROM regions WHERE uuid='$uuid'"); + if ($db->Errno!=0) return false; + + return true; +} + + + + +///////////////////////////////////////////////////////////////////////////////////// +// +// for Home Region +// + +function opensim_get_home_region($uuid, &$db=null) +{ + if (!isGUID($uuid)) return null; + if (!is_object($db)) $db = opensim_new_db(); + + $region_name = ''; + $db->query("SELECT regionName FROM GridUser,regions WHERE HomeRegionID=uuid AND UserID='$uuid'"); + list($region_name) = $db->next_record(); + + return $region_name; +} + + +function opensim_set_home_region($uuid, $hmregion, $pos_x='128', $pos_y='128', $pos_z='0', &$db=null) +{ + if (!isGUID($uuid)) return false; + if (!isNumeric($pos_x) or !isNumeric($pos_y) or !isNumeric($pos_z)) return false; + if (!is_object($db)) $db = opensim_new_db(); + + $db->query("SELECT uuid,regionHandle FROM regions WHERE regionName='$hmregion'"); + $errno = $db->Errno; + if ($errno==0) { + list($regionID, $regionHandle) = $db->next_record(); + + $homePosition = "<$pos_x,$pos_y,$pos_z>"; + $db->query("UPDATE GridUser SET HomeRegionID='$regionID',HomePosition='$homePosition' WHERE UserID='$uuid'"); + $errno = $db->Errno; + } + + if ($errno!=0) return false; + return true; +} + + + + +///////////////////////////////////////////////////////////////////////////////////// +// +// for Estate +// + +// +// リージョンID $region のエステート名を $estate にし,オーナーを $owner(UUID) にする. +// エステート名と オーナー(UUID) の組み合わせが存在しない場合は,新しくエステートを作成する. +// +function opensim_set_region_estate($region, $estate, $owner, &$db=null) +{ + if (!isGUID($region) or $estate=='' or !isGUID($owner)) return false; + if (!is_object($db)) $db = opensim_new_db(); + + $estate_id = opensim_create_estate($estate, $owner, $db); + if ($estate_id==0) return false; + + $db->query("UPDATE estate_map SET EstateID='$estate_id' WHERE RegionID='$region'"); + + if ($db->Errno!=0) return false; + return true; +} + + +// +// Estate名 $estate, オーナーUUID $owner のエステートを作成して ID を返す. +// 既に有る場合も,そのエステートのIDを返す. +// エラーの場合は 0を返す. +// +function opensim_create_estate($estate, $owner, &$db=null) +{ + if ($estate=='' or !isGUID($owner)) return 0; + if (!is_object($db)) $db = opensim_new_db(); + + $db->query("SELECT EstateID FROM estate_settings WHERE EstateName='$estate' AND EstateOwner='$owner'"); + if ($db->Errno==0) { + list($eid) = $db->next_record(); + if (intval($eid)>0) return $eid; + } + + $insert_columns = 'EstateName,AbuseEmailToEstateOwner,DenyAnonymous,ResetHomeOnTeleport,FixedSun,DenyTransacted,BlockDwell,'. + 'DenyIdentified,AllowVoice,UseGlobalTime,PricePerMeter,TaxFree,AllowDirectTeleport,RedirectGridX,RedirectGridY,'. + 'ParentEstateID,SunPosition,EstateSkipScripts,BillableFactor,PublicAccess,AbuseEmail,EstateOwner,DenyMinors,'. + 'AllowLandmark,AllowParcelChanges,AllowSetHome'; + $insert_values = "'$estate','0','0','0','0','0','0','0','1','1','1','0','1','0','0','1','0','0','0','1','','$owner','0','1','1','1'"; + + $db->query("INSERT INTO estate_settings ($insert_columns) VALUES ($insert_values)"); + $db->query("SELECT EstateID FROM estate_settings WHERE EstateName='$estate' AND EstateOwner='$owner'"); + + if ($db->Errno==0) { + list($eid) = $db->next_record(); + return $eid; + } + + return 0; +} + + + +function opensim_get_estates_infos(&$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + + $estates = array(); + $db->query('SELECT EstateID,EstateOwner,EstateName FROM estate_settings ORDER BY EstateID'); + if ($db->Errno==0) { + while (list($estateid, $estateown, $estatename) = $db->next_record()) { + $estates[$estateid]['estate_id'] = $estateid; + $estates[$estateid]['estate_owner'] = $estateown; + $estates[$estateid]['estate_name'] = $estatename; + $estates[$estateid]['firstname'] = ''; + $estates[$estateid]['lastname'] = ''; + $estates[$estateid]['fullname'] = ''; + } + } + + foreach($estates as $estate) { + $avatar = opensim_get_avatar_name($estate['estate_owner'], false); + if ($avatar!=null) { + $estateid = $estate['estate_id']; + $estates[$estateid]['firstname'] = $avatar['firstname']; + $estates[$estateid]['lastname'] = $avatar['lastname']; + $estates[$estateid]['fullname'] = $avatar['fullname']; + } + } + + return $estates; +} + + +// +// SIMのリージョンIDからエステートの情報を返す. +// +function opensim_get_estate_info($region, &$db=null) +{ + if (!isGUID($region)) return null; + if (!is_object($db)) $db = opensim_new_db(); + + $firstname = null; + $lastname = null; + $fullname = null; + $owneruuid = null; + + $rqdt = 'PrincipalID,FirstName,LastName,estate_settings.EstateID,EstateOwner,EstateName'; + $tbls = 'UserAccounts,estate_map,estate_settings'; + $cndn = "RegionID='$region' AND estate_map.EstateID=estate_settings.EstateID AND EstateOwner=PrincipalID"; + + $db->query('SELECT '.$rqdt.' FROM '.$tbls.' WHERE '.$cndn); + list($owneruuid, $firstname, $lastname, $estateid, $estateowner, $estatename) = $db->next_record(); + + $fullname = $firstname.' '.$lastname; + if ($fullname==' ') $fullname = null; + + // owner name + $name['firstname'] = $firstname; + $name['lastname'] = $lastname; + $name['fullname'] = $fullname; + // + $name['owner_uuid'] = $owneruuid; + $name['estate_id'] = $estateid; + $name['estate_owner']= $estateowner; + $name['estate_name'] = $estatename; + + return $name; +} + + +// +// リージョンのエステートを変更する. +// +function opensim_set_region_estateid($region, $estateid, &$db=null) +{ + if (!isGUID($region) or !isNumeric($estateid)) return false; + if (!is_object($db)) $db = opensim_new_db(); + + $db->query("SELECT EstateID FROM estate_settings WHERE EstateID='$estateid'"); + list($esid) = $db->next_record(); + if (intval($esid)==0) return false; + + $db->query("SELECT EstateID FROM estate_map WHERE RegionID='$region'"); + list($esid) = $db->next_record(); + + if (intval($esid)!=$estateid) { + $db->query("UPDATE estate_map SET EstateID='$estateid' WHERE RegionID='$region'"); + } + else if (intval($esid)==0) { + $db->query("INSERT INTO estate_map (RegionID,EstateID) VALUES ('$region','$estateid')"); + } + + return true; +} + + +// +// リージョンのエステートを変更せずに,オーナーのみ変更する. +// 従って,同じエステートを持つ他のリージョンのオーナーも変更される. +// +function opensim_set_estate_owner($region, $owner, &$db=null) +{ + if (!isGUID($region) or !isGUID($owner)) return false; + if (!is_object($db)) $db = opensim_new_db(); + + $db->query("UPDATE estate_settings,estate_map SET EstateOwner='$owner' WHERE estate_settings.EstateID=estate_map.EstateID AND RegionID='$region'"); + $errno = $db->Errno; + + if ($errno==0) $db->query("UPDATE regions SET owner_uuid='$owner' WHERE uuid='$region'"); + if ($errno!=0) return false; + + return true; +} + + +function opensim_del_estate($id, &$db=null) +{ + if (!isNumeric($id)) return; + if (!is_object($db)) $db = opensim_new_db(); + + $db->query("DELETE from estate_settings WHERE EstateID=$id"); + + return; +} + + +function opensim_update_estate($id, $name, $owner, &$db=null) +{ + if (!isNumeric($id)) return false; + if (!$name and !$owner) return false; + if (!is_object($db)) $db = opensim_new_db(); + + if ($name) { + $db->query("UPDATE estate_settings SET EstateName='$name' WHERE EstateID=$id"); + } + + $uuid = opensim_get_avatar_uuid($owner, false); + if ($uuid) { + $db->query("UPDATE estate_settings SET EstateOwner='$uuid' WHERE EstateID=$id"); + } + + return true; +} + + + + +///////////////////////////////////////////////////////////////////////////////////// +// +// for Parcel +// + +function opensim_get_parcel_name($parcel, &$db=null) +{ + if (!isGUID($parcel)) return null; + if (!is_object($db)) $db = opensim_new_db(); + + $name = null; + $db->query("SELECT name FROM land WHERE UUID='$parcel'"); + + if ($db->Errno==0) list($name) = $db->next_record(); + + return $name; +} + + +function opensim_get_parcel_info($parcel, &$db=null) +{ + if (!isGUID($parcel)) return null; + if (!is_object($db)) $db = opensim_new_db(); + + $info = array(); + + $items = "RegionUUID,Name,Description,OwnerUUID,Category,SalePrice,LandStatus,LandFlags,LandingType,Dwell"; + $query_str = "SELECT ".$items." FROM land WHERE UUID='".$parcel."'"; + + $db->query($query_str); + if ($db->Errno==0) $info = $db->next_record(); + + return $info; +} + + + + +///////////////////////////////////////////////////////////////////////////////////// +// +// for Assets +// + +function opensim_get_asset_data($uuid, &$db=null) +{ + if (!isGUID($uuid)) return null; + if (!is_object($db)) $db = opensim_new_db(); + + $asset = array(); + + $db->query("SELECT name,description,assetType,data,asset_flags,CreatorID FROM assets WHERE id='$uuid'"); + list($name, $desc, $type, $data, $flag, $creator) = $db->next_record(); + + $asset['UUID'] = $uuid; + $asset['name'] = $name; + $asset['desc'] = $desc; + $asset['type'] = $type; + $asset['data'] = $data; + $asset['flag'] = $flag; + $asset['creator'] = $creator; + + return $asset; +} + + +function opensim_display_texture_data($uuid, $prog, $xsize='0', $ysize='0', $cachedir='', $use_tga=false) +{ + if (!isGuid($uuid)) return false; + if ($prog==null or $prog=='') return false; + + if ($cachedir=='') $cachedir = '/tmp'; + $cachefile = $cachedir.'/'.$uuid; + $win_com = ''; + + // PHP module + $imagick = null; + if ($prog=='imagick') { + if (class_exists('Imagick')) { + $imagick = new Imagick(); + } + else { + echo '

PHP module Imagick is not installed!!

'; + return false; + } + } + + // Linux Command + else if ($prog=='convert' or $prog=='jasper') { + if (file_exists('/usr/bin/'.$prog)) $path = '/usr/bin/'; + else if (file_exists('/usr/X11R6/bin/'.$prog)) $path = '/usr/X11R6/bin/'; + else if (file_exists('/usr/local/bin/'.$prog)) $path = '/usr/local/bin/'; + else { + echo '

program '.$prog.' is not found!!

'; + return false; + } + if ($prog=='jasper') { // JasPer does not support Targa image format. + $use_tga = false; + } + } + + // Windows Command + else if ($prog=='opj_decompress') { + $use_tga = false; + $win_com = CMS_MODULE_PATH."\\win\\".$prog.".exe"; + if (!file_exists($win_com)) { + echo '

program '.$prog.' is not found!!

'; + return false; + } + } + + // Check j2k to TGA command + if ($use_tga) { + $tga_com = get_j2k_to_tga_command(); + if ($tga_com=='') $use_tga = false; + } + + // get and save image + if (! ((!$use_tga and file_exists($cachefile)) or ($use_tga and file_exists($cachefile.'.tga')))) { + $imgdata = ''; + + // from MySQL Server + $asset = opensim_get_asset_data($uuid); + if ($asset) { + if ($asset['type']==0) { + $imgdata = $asset['data']; + } + } + else { + echo '

asset uuid is not found!! ('.htmlspecialchars($uuid).')

'; + return false; + } + +/* // from Asset Server + //$asset_url = $ASSET_SERVER_URL.'/assets/'.$uuid; + $asset_url = 'http://202.26.159.200:8003/assets/'.$uuid; + $fp = fopen($asset_url, "rb"); + stream_set_timeout($fp, 5); + $content = stream_get_contents($fp); + fclose($fp); + if (!$content) { + echo '

asset uuid is not found!! ('.htmlspecialchars($uuid).')

'; + return false; + } + + $xml = new SimpleXMLElement($content); + $imgdata = base64_decode($xml->Data); +*/ + // Save Image Data + $fp = fopen($cachefile, 'wb'); + fwrite($fp, $imgdata); + fclose($fp); + + if ($use_tga) { + if (!j2k_to_tga($cachefile)) $use_tga = false; + } + } + + if ($use_tga && file_exists($cachefile.'.tga')) $cachefile .= '.tga'; + + // + // program for image processing of jpeg2000 + // + + // Imagick of PHP + if ($prog=='imagick' and $imagick!=null) { + $ret = $imagick->readImage($cachefile); + if (!$ret) { + echo '

Imagick could not read '.$cachefile.'!!

'; + return false; + } + $imagick->setImageFormat('JPEG'); + if ($xsize>0 and $ysize>0) { + $imagick->scaleImage($xsize, $ysize); + } + + header("Content-Type: image/jpeg"); + echo $imagick; + } + + // ImageMagic (convert) + else if ($prog=='convert') { + $imgsize = ''; + if ($xsize>0 and $ysize>0) $imgsize = ' -resize '.$xsize.'x'.$ysize.'!'; + $prog = $path.'convert '. $cachefile.$imgsize.' jpeg:-'; + + header("Content-Type: image/jpeg"); + passthru($prog); + } + + // Jasper + else if ($prog=='jasper') { + $conv = ''; + if ($xsize>0 and $ysize>0) { + $conv = get_image_size_convert_command($xsize, $ysize); + if ($conv!='') $conv = ' | '.$conv; + } + $prog = $path.'jasper -f '.$cachefile.' -T jpg '.$conv; + + header("Content-Type: image/jpeg"); + passthru($prog); + } + + // opj_decompress on Windows + else if ($prog=='opj_decompress') { + if (!file_exists($cachefile.'.bmp')) { + $prog = $win_com.' -i '.$cachefile.' -o '.$cachefile.'.bmp'; + exec($prog); + } + header("Content-Type: image/bmp"); + readfile($cachefile.'.bmp'); + } + + return true; +} + + +function opensim_get_object_name($uuid, &$db=null) +{ + $objName = ''; + if (!isGuid($uuid)) return null; + if ($uuid==UUID_ZERO) return $objName; + if (!is_object($db)) $db = opensim_new_db(); + + $query_str = "SELECT Name FROM prims WHERE UUID='$uuid'"; + + $db->query($query_str); + if ($db->Errno==0) list($objName) = $db->next_record(); + + return $objName; +} + + + +///////////////////////////////////////////////////////////////////////////////////// +// +// for Inventory +// + +function opensim_create_avatar_inventory($uuid, $base_uuid, &$db=null) +{ + if (!isGuid($uuid) or !isGuid($base_uuid)) return; + if (!is_object($db)) $db = opensim_new_db(); + + $name = array(); + if ($base_uuid!=UUID_ZERO) $name = opensim_get_avatar_name($base_uuid, false, $db); + + if (isset($name['fullname'])) { + $folder = opensim_create_inventory_folders_dup($uuid, $base_uuid, $db); + $invent = opensim_create_inventory_items_dup($uuid, $base_uuid, $folder, $db); + opensim_create_avatar_wear_dup($uuid, $base_uuid, $invent, $db); + } + else { + opensim_create_default_inventory_folders($uuid, $db); + $invent = opensim_create_default_inventory_items($uuid, $db); + opensim_create_default_avatar_wear($uuid, $invent, $db); + } + + return; +} + + +function opensim_create_avatar_wear_dup($touuid, $fromid, $invent, &$db=null) +{ + if (!$invent or !is_array($invent)) return false; + if (!is_object($db)) $db = opensim_new_db(); + if (!$db->exist_table('Avatars')) return false; + + $db->query("SELECT * FROM Avatars WHERE PrincipalID='$fromid'"); + $errno = $db->Errno; + + if ($errno==0) { + $db2 = opensim_new_db(); + while (list($PrincipalID,$Name,$Value) = $db->next_record()) { + if (!strncasecmp($Name, 'Wearable ', 9)) { + $id = explode(':', $Value); + if (array_key_exists(1, $id)) { + if (isGUID($id[0]) and isGUID($id[1])) { + if (isset($invent[$id[0]])) $Value = $invent[$id[0]].':'.$id[1]; + } + } + } + else if (!strncasecmp($Name, '_ap_', 4)) { + if (isGUID($Value)) { + if (isset($invent[$Value])) $Value = $invent[$Value]; + } + } + + $Name = addslashes($Name); + $Value = addslashes($Value); + // + $db2->query("INSERT INTO Avatars (PrincipalID,Name,Value) VALUES ('$touuid','$Name','$Value')"); + } + } + + if ($errno!=0) return false; + return true; +} + + +// +// コピーしたアイテムのIDのコピー元とコピー先の対応を格納した配列を返す. +// +function opensim_create_inventory_items_dup($touuid, $fromid, $folder, &$db=null) +{ + if (!$folder or !is_array($folder)) return null; + if (!is_object($db)) $db = opensim_new_db(); + + $invent = array(); + $db->query("SELECT * FROM inventoryitems WHERE avatarID='$fromid'"); + $errno = $db->Errno; + + if ($errno==0) { + $db2 = opensim_new_db(); + while (list($assetID,$assetType,$inventoryName,$inventoryDescription,$inventoryNextPermissions,$inventoryCurrentPermissions, + $invType,$creatorID,$inventoryBasePermissions,$inventoryEveryOnePermissions,$salePrice,$saleType,$creationDate, + $groupID,$groupOwned,$flags,$inventoryID,$avatarID,$parentFolderID,$inventoryGroupPermissions) = $db->next_record()) { + + if (isset($folder[$parentFolderID]) and $folder[$parentFolderID]->type=='46') continue; // Current Outfit + + $inventoryName = addslashes($inventoryName); + $inventoryDescription = addslashes($inventoryDescription); + + $avatarID = $touuid; + $inventID = make_random_guid(); + if (isset($folder[$parentFolderID])) $parent = $folder[$parentFolderID]->folderID; + else $parent = UUID_ZERO; + $invent[$inventoryID] = $inventID; + // + $db2->query('INSERT INTO inventoryitems (assetID,assetType,inventoryName,inventoryDescription,inventoryNextPermissions,'. + 'inventoryCurrentPermissions,invType,creatorID,inventoryBasePermissions,inventoryEveryOnePermissions,salePrice,'. + 'saleType,creationDate,groupID,groupOwned,flags,inventoryID,avatarID,parentFolderID,inventoryGroupPermissions) '. + "VALUES ('$assetID','$assetType','$inventoryName','$inventoryDescription','$inventoryNextPermissions','$inventoryCurrentPermissions',". + "'$invType','$creatorID','$inventoryBasePermissions','$inventoryEveryOnePermissions','$salePrice','$saleType','$creationDate',". + "'$groupID','$groupOwned','$flags','$inventID','$avatarID','$parent','$inventoryGroupPermissions')"); + } + } + + return $invent; +} + + +// +// 作成したフォルダーの情報を返す.キーはコピー元フォルダーのフォルダーID +// +function opensim_create_inventory_folders_dup($touuid, $fromid, &$db=null) +{ + if (!isGUID($fromid)) return null; + if (!is_object($db)) $db = opensim_new_db(); + + $folder = array(); + + $db->query("SELECT * FROM inventoryfolders WHERE agentID='$fromid'"); + $errno = $db->Errno; + + if ($errno==0) { + while(list($folderName,$type,$version,$folderID,$agentID,$parentFolderID) = $db->next_record()) { + $folder[$folderID] = new stdClass(); + $folder[$folderID]->folderName = $folderName; + $folder[$folderID]->type = $type; + $folder[$folderID]->version = $version; + $folder[$folderID]->folderID = make_random_guid(); + $folder[$folderID]->agentID = $touuid; + $folder[$folderID]->parentFolderID = $parentFolderID; + } + + foreach($folder as $fid=>$fld) { + $parent = UUID_ZERO; + if ($fld->parentFolderID) { + if (isset($folder[$fld->parentFolderID])) $parent = $folder[$fld->parentFolderID]->folderID; + } + $folder[$fid]->parentFolderID = $parent; + + $folderName = addslashes($fld->folderName); + $folderType = $fld->type; + $version = $fld->version; + $folderID = $fld->folderID; + // + $db->query("INSERT INTO inventoryfolders (folderName,type,version,folderID,agentID,parentFolderID) ". + "VALUES ('$folderName','$folderType','$version','$folderID','$touuid','$parent')"); + } + } + + return $folder; +} + + +function opensim_create_default_avatar_wear($uuid, $invent, &$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + if (!$db->exist_table('Avatars')) return false; + + $db->query("INSERT INTO Avatars (PrincipalID,Name,Value) VALUES ('$uuid','AvatarHeight','".DEFAULT_AVATAR_HEIGHT."')"); + $db->query("INSERT INTO Avatars (PrincipalID,Name,Value) VALUES ('$uuid','AvatarType','1')"); + $db->query("INSERT INTO Avatars (PrincipalID,Name,Value) VALUES ('$uuid','Serial','0')"); + $db->query("INSERT INTO Avatars (PrincipalID,Name,Value) VALUES ('$uuid','VisualParams','".DEFAULT_AVATAR_PARAMS."')"); + + if (is_array($invent)) { + if (isset($invent['Shape'])) + $db->query("INSERT INTO Avatars (PrincipalID,Name,Value) VALUES ('$uuid','Wearable 0:0','".$invent['Shape'].':'.DEFAULT_ASSET_SHAPE."')"); + if (isset($invent['Skin'])) + $db->query("INSERT INTO Avatars (PrincipalID,Name,Value) VALUES ('$uuid','Wearable 1:0','".$invent['Skin']. ':'.DEFAULT_ASSET_SKIN."')"); + if (isset($invent['Hair'])) + $db->query("INSERT INTO Avatars (PrincipalID,Name,Value) VALUES ('$uuid','Wearable 2:0','".$invent['Hair']. ':'.DEFAULT_ASSET_HAIR."')"); + if (isset($invent['Eyes'])) + $db->query("INSERT INTO Avatars (PrincipalID,Name,Value) VALUES ('$uuid','Wearable 3:0','".$invent['Eyes']. ':'.DEFAULT_ASSET_EYES."')"); + if (isset($invent['Shirt'])) + $db->query("INSERT INTO Avatars (PrincipalID,Name,Value) VALUES ('$uuid','Wearable 4:0','".$invent['Shirt'].':'.DEFAULT_ASSET_SHIRT."')"); + if (isset($invent['Pants'])) + $db->query("INSERT INTO Avatars (PrincipalID,Name,Value) VALUES ('$uuid','Wearable 5:0','".$invent['Pants'].':'.DEFAULT_ASSET_PANTS."')"); + } + + return true; +} + + +function opensim_create_default_inventory_items($uuid, &$db=null) +{ + if (!isGUID($uuid)) return false; + if (!is_object($db)) $db = opensim_new_db(); + + $db->query("SELECT folderID FROM inventoryfolders WHERE agentID='$uuid' AND type='13'"); // Body Parts Folder + list($body_folder) = $db->next_record(); + $db->query("SELECT folderID FROM inventoryfolders WHERE agentID='$uuid' AND type='5'"); // Clothing Folder + list($cloth_folder) = $db->next_record(); + if (!$body_folder or !$cloth_folder) return false; + + $default_inv = array(); + + $create_time = time(); + $insert_columns = 'assetID,assetType,inventoryName,inventoryDescription,inventoryNextPermissions,inventoryCurrentPermissions,invType,'. + 'creatorID,inventoryBasePermissions,inventoryEveryOnePermissions,creationDate,flags,inventoryID,avatarID,parentFolderID,'. + 'inventoryGroupPermissions'; + $insert_common = "'','581632','581632','18','$uuid','581632','581632','$create_time'"; + + $invID = make_random_guid(); + $db->query("INSERT INTO inventoryitems ($insert_columns) ". + "VALUES ('".DEFAULT_ASSET_SHAPE."','13','Default Shape',$insert_common,'0','$invID','$uuid','$body_folder', '581632')"); + $errno = $db->Errno; + + if ($errno==0) { + $default_inv['Shape'] = $invID; + $invID = make_random_guid(); + $db->query("INSERT INTO inventoryitems ($insert_columns) ". + "VALUES ('".DEFAULT_ASSET_SKIN. "','13','Default Skin', $insert_common,'1','$invID','$uuid','$body_folder', '581632')"); + $errno = $db->Errno; + } + if ($errno==0) { + $default_inv['Skin'] = $invID; + $invID = make_random_guid(); + $db->query("INSERT INTO inventoryitems ($insert_columns) ". + "VALUES ('".DEFAULT_ASSET_HAIR. "','13','Default Hair', $insert_common,'2','$invID','$uuid','$body_folder', '581632')"); + $errno = $db->Errno; + } + if ($errno==0) { + $default_inv['Hair'] = $invID; + $invID = make_random_guid(); + $db->query("INSERT INTO inventoryitems ($insert_columns) ". + "VALUES ('".DEFAULT_ASSET_EYES. "','13','Default Eyes', $insert_common,'3','$invID','$uuid','$body_folder', '581632')"); + $errno = $db->Errno; + } + if ($errno==0) { + $default_inv['Eyes'] = $invID; + $invID = make_random_guid(); + $db->query("INSERT INTO inventoryitems ($insert_columns) ". + "VALUES ('".DEFAULT_ASSET_SHIRT. "','5','Default Shirt',$insert_common,'4','$invID','$uuid','$cloth_folder','581632')"); + $errno = $db->Errno; + } + if ($errno==0) { + $default_inv['Shirt'] = $invID; + $invID = make_random_guid(); + $db->query("INSERT INTO inventoryitems ($insert_columns) ". + "VALUES ('".DEFAULT_ASSET_PANTS. "','5','Default Pants',$insert_common,'5','$invID','$uuid','$cloth_folder','581632')"); + $errno = $db->Errno; + } + if ($errno==0) { + $default_inv['Pants'] = $invID; + } + + return $default_inv; +} + + +function opensim_create_default_inventory_folders($uuid, &$db=null) +{ + if (!isGUID($uuid)) return false; + if (!is_object($db)) $db = opensim_new_db(); + + $my_inventory = make_random_guid(); + $db->query('INSERT INTO inventoryfolders (folderName,type,version,folderID,agentID,parentFolderID) '. + "VALUES ('My Inventory','8','1','$my_inventory','$uuid','".UUID_ZERO."')"); + // + $db->query('INSERT INTO inventoryfolders (folderName,type,version,folderID,agentID,parentFolderID) '. + "VALUES ('Textures','0','1','".make_random_guid()."','$uuid','$my_inventory')"); + $db->query('INSERT INTO inventoryfolders (folderName,type,version,folderID,agentID,parentFolderID) '. + "VALUES ('Sounds','1','1','".make_random_guid()."','$uuid','$my_inventory')"); + $db->query('INSERT INTO inventoryfolders (folderName,type,version,folderID,agentID,parentFolderID) '. + "VALUES ('Calling Cards','2','1','".make_random_guid()."','$uuid','$my_inventory')"); + $db->query('INSERT INTO inventoryfolders (folderName,type,version,folderID,agentID,parentFolderID) '. + "VALUES ('Landmarks','3','1','".make_random_guid()."','$uuid','$my_inventory')"); + $db->query('INSERT INTO inventoryfolders (folderName,type,version,folderID,agentID,parentFolderID) '. + "VALUES ('Clothing','5','1','".make_random_guid()."','$uuid','$my_inventory')"); + $db->query('INSERT INTO inventoryfolders (folderName,type,version,folderID,agentID,parentFolderID) '. + "VALUES ('Objects','6','1','".make_random_guid()."','$uuid','$my_inventory')"); + $db->query('INSERT INTO inventoryfolders (folderName,type,version,folderID,agentID,parentFolderID) '. + "VALUES ('Notecards','7','1','".make_random_guid()."','$uuid','$my_inventory')"); + $db->query('INSERT INTO inventoryfolders (folderName,type,version,folderID,agentID,parentFolderID) '. + "VALUES ('Scripts','10','1','".make_random_guid()."','$uuid','$my_inventory')"); + $db->query('INSERT INTO inventoryfolders (folderName,type,version,folderID,agentID,parentFolderID) '. + "VALUES ('Body Parts','13','1','".make_random_guid()."','$uuid','$my_inventory')"); + $db->query('INSERT INTO inventoryfolders (folderName,type,version,folderID,agentID,parentFolderID) '. + "VALUES ('Trash','14','1','".make_random_guid()."','$uuid','$my_inventory')"); + $db->query('INSERT INTO inventoryfolders (folderName,type,version,folderID,agentID,parentFolderID) '. + "VALUES ('Photo Album','15','1','".make_random_guid()."','$uuid','$my_inventory')"); + $db->query('INSERT INTO inventoryfolders (folderName,type,version,folderID,agentID,parentFolderID) '. + "VALUES ('Lost And Found','16','1','".make_random_guid()."','$uuid','$my_inventory')"); + $db->query('INSERT INTO inventoryfolders (folderName,type,version,folderID,agentID,parentFolderID) '. + "VALUES ('Animations','20','1','".make_random_guid()."','$uuid','$my_inventory')"); + $db->query('INSERT INTO inventoryfolders (folderName,type,version,folderID,agentID,parentFolderID) '. + "VALUES ('Gestures','21','1','".make_random_guid()."','$uuid','$my_inventory')"); + return true; +} + + + + +///////////////////////////////////////////////////////////////////////////////////// +// +// for Password +// + +function opensim_get_password($uuid, $tbl='', &$db=null) +{ + if (!isGUID($uuid)) return null; + if (!isAlphabetNumeric($tbl, true)) return null; + if (!is_object($db)) $db = opensim_new_db(); + + $passwdhash = null; + $passwdsalt = null; + + if ($tbl=='' or $tbl=='auth') { + if ($db->exist_table('auth')) { + $db->query("SELECT passwordHash,passwordSalt FROM auth WHERE UUID='$uuid'"); + list($passwdhash, $passwdsalt) = $db->next_record(); + } + } + + if ($passwdhash==null and $passwdsalt==null) { + if ($tbl=='' or $tbl=='users') { + if ($db->exist_table('users')) { + $db->query("SELECT passwordHash,passwordSalt FROM users WHERE UUID='$uuid'"); + list($passwdhash, $passwdsalt) = $db->next_record(); + } + } + } + + $ret['passwordHash'] = $passwdhash; + $ret['passwordSalt'] = $passwdsalt; + return $ret; +} + + +function opensim_set_password($uuid, $passwdhash, $passwdsalt='', $tbl='', &$db=null) +{ + if (!isGUID($uuid)) return false; + if (!isAlphabetNumeric($passwdhash)) return false; + if (!isAlphabetNumeric($passwdsalt, true)) return false; + if (!isAlphabetNumeric($tbl, true)) return false; + if (!is_object($db)) $db = opensim_new_db(); + + $setpasswd = "passwordHash='$passwdhash'"; + if ($passwdsalt!='') { + $setpasswd .= ",passwordSalt='$passwdsalt'"; + } + + $errno = 0; + if ($tbl=='' or $tbl=='auth') { + if ($db->exist_table('auth')) { + $db->query("UPDATE auth SET ".$setpasswd." WHERE UUID='$uuid'"); + $errno = $db->Errno; + } + } + + if (($tbl=='' or $tbl=='users') and $errno==0) { + if ($db->exist_table('users')) { + $db->query("UPDATE users SET ".$setpasswd." WHERE UUID='$uuid'"); + if ($db->Errno!=0) { + if (!$db->exist_table('auth')) $errno = 99; + } + } + } + + if ($errno!=0) return false; + return true; +} + + + +///////////////////////////////////////////////////////////////////////////////////// +// +// for Voice (VoIP) +// + +function opensim_get_voice_mode($region, &$db=null) +{ + if (!isGUID($region)) return -1; + if (!is_object($db)) $db = opensim_new_db(); + + $voiceflag = 0x60000000; + + $count = 0; + $db->query("SELECT LandFlags FROM land WHERE RegionUUID='$region'"); + while (list($flag) = $db->next_record()) { + $voiceflag &= $flag; + $count++; + } + + if ($count>0) { + if ($voiceflag==0x20000000) return 1; // プライベート + else if ($voiceflag==0x40000000) return 2; // パーセル + else return 0; // 無効 + } + + return 9; // 不明 +} + + +function opensim_set_voice_mode($region, $mode, &$db=null) +{ + if (!isGUID($region)) return false; + if (!preg_match('/^[0-2]$/', $mode)) return false; + if (!is_object($db)) $db = opensim_new_db(); + + $colum = 0; + $vflags = array(); + + $db->query("SELECT UUID,LandFlags FROM land WHERE RegionUUID='$region'"); + while (list($UUID, $flag) = $db->next_record()) { + $flag &= 0x9fffffff; + if ($mode==1) $flag |= 0x20000000; + else if ($mode==2) $flag |= 0x40000000; + + $vflags[$colum]['UUID'] = $UUID; + $vflags[$colum]['flag'] = $flag; + $colum++; + } + + foreach($vflags as $vflag) { + $UUID = $vflag['UUID']; + $flag = $vflag['flag']; + $db->query("UPDATE land SET LandFlags='$flag' WHERE UUID='$UUID'"); + } + + return true; +} + + + +///////////////////////////////////////////////////////////////////////////////////// +// +// for Currency + +// Transaction Type +$TransactionType['900'] = 'BirthGift'; +$TransactionType['901'] = 'AwardPoints'; +$TransactionType['1002'] = 'GroupCreate'; +$TransactionType['1004'] = 'GroupJoin'; +$TransactionType['1101'] = 'UploadCharge'; +$TransactionType['1102'] = 'LandAuction'; +$TransactionType['1103'] = 'ClassifiedCharge'; +$TransactionType['2003'] = 'ParcelDirFee'; +$TransactionType['2005'] = 'ClassifiedRenew'; +$TransactionType['2900'] = 'ScheduledFee'; +$TransactionType['3000'] = 'GiveInventory'; +$TransactionType['5000'] = 'ObjectSale'; +$TransactionType['5001'] = 'Gift'; +$TransactionType['5002'] = 'LandSale'; +$TransactionType['5003'] = 'ReferBonus'; +$TransactionType['5004'] = 'InvntorySale'; +$TransactionType['5005'] = 'RefundPurchase'; +$TransactionType['5006'] = 'LandPassSale'; +$TransactionType['5007'] = 'DwellBonus'; +$TransactionType['5008'] = 'PayObject'; +$TransactionType['5009'] = 'ObjectPays'; +$TransactionType['5010'] = 'BuyMoney'; +$TransactionType['5011'] = 'MoveMoney'; +$TransactionType['5012'] = 'SendMoney'; +$TransactionType['6003'] = 'GroupLiability'; +$TransactionType['6004'] = 'GroupDividend'; +$TransactionType['10000'] = 'StipendBasic'; + + +// +function opensim_get_transaction_type($type) +{ + global $TransactionType; + + if (!array_key_exists($type, $TransactionType)) return null; + + return $TransactionType[$type]; +} + + +// +// status: 0-SUCCESS, 1-PENDING, 2-FAILED(EXPIRED), 9-ERROR +// +function opensim_set_currency_transaction($srcId, $dstId, $amount, $type, $status, $desc, &$db=null) +{ + if (!isNumeric($amount)) return false; + if (!isGUID($srcId)) $srcId = UUID_ZERO; + if (!isGUID($dstId)) $dstId = UUID_ZERO; + if (!is_object($db)) $db = opensim_new_db(); + + $handle = 0; + $secure = UUID_ZERO; + $client = $srcId; + $UUID = make_random_guid(); + $srcID = $srcId; + $dstID = $dstId; + if ($client==UUID_ZERO) $client = $dstId; + + $avt = opensim_get_avatar_session($client); + if ($avt!=null) { + $region = $avt['regionID']; + $secure = $avt['secureID']; + + $rgn = opensim_get_region_info($region); + if ($rgn!=null) $handle = $rgn['regionHandle']; + } + + $senderBalance = opensim_get_currency_balance($srcID) - $amount; + $receiverBalance = opensim_get_currency_balance($dstID) + $amount; + + $sql = 'INSERT INTO transactions (UUID,sender,receiver,amount,senderBalance,receiverBalance,objectUUID,objectName'. + 'regionHandle,type,time,secure,status,description,commonName) '. + "VALUES ('".$UUID."','". + $srcID."','". + $dstID."','". + $amount."','". + $senderBalance."','". + $receiverBalance."','". + UUID_ZERO."','". + "','". + $handle."','". + $db->escape($type)."','". + time()."','". + $secure."','". + $db->escape($status)."','". + $db->escape($desc)."','". + "')"; + $db->query($sql); + + if ($db->Errno==0) return true; + return false; +} + + +function opensim_set_currency_balance($uuid, $amount, &$db=null) +{ + if (!isGUID($uuid) or !isNumeric($amount)) return false; + if (!is_object($db)) $db = opensim_new_db(); + + $userid = $db->escape($uuid); + + $db->lock_table('balances'); + + $db->query("SELECT balance FROM balances WHERE user='$userid'"); + if ($db->Errno==0) { + list($cash) = $db->next_record(); + $balance = (integer)$cash + (integer)$amount; + + $db->query("UPDATE balances SET balance='$balance' WHERE user='$userid'"); + if ($db->Errno==0) $db->next_record(); + } + + $db->unlock_table(); + + return true; +} + + +function opensim_get_currency_balance($uuid, &$db=null) +{ + if (!isGUID($uuid)) return 0; + if (!is_object($db)) $db = opensim_new_db(); + + $userid = $db->escape($uuid); + $db->query("SELECT balance FROM balances WHERE user='$userid'"); + + $cash = 0; + if ($db->Errno==0) list($cash) = $db->next_record(); + + return (integer)$cash; +} + + +function opensim_del_currency_expired(&$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + + $db->query("DELETE FROM transactions WHERE status='2'"); + + return; +} + + +function opensim_get_currency_transactions($condition='', $order='', $limit='', &$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + if ($condition!='') $condition = 'WHERE '.$condition; + if ($order!='') $order = ' ORDER BY '.$order; + if ($limit!='') $limit = ' LIMIT '. $limit; + + $query_str = 'SELECT UUID,sender,receiver,amount,senderBalance,receiverBalance,'. + 'objectUUID,objectName,type,time,status,commonName,description FROM transactions '. + $condition.$order.$limit; + $db->query($query_str); + + $trans = array(); + if ($db->Errno==0) { + while (list($UUID,$sender,$receiver,$amount,$sndBalance,$rcvBalance, + $objUUID,$objName,$type,$time,$status,$comName,$desc)=$db->next_record()) { + $trans[$UUID]['UUID'] = $UUID; + $trans[$UUID]['sender'] = $sender; + $trans[$UUID]['receiver'] = $receiver; + $trans[$UUID]['amount'] = $amount; + $trans[$UUID]['senderBalance'] = $sndBalance; + $trans[$UUID]['receiverBalance'] = $rcvBalance; + $trans[$UUID]['objectUUID'] = $objUUID; + $trans[$UUID]['objectName'] = $objName; + $trans[$UUID]['type'] = $type; + $trans[$UUID]['time'] = $time; + $trans[$UUID]['status'] = $status; + $trans[$UUID]['commonName'] = $comName; + $trans[$UUID]['description'] = $desc; + } + } + return $trans; +} + + +function opensim_get_currency_amounts_log($uuid, $condition='', $order='', $limit='', &$db=null) +{ + if (!isGUID($uuid)) return null; + if (!is_object($db)) $db = opensim_new_db(); + if ($condition!='') $condition = ' AND ('.$condition.')'; + if ($order!='') $order = ' ORDER BY '.$order; + if ($limit!='') $limit = ' LIMIT '. $limit; + + $condition = " WHERE (sender='$uuid' OR receiver='$uuid') AND sender!=receiver AND (status='0' OR status='9') ". + $condition.$order.$limit; + $query_str = 'SELECT UUID,sender,receiver,amount,senderBalance,receiverBalance,objectUUID,objectName,'. + 'regionHandle,regionUUID,type,time,status,commonName,description FROM transactions '.$condition; + $db->query($query_str); + + $trans = array(); + if ($db->Errno==0) { + while (list($UUID,$sender,$receiver,$amount,$sndBalance,$rcvBalance,$objUUID,$objName, + $regionHandle,$regionUUID,$type,$time,$status,$comName,$desc)=$db->next_record()) { + $trans[$UUID]['UUID'] = $UUID; + $trans[$UUID]['sender'] = $sender; + $trans[$UUID]['receiver'] = $receiver; + $trans[$UUID]['amount'] = $amount; + $trans[$UUID]['senderBalance'] = $sndBalance; + $trans[$UUID]['receiverBalance'] = $rcvBalance; + $trans[$UUID]['objectUUID'] = $objUUID; + $trans[$UUID]['objectName'] = $objName; + $trans[$UUID]['regionHandle'] = $regionHandle; + $trans[$UUID]['regionUUID'] = $regionUUID; + $trans[$UUID]['type'] = $type; + $trans[$UUID]['time'] = $time; + $trans[$UUID]['status'] = $status; + $trans[$UUID]['commonName'] = $comName; + $trans[$UUID]['description'] = $desc; + } + } + return $trans; + +} + + +function opensim_get_currency_amounts_num($uuid, $condition='', &$db=null) +{ + if (!isGUID($uuid)) return 0; + if (!is_object($db)) $db = opensim_new_db(); + if ($condition!='') $condition = 'AND ('.$condition.')'; + + $count = 0; + + $condition = "WHERE (sender='$uuid' OR receiver='$uuid') AND sender!=receiver AND (status='0' OR status='9') ".$condition; + $query_str = 'SELECT COUNT(*) FROM transactions '.$condition; + $db->query($query_str); + list($count) = $db->next_record(); + + return (integer)$count; + +} + + +function opensim_set_userinfo($user, $simip, $avatar, $pass, &$db=null) +{ + if (!isGUID($user)) return false; + if (!is_object($db)) $db = opensim_new_db(); + + $db_user = null; + $db->query("SELECT user,simip,avatar,pass FROM userinfo WHERE user='$user'"); + if ($db->Errno==0) { + list($db_user, $db_simip, $db_avatar, $db_pass) = $db->next_record(); + } + + if ($db_user) { + if ($simip !=null) $db_simip = $simip; + if ($avatar!=null) $db_avatar = $avatar; + if ($pass !=null) $db_pass = $pass; + $query_str = "UPDATE userinfo SET simip='$db_simip', avatar='$db_avatar', pass='$db_pass' WHERE user='$uuid'"; + } + else { + $query_str = "INSERT INTO userinfo (user, simip, avatar, pass) VALUES ('$user','$simip','$avatar','$pass')"; + } + + $db->query($query_str); + + if ($db->Errno==0) return true; + return false; +} + + +function opensim_get_userinfo($uuid, &$db=null) +{ + if (!isGUID($uuid)) return null; + if (!is_object($db)) $db = opensim_new_db(); + + $query_str = "SELECT user,simip,avatar,pass FROM userinfo WHERE user='$uuid'"; + $db->query($query_str); + + list($user, $simip, $avatar, $pass) = $db->next_record(); + + $info['UUID'] = $user; + $info['user'] = $user; + $info['simip'] = $simip; + $info['avatar'] = $avatar; + $info['pass'] = $pass; + + return $info; +} + + +function opensim_get_userinfos($condition='', $order='', $limit='', &$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + if ($condition!='') $condition = 'WHERE '.$condition; + if ($order!='') $order = ' ORDER BY '.$order; + if ($limit!='') $limit = ' LIMIT '. $limit; + + $infos = array(); + $query_str = 'SELECT user,simip,avatar,pass,type,class FROM userinfo '.$condition.$order.$limit; + + $db->query($query_str); + if ($db->Errno==0) { + while (list($UUID, $simip, $avatar, $pass, $type, $class) = $db->next_record()) { + $infos[$UUID]['UUID'] = $UUID; + $infos[$UUID]['user'] = $UUID; + $infos[$UUID]['simip'] = $simip; + $infos[$UUID]['avatar'] = $avatar; + $infos[$UUID]['pass'] = $pass; + $infos[$UUID]['type'] = $type; + $infos[$UUID]['class'] = $class; + } + } + + return $infos; +} + + +function opensim_get_totalsales($condition='', $order='', $limit='', &$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + + $db->query('SELECT MIN(time) FROM totalsales'); + list($mintime) = $db->next_record(); + + // + $sales = array(); + + if ($condition!='') $condition = 'WHERE '.$condition; + if ($order!='') $order = ' ORDER BY '.$order; + if ($limit!='') $limit = ' LIMIT '. $limit; + + $query_str = 'SELECT user,objectUUID,type,TotalCount,TotalAmount FROM totalsales '.$condition.$order.$limit; + + $db->query($query_str); + if ($db->Errno==0) { + $num = 0; + while (list($user, $objUUID, $type, $count, $amount) = $db->next_record()) { + $sales[$num]['user'] = $user; + $sales[$num]['object'] = opensim_get_object_name($objUUID); + $sales[$num]['type'] = $type; + $sales[$num]['count'] = $count; + $sales[$num]['amount'] = $amount; + $sales[$num]['time'] = $mintime; + $name = opensim_get_avatar_name($user); + $sales[$num]['name'] = $name['fullname']; + if ($sales[$num]['name'] =='') $sales[$num]['name'] = $user; + if ($sales[$num]['object']=='') $sales[$num]['object'] = $objUUID; + // + $num++; + } + } + + return $sales; +} + + +function opensim_regenerate_totalsales(&$since=0, &$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + $time = $since; + if ($time==null) $time = 0; + + $trans = array(); + + $query_str = 'SELECT receiver,objectUUID,type,COUNT(*),SUM(amount),MIN(time) FROM transactions '. + " WHERE sender != receiver AND status = '0' AND time>='$time' AND sender != '".UUID_ZERO."' ". + " GROUP BY receiver,objectUUID,type"; + + $db->query($query_str); + if ($db->Errno==0) { + $num = 0; + while (list($user, $objID, $type, $count, $amount, $time) = $db->next_record()) { + $trans[$num]['user'] = $user; + $trans[$num]['object'] = $objID; + $trans[$num]['type'] = $type; + $trans[$num]['count'] = $count; + $trans[$num]['amount'] = $amount; + $trans[$num]['time'] = $time; + $num++; + } + } + else return false; + + // + $db->lock_table('totalsales'); + $db->query('DELETE FROM totalsales'); + if ($db->Errno!=0) return false; + + $mintime = time(); + for ($i=0; $i<$num; $i++) { + $UUID = make_random_guid(); + $user = $trans[$i]['user']; + $objID = $trans[$i]['object']; + $type = $trans[$i]['type']; + $count = $trans[$i]['count']; + $amount = $trans[$i]['amount']; + $time = $trans[$i]['time']; + + $query_str = 'INSERT INTO totalsales (UUID, user, objectUUID, type, TotalCount, TotalAmount, time) '. + "VALUES ('$UUID','$user','$objID','$type','$count','$amount','$time')"; + $db->query($query_str); + if ($db->Errno!=0) break; + + if ($time<$mintime) $mintime = $time; + } + + $db->unlock_table(); + + if ($i!=$num) return false; + + $since = $mintime; + return true; +} + + + +///////////////////////////////////////////////////////////////////////////////////// +// +// Tools +// + +function opensim_get_servers_ip(&$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + + $ips = array(); + + $db->query("SELECT DISTINCT serverURI FROM regions"); + if ($db->Errno==0) { + $count = 0; + while (list($url) = $db->next_record()) { + $item = preg_split("/[:\/]/", $url); + if (array_key_exists(3, $item)) { + $ips[$count] = gethostbyname($item[3]); + $count++; + } + } + } + +/* + $db->query("SELECT DISTINCT serverIP FROM regions"); + if ($db->Errno==0) { + $count = 0; + while (list($server) = $db->next_record()) { + $ips[$count] = gethostbyname($server); + $count++; + } + } +*/ + +/* + $db->query("SELECT DISTINCT serverURI FROM regions"); + if ($db->Errno==0) { + $count = 0; + while (list($server) = $db->next_record()) { + $uri = preg_split("/[:\/]/", $server); + if (array_key_exists(3, $uri)) { + $ips[$count] = gethostbyname($uri[3]); + $count++; + } + } + } +*/ + return $ips; +} + + +function opensim_get_servers_name(&$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + + $nms = array(); + + $db->query("SELECT DISTINCT serverURI FROM regions"); + if ($db->Errno==0) { + $count = 0; + while (list($server) = $db->next_record()) { + $uri = preg_split("/[:\/]/", $server); + if (array_key_exists(3, $uri)) { + $nms[$count] = $uri[3]; + $count++; + } + } + } + + return $nms; +} + + +function opensim_get_server_info($uuid, &$db=null) +{ + if (!isGUID($uuid)) return null; + if (!is_object($db)) $db = opensim_new_db(); + + $ret = array(); + + $sql = "SELECT serverIP,serverHttpPort,serverURI,regionSecret FROM GridUser "; + $sql.= "INNER JOIN regions ON regions.uuid=GridUser.LastRegionID WHERE GridUser.UserID='".$uuid."'"; + $db->query($sql); + if ($db->Errno==0) list($serverip, $httpport, $serveruri, $secret) = $db->next_record(); + + if ($db->Errno==0) { + $ret["serverIP"] = $serverip; + $ret["serverHttpPort"] = $httpport; + $ret["serverURI"] = $serveruri; + $ret["regionSecret"] = $secret; + // + $uri = preg_split("/[:\/]/", $serveruri); + if (array_key_exists(3, $uri)) { + $ret['serverName'] = $uri[3]; +// $ret["serverIP2"] = gethostbyname($uri[3]); + } + } + return $ret; +} + + +// +// DNSが引けないと長時間待たされる +// +function opensim_is_access_from_region_server() +{ + ///////////////////////// + return true; + ///////////////////////// + + $ip_match = false; + $remote_addr = $_SERVER['REMOTE_ADDR']; + $server_addr = $_SERVER['SERVER_ADDR']; + + if ($remote_addr==$server_addr or $remote_addr=="127.0.0.1") return true; + + $ips = opensim_get_servers_ip(); + + foreach($ips as $ip) { + if ($ip==$remote_addr) { + $ip_match = true; + break; + } + } + + return $ip_match; +} + + +// +function opensim_check_secure_session($uuid, $regionid, $secure, &$db=null) +{ + if (!isGUID($uuid) or !isGUID($secure)) return false; + if (!is_object($db)) $db = opensim_new_db(); + + if ($db->exist_table('Presence')) { + $sql = "SELECT UserID FROM Presence WHERE UserID='".$uuid."' AND SecureSessionID='".$secure."'"; + if (isGUID($regionid)) $sql = $sql." AND RegionID='".$regionid."'"; + } + // Standalone + else { + $sql = "SELECT UserID FROM GridUser WHERE UserID='".$uuid."' AND Online='True'"; + if (isGUID($regionid)) $sql = $sql." AND LastRegionID='".$regionid."'"; + } + + $db->query($sql); + if ($db->Errno!=0) return false; + + list($UUID) = $db->next_record(); + if ($UUID!=$uuid) return false; + return true; +} + + +// +function opensim_check_region_secret($uuid, $secret, &$db=null) +{ + if (!isGUID($uuid)) return false; + if (!is_object($db)) $db = opensim_new_db(); + + // + $sql = "SELECT UUID FROM regions WHERE UUID='".$uuid."' AND regionSecret='".$db->escape($secret)."'"; + $db->query($sql); + if ($db->Errno==0) { + list($UUID) = $db->next_record(); + if ($UUID==$uuid) return true; + } + + return false; +} + + + + +///////////////////////////////////////////////////////////////////////////////////// +// +// Management of System +// + +function opensim_clear_login_table(&$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + + if ($db->exist_table('Presence')) { + $db->query('DELETE FROM Presence'); + } + + // Standalone + else { + $userids = array(); + $db->query("SELECT UserID FROM GridUser WHERE Online='True'"); + while (list($uid) = $db->next_record()) { + $userids[] = $uid; + } + // + foreach($userids as $userid) { + $db->query("UPDATE GridUser SET Online='False' WHERE UserID='". $userid."'"); + } + } + + return true; +} + + +function opensim_cleanup_db(&$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + + opensim_del_currency_expired($db); + opensim_del_terrainImages($db); + + return; +} + + +function opensim_del_terrainImage($uuid, &$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + if (!isGuid($uuid)) return false; + + $ret = false; + + $query = "SELECT id,name FROM assets WHERE CreatorID='".$uuid."'"; + $db->query($query); + if ($db->Errno==0) { + $db2 = opensim_new_db(); + while (list($tex_id, $name) = $db->next_record()) { + if (isGuid($tex_id) and ($name=="terrainImage_".$uuid or $name=="parcelImage_".$uuid)) { + $del_query = "DELETE FROM assets WHERE id='".$tex_id."'"; + $db2->query($del_query); + echo $del_query."
"; + $ret = true; + } + } + } + + return $ret; +} + + +function opensim_del_terrainImages(&$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + $uuids = opensim_get_regions_uuid(false, $db); + + foreach($uuids as $uuid) { + echo "checking.... ".$uuid."
"; + if (isGuid($uuid)) { + $ret = opensim_del_terrainImage($uuid, $db); + } + } +} + + +// +// for TEST +// +function opensim_debug_command(&$db=null) +{ + if (!is_object($db)) $db = opensim_new_db(); + + + //echo "Set your Debug Command at opensim_debug_command() in opensim.mysql.php
"; + +/* + $db->query('SELECT name,assetType,id,asset_flags FROM assets'); + + while (list($name,$type,$id,$flags) = $db->next_record()) { + echo $name." ".$type." ".$id." ".$flags."
"; + } +*/ +} + diff --git a/helper-php/DTLNSL_helper_scripts/include/tools.func.php b/helper-php/DTLNSL_helper_scripts/include/tools.func.php new file mode 100644 index 0000000..191a843 --- /dev/null +++ b/helper-php/DTLNSL_helper_scripts/include/tools.func.php @@ -0,0 +1,224 @@ + [key1] => value1 +// [key2] => Array +// ( +// [key3] => value3 +// ) +// + +function split_key_value($str) +{ + $info = array(); + $str = trim($str); + + if (substr($str, 0, 1)=='{' and substr($str, -1)=='}') { + $str = substr($str, 1, -1); + $inbrkt = 0; + $inquot = false; + $inkkko = false; + $isakey = true; + $key = ""; + $val = ""; + + for ($i=0; $i file.tga +// +function j2k_to_tga($file, $iscopy=true) +{ + if (!file_exists($file)) return false; + + $com_totga = get_j2k_to_tga_command(); + if ($com_totga=='') return false; + + if ($iscopy) $ret = copy ($file, $file.'.j2k'); + else $ret = rename($file, $file.'.j2k'); + if (!$ret) return false; + + exec("$com_totga -i $file.j2k -o $file.tga 1>/dev/null 2>&1"); + unlink($file.'.j2k'); + + return true; +} + + +function get_j2k_to_tga_command() +{ + $command = find_command_path('j2k_to_image'); + return $command; +} + + +// +// Image Size Convert Command String +// +function get_image_size_convert_command($xsize, $ysize) +{ + if (!isNumeric($xsize) or !isNumeric($ysize)) return ''; + + $command = find_command_path('convert'); + if ($command=='') return ''; + + $prog = $command.' - -geometry '.$xsize.'x'.$ysize.'! -'; + return $prog; +} + + +function find_command_path($command) +{ + $path = ''; + if (file_exists('/usr/local/bin/'.$command)) $path = '/usr/local/bin/'; + else if (file_exists('/usr/bin/'.$command)) $path = '/usr/bin/'; + else if (file_exists('/usr/X11R6/bin/'.$command)) $path = '/usr/X11R6/bin/'; + else if (file_exists('/bin/'.$command)) $path = '/bin/'; + else return ''; + + return $path.$command; +} + diff --git a/helper-php/DTLNSL_helper_scripts/include/xmlgroups_config.php b/helper-php/DTLNSL_helper_scripts/include/xmlgroups_config.php new file mode 100644 index 0000000..0e6468b --- /dev/null +++ b/helper-php/DTLNSL_helper_scripts/include/xmlgroups_config.php @@ -0,0 +1,54 @@ + 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 0168 + catch (Exception e) + #pragma warning restore 0168 + { + 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/source/ThirdParty/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs b/source/ThirdParty/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/MySQLSuperManager.cs new file mode 100644 index 0000000..cd2bae4 --- /dev/null +++ b/source/ThirdParty/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/source/ThirdParty/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/Properties/AssemblyInfo.cs b/source/ThirdParty/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f4a2a82 --- /dev/null +++ b/source/ThirdParty/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/source/ThirdParty/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs b/source/ThirdParty/OpenSim.Data.MySQL.MySQLMoneyDataWrapper/TransactionData.cs new file mode 100644 index 0000000..e73b579 --- /dev/null +++ b/source/ThirdParty/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/source/ThirdParty/OpenSim.Grid.MoneyServer/IMoneyDBService.cs b/source/ThirdParty/OpenSim.Grid.MoneyServer/IMoneyDBService.cs new file mode 100644 index 0000000..7140c0f --- /dev/null +++ b/source/ThirdParty/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/source/ThirdParty/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs b/source/ThirdParty/OpenSim.Grid.MoneyServer/IMoneyServiceCore.cs new file mode 100644 index 0000000..d6e3721 --- /dev/null +++ b/source/ThirdParty/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/source/ThirdParty/OpenSim.Grid.MoneyServer/MoneyDBService.cs b/source/ThirdParty/OpenSim.Grid.MoneyServer/MoneyDBService.cs new file mode 100644 index 0000000..448ab19 --- /dev/null +++ b/source/ThirdParty/OpenSim.Grid.MoneyServer/MoneyDBService.cs @@ -0,0 +1,574 @@ +/* + * 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 +{ +#pragma warning disable 0168 + + 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(); + } + } + } +#pragma warning restore 0168 +} diff --git a/source/ThirdParty/OpenSim.Grid.MoneyServer/MoneyServerBase.cs b/source/ThirdParty/OpenSim.Grid.MoneyServer/MoneyServerBase.cs new file mode 100644 index 0000000..654e4d1 --- /dev/null +++ b/source/ThirdParty/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/source/ThirdParty/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs b/source/ThirdParty/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs new file mode 100644 index 0000000..1b52f86 --- /dev/null +++ b/source/ThirdParty/OpenSim.Grid.MoneyServer/MoneyXmlRpcModule.cs @@ -0,0 +1,1708 @@ +/* + * 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); + + + // + //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/source/ThirdParty/OpenSim.Grid.MoneyServer/Program.cs b/source/ThirdParty/OpenSim.Grid.MoneyServer/Program.cs new file mode 100644 index 0000000..3fbd12d --- /dev/null +++ b/source/ThirdParty/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/source/ThirdParty/OpenSim.Grid.MoneyServer/Properties/AssemblyInfo.cs b/source/ThirdParty/OpenSim.Grid.MoneyServer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..407ecee --- /dev/null +++ b/source/ThirdParty/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/source/ThirdParty/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs b/source/ThirdParty/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs new file mode 100644 index 0000000..344644e --- /dev/null +++ b/source/ThirdParty/OpenSim.Modules.Currency/DTLNSLMoneyModule.cs @@ -0,0 +1,1905 @@ +// * 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 (!isNpc) { + 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 true; + 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/source/ThirdParty/OpenSim.Modules.Currency/NSLCertificateTools.cs b/source/ThirdParty/OpenSim.Modules.Currency/NSLCertificateTools.cs new file mode 100644 index 0000000..db0008c --- /dev/null +++ b/source/ThirdParty/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; i + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/bin/MoneyServer.ini b/source/bin/MoneyServer.ini new file mode 100644 index 0000000..f439360 --- /dev/null +++ b/source/bin/MoneyServer.ini @@ -0,0 +1,99 @@ +[Startup] +; +; Place to create a PID file +; PIDFile = "/tmp/money.pid" + + +[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 = 50 + + +[MoneyServer] +; +; 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 = true + +; +; 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 = true +EnableGuestAvatar = true +HGAvatarDefaultBalance = 1000 +GuestAvatarDefaultBalance = 1000 + +; +; 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/source/bin/OpenSim.ini.sample b/source/bin/OpenSim.ini.sample new file mode 100644 index 0000000..c932f78 --- /dev/null +++ b/source/bin/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 = 0 + + ;; Mesh upload factors + ;MeshModelUploadCostFactor = 0.0 + ;MeshModelUploadTextureCostFactor = 1.0 + ;MeshModelMinCostFactor = 0.0 + + ;; Money Unit fee to create groups. Default is 0. + PriceGroupCreate = 0 + + ; + ;; 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/source/bin/README.md b/source/bin/README.md new file mode 100644 index 0000000..0d65386 --- /dev/null +++ b/source/bin/README.md @@ -0,0 +1 @@ +# Copy this to /opensim/bin diff --git a/source/bin/SineWaveCert.pfx b/source/bin/SineWaveCert.pfx new file mode 100644 index 0000000..11c8ae5 Binary files /dev/null and b/source/bin/SineWaveCert.pfx differ diff --git a/source/bin/server_cert.p12 b/source/bin/server_cert.p12 new file mode 100644 index 0000000..16adb01 Binary files /dev/null and b/source/bin/server_cert.p12 differ diff --git a/source/money-prebuild.xml b/source/money-prebuild.xml new file mode 100644 index 0000000..e158440 --- /dev/null +++ b/source/money-prebuild.xml @@ -0,0 +1,141 @@ + + + + + ../../bin/ + + + + + ../../bin/ + + + + ../../bin/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../bin/ + + + + + ../../bin/ + + + + ../../bin/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../../bin/ + + + + + ../../bin/ + + + + ../../bin/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/patch-old-archive/DTLNSLMoneyModule-NPC.patch b/source/patch-old-archive/DTLNSLMoneyModule-NPC.patch new file mode 100644 index 0000000..b81fc0e --- /dev/null +++ b/source/patch-old-archive/DTLNSLMoneyModule-NPC.patch @@ -0,0 +1,26 @@ +--------------------------------------------------------------------- +--- DTLNSLMoneyModule.cs.ori 2020-01-16 11:40:31.320753823 +0100 ++++ DTLNSLMoneyModule.cs 2020-01-16 11:39:54.717394571 +0100 +@@ -1638,8 +1638,10 @@ + avatarType = (int)AvatarType.NPC_AVATAR; + } + // +- if ((agent.teleportFlags & (uint)Constants.TeleportFlags.ViaHGLogin)!=0 || String.IsNullOrEmpty(userName)) { +- avatarType = (int)AvatarType.HG_AVATAR; ++ if (!isNpc) { ++ if ((agent.teleportFlags & (uint)Constants.TeleportFlags.ViaHGLogin)!=0 || String.IsNullOrEmpty(userName)) { ++ avatarType = (int)AvatarType.HG_AVATAR; ++ } + } + } + if (String.IsNullOrEmpty(userName)) { +@@ -1648,7 +1650,7 @@ + + // + avatarClass = avatarType; +- if (avatarType==(int)AvatarType.NPC_AVATAR) return false; ++ if (avatarType==(int)AvatarType.NPC_AVATAR) return true; + if (avatarType==(int)AvatarType.HG_AVATAR) avatarClass = m_hg_avatarClass; + + // +--------------------------------------------------------------------- \ No newline at end of file diff --git a/source/patch-old-archive/opensim-0.9.0.patch b/source/patch-old-archive/opensim-0.9.0.patch new file mode 100644 index 0000000..8e9ebeb --- /dev/null +++ b/source/patch-old-archive/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/source/patch-old-archive/opensim-0.9.1.patch b/source/patch-old-archive/opensim-0.9.1.patch new file mode 100644 index 0000000..e803d15 --- /dev/null +++ b/source/patch-old-archive/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