1
0
Fork 0

Compare commits

...

136 Commits

Author SHA1 Message Date
Anonymous Contributor 685853a9f9 Make daily cronjobs run at midnight 2023-09-15 19:20:06 +02:00
Anonymous Contributor 36ef1c5721 Add documentation 2023-09-15 19:19:45 +02:00
Anonymous Contributor 882c5ae372 Fix icon url in presession template 2023-09-15 19:03:26 +02:00
Anonymous Contributor e4e050908b Mention IAR password to user 2023-09-11 08:50:33 +02:00
Anonymous Contributor 2b5c00db1c Improve design of profile page 2023-09-11 08:31:21 +02:00
Anonymous Contributor 2f9caf8923 Show IAR creation state/download on profile page 2023-09-11 08:29:28 +02:00
Anonymous Contributor 939f600b3f Use curl for getDataFromHTTP utility function 2023-09-11 08:01:12 +02:00
Anonymous Contributor 8231fbb08c Fix DB query and stats parsing of RegionChecker 2023-09-11 07:29:02 +02:00
Anonymous Contributor eb5a850869 Small fixes 2023-09-11 07:05:21 +02:00
Anonymous Contributor ce41e107af Fix documentation in example config 2023-09-11 06:06:33 +02:00
Anonymous Contributor 204fcb96e1 Fix identities table not being migrated properly 2023-09-11 06:06:22 +02:00
Anonymous Contributor 891f1dff70 Add API endpoint to make IAR files downloadable 2023-09-11 06:05:50 +02:00
Anonymous Contributor 6a4c343b03 Optimize IarMonitor 2023-09-11 06:04:30 +02:00
Anonymous Contributor dbcb57f71f Add better explanations for the identity system 2023-09-11 04:26:06 +02:00
Anonymous Contributor 1a869b62c2 Create separate user profile for new identities 2023-09-11 03:13:59 +02:00
Anonymous Contributor 93d8aafd6f Add new cronjob for IAR request queue processing 2023-09-10 12:07:45 +02:00
Anonymous Contributor 2ed8285c93 Change structure of IAR table, add migrations 2023-09-10 12:00:49 +02:00
Anonymous Contributor a166e65421 Return JSON report for cron calls 2023-09-10 11:58:20 +02:00
Anonymous Contributor e6ba73430b Add simple OpenSimulator RestConsole client 2023-09-10 07:41:42 +02:00
Anonymous Contributor 80db5f35d1 Add internal cronjob for PHP session cleanup 2023-09-10 04:53:40 +02:00
Anonymous Contributor bb40c8c9a4 Port working cronjobs to new CronJob API 2023-09-10 04:53:13 +02:00
Anonymous Contributor a213a38b3c Add simple API and starter for cron jobs 2023-09-10 04:41:21 +02:00
Anonymous Contributor b18b960fb0 Use APCu (if available) to cache username lookups 2023-09-10 02:45:06 +02:00
Anonymous Contributor c416ec0992 Fix region deletion as admin 2023-09-10 01:31:26 +02:00
Anonymous Contributor 13495cd041 Fix control flow of registration RequestHandler 2023-09-10 01:30:11 +02:00
Anonymous Contributor 9e0267513b Add displayError utility function 2023-09-10 01:29:08 +02:00
Anonymous Contributor 8e20909f23 Fix leftover reference to RUNTIME in Profile.php 2023-09-09 06:31:23 +02:00
Anonymous Contributor f87fa5d5bb Fix some template variables 2023-09-09 06:30:42 +02:00
Anonymous Contributor 5274f306e3 Make sure argument of Util::fillString is string 2023-09-09 06:29:52 +02:00
Anonymous Contributor 9483889e55 Fix func. call with wrong signature in OpenSim.php 2023-09-09 06:28:51 +02:00
Anonymous Contributor 37ad81fb16 Rename own tables to be consistent and distinct 2023-09-09 06:27:20 +02:00
Anonymous Contributor f50053a745 Add migration feature for own MySQL tables 2023-09-09 06:21:30 +02:00
Anonymous Contributor 07f196fc51 Remove old temporary directory 2023-09-06 17:42:15 +02:00
Anonymous Contributor fc0412d50a Strip all tags from plaintext mail 2023-09-06 17:35:56 +02:00
Anonymous Contributor f9453d5dec Switch to INI-based configuration 2023-09-06 17:34:32 +02:00
Anonymous Contributor 598e55dd6f Fix text overflow in minimized navbar 2023-09-06 16:07:15 +02:00
Anonymous Contributor 7be78e387d Use Ubuntu font globally 2023-09-06 16:00:33 +02:00
Anonymous Contributor 79959cc696 Remove unused styles from login CSS 2023-09-06 15:46:02 +02:00
Anonymous Contributor 01eb373798 Remove unused classes from login's util.css 2023-09-06 01:56:24 +02:00
Anonymous Contributor 27fe2187b4 Improve font-family declarations in login CSS 2023-09-06 01:55:41 +02:00
Anonymous Contributor c7bf6f4d6e Fix minor errors in template HTML 2023-09-05 22:25:08 +02:00
Anonymous Contributor 3bc2fee80a Fix class name of reset-password handler 2023-09-05 22:23:56 +02:00
Anonymous Contributor 2e3124406a Adapt DiscordUtil to directory structure change 2023-09-05 22:00:18 +02:00
Anonymous Contributor ef9cb8831e Fix username not being included in profile page 2023-09-05 21:59:49 +02:00
Anonymous Contributor cbc065f3a4 Delete leftovers from old structure 2023-09-05 21:59:21 +02:00
Anonymous Contributor eca5aa0d92 Adapt templates to new asset dir structure 2023-09-05 21:59:03 +02:00
Anonymous Contributor 1ac6474194 Move other JS and data files to new dir structure 2023-09-05 20:51:41 +02:00
Anonymous Contributor a4b008f7ae Use Sass to compile one stylesheet per page type 2023-09-05 20:50:12 +02:00
Anonymous Contributor 6dae322a2a Add dep. management and Sass compilation with npm 2023-09-05 20:48:06 +02:00
Anonymous Contributor ea5c1ee4d7 Move Ubuntu font, only keeping one copy 2023-09-05 18:20:54 +02:00
Anonymous Contributor 16dcd2ab46 Move and update FontAwesome files 2023-09-05 18:19:26 +02:00
Anonymous Contributor 94d84ab935 Move favicon 2023-09-05 18:14:13 +02:00
Anonymous Contributor d33a89b355 Remove unused assets 2023-09-05 18:11:15 +02:00
Anonymous Contributor 9ebb54ce64 Change directory structure for images 2023-09-05 10:40:08 +02:00
Anonymous Contributor b68884f33c Implement API endpoints as RequestHandlers 2023-09-05 01:27:06 +02:00
Anonymous Contributor 6b96f8281c Adapt all templates to new templating system 2023-09-05 01:15:40 +02:00
Anonymous Contributor 1cc4746c6a Implement all existing pages as RequestHandlers 2023-09-05 01:12:48 +02:00
Anonymous Contributor 27899ce9c1 Fix/improve middleware classes 2023-09-05 01:09:59 +02:00
Anonymous Contributor 686e991266 Adapt existing classes to new structure and PSR-12 2023-09-05 01:09:01 +02:00
Anonymous Contributor cc240f47a2 Add templating system based on native PHP 2023-09-05 01:02:53 +02:00
Anonymous Contributor a80919fdce Add object-oriented bootstrap and request handling 2023-09-05 00:55:09 +02:00
Anonymous Contributor b163f4d764 Add middleware functionality 2023-08-29 13:55:12 +02:00
Anonymous Contributor 024a140609 Add simple PSR-4 autoloader 2023-08-29 13:53:32 +02:00
Anonymous Contributor 6c22684b2f Add optional API key for cron, rename entry point 2023-08-27 12:56:12 +02:00
Anonymous Contributor ea2fffa872 Add account and identity deletion to admin 2023-08-27 12:20:45 +02:00
Anonymous Contributor d6ca2e6e00 Move identity deletion into OpenSim function 2023-08-27 12:17:33 +02:00
Anonymous Contributor c5871a626b Add distinction of users and identities in admin 2023-08-27 11:59:43 +02:00
Anonymous Contributor 007e0ac4fb Add self-service user account deletion 2023-08-27 11:20:50 +02:00
Anonymous Contributor 48882cbb1b Add feature for deleting own identities 2023-08-27 08:32:40 +02:00
Anonymous Contributor 120fb3772e Fix formatting according to PSR-12 2023-08-27 05:31:32 +02:00
Anonymous Contributor 9a5182816f Add user-friendly invite code error messages 2023-08-23 18:16:36 +02:00
Anonymous Contributor bfeb4f5eef Fix potential access to empty region stats array 2023-08-23 18:16:36 +02:00
Anonymous Contributor 6f150ac55c Fix session variable assignment in login 2023-08-23 18:16:36 +02:00
Anonymous Contributor fb605bcf4a Fix typo 2023-08-23 18:16:36 +02:00
Anonymous Contributor 3b35e88a9f Fix includes in OfflineIM 2023-08-23 18:16:36 +02:00
Anonymous Contributor f19e186cd3 Let admins generate password reset links 2023-08-23 18:16:36 +02:00
Anonymous Contributor 0991d5a487 Improve login security and efficiency 2023-08-23 18:16:36 +02:00
Anonymous Contributor eda4c5a030 Finalize "forgot password" feature 2023-08-23 18:16:36 +02:00
Anonymous Contributor 1ee795a399 Improve routing 2023-08-23 18:16:36 +02:00
Anonymous Contributor 88e9c25bb0 Fix "forgot password" link 2023-08-23 18:16:36 +02:00
Anonymous Contributor 82157cad76 Add e-mail-based password reset feature 2023-08-23 18:16:36 +02:00
Anonymous Contributor 406cdcce31 Update PHPMailer 2023-08-23 18:16:36 +02:00
Anonymous Contributor 94e8b2e6af Improve login form error messages 2023-08-23 18:16:36 +02:00
Anonymous Contributor b400f0c4e5 Fix erroneous SQL queries (ignored pre-PHP 8) 2023-08-23 18:16:36 +02:00
Anonymous Contributor 463ab4abe0 Prevent invite codes being reusable in some cases 2023-08-23 18:16:36 +02:00
Anonymous Contributor 0140cf1a57 Revert password hashing for OpenSim compatibility 2023-08-23 18:16:36 +02:00
Anonymous Contributor 954794870e Enforce POSt for region removal, validate input 2023-08-23 18:16:36 +02:00
Anonymous Contributor 16affffa66 Display correct title on friends page 2023-08-23 18:16:36 +02:00
Anonymous Contributor 2670cf604e Change validation regexes to be more strict 2023-08-23 18:16:36 +02:00
Anonymous Contributor 879b1d8e3f Fix profile form validation 2023-08-23 18:16:36 +02:00
Anonymous Contributor 0a79d465ac Add include path to cron 2023-08-23 18:16:36 +02:00
Anonymous Contributor 0ecd9aed75 Optimize user group query 2023-08-23 18:16:36 +02:00
Anonymous Contributor 391d716200 Fix include path 2023-08-23 18:16:36 +02:00
Anonymous Contributor bf07367b0c Fix friend entries having GET removal links in some cases 2023-08-23 18:16:36 +02:00
Anonymous Contributor 5d6b6565cd Add minimum password length requirement 2023-08-23 18:16:36 +02:00
Anonymous Contributor 8946511d46 Fix viewer welcome page 2023-08-23 18:16:35 +02:00
Anonymous Contributor 143f8ef3ed Fix property references 2023-08-23 18:16:35 +02:00
Anonymous Contributor 29796584f7 Check if "required" parameter is set 2023-08-23 18:16:35 +02:00
Anonymous Contributor e036a009cb Adapt HTML to password form change 2023-08-23 18:16:35 +02:00
Anonymous Contributor 22bdf45b26 Fix profile page HTML 2023-08-23 18:16:35 +02:00
Anonymous Contributor 4dfb3d81c3 Enforce POST when sending register form 2023-08-23 18:16:35 +02:00
Anonymous Contributor d46835e8eb Check if new name is already taken 2023-08-23 18:16:35 +02:00
Anonymous Contributor 03f5cd489d Remove useless double check of input lengths 2023-08-23 18:16:35 +02:00
Anonymous Contributor 7b0539b96f Save IAR message state across requests 2023-08-23 18:16:35 +02:00
Anonymous Contributor 497dcb85a8 Remove unused allUsers variable and query 2023-08-23 18:16:35 +02:00
Anonymous Contributor c3106f4787 Actually merge profile and password change pages 2023-08-23 18:16:35 +02:00
Anonymous Contributor 4d20b7e2f3 Only check regex and equals when field is provided 2023-08-23 18:16:35 +02:00
Anonymous Contributor d70fd4357a Remove dead link from admin dashboard 2023-08-23 18:16:35 +02:00
Anonymous Contributor 6b88527f05 Properly check request method in login form 2023-08-23 18:16:35 +02:00
Anonymous Contributor 5d0a79f20f Fix password form regexes 2023-08-23 18:16:35 +02:00
Anonymous Contributor 3d0c156cb3 Fix OpenSim API being included too late in users 2023-08-23 18:16:35 +02:00
Anonymous Contributor 841f1707eb Enforce POST and validate input for profile forms 2023-08-23 18:16:35 +02:00
Anonymous Contributor f073fb621d Add validation regex for new identity's name 2023-08-23 18:16:35 +02:00
Anonymous Contributor e192d3fd04 Fix POST request handling in dashboard forms 2023-08-23 18:16:35 +02:00
Anonymous Contributor 1df2182bae Use POST for password changes, validate input 2023-08-23 18:16:35 +02:00
Anonymous Contributor c97c58e30d Fix HTML format 2023-08-23 18:16:35 +02:00
Anonymous Contributor e2795e99b9 Do not use PHP's error control operator 2023-08-23 18:16:35 +02:00
Anonymous Contributor 9954f31721 Fix typos 2023-08-23 18:16:35 +02:00
Anonymous Contributor 57ff06d418 Generate a random string as invite code 2023-08-23 18:16:35 +02:00
Anonymous Contributor 8c7a31d88a Use POST for user management, validate input 2023-08-23 18:16:35 +02:00
Anonymous Contributor 6ca8988128 Use POST for managing identities, validate input 2023-08-23 18:16:35 +02:00
Anonymous Contributor 9d760f7dc3 Use POST for leaving groups, validate input 2023-08-23 18:16:35 +02:00
Anonymous Contributor e6d51a0afb Use POST when removing friends, validate input 2023-08-23 18:16:35 +02:00
Anonymous Contributor 87c21a06eb Fix incorrect regex escaping 2023-08-23 18:16:35 +02:00
Anonymous Contributor 0a6b06fb29 Fix property reference 2023-08-23 18:16:35 +02:00
Anonymous Contributor 2e7abe5bd5 Remove unneeded file 2023-08-23 18:16:35 +02:00
Anonymous Contributor d3f3ca5779 Fix include/template paths 2023-08-23 18:16:35 +02:00
Anonymous Contributor 17fe6651c8 Reflect directory structure changes 2023-08-23 18:16:35 +02:00
Anonymous Contributor f9828aa110 Move templates to template directory 2023-08-23 18:16:35 +02:00
Anonymous Contributor 4415adb6e6 Reflect directory structure changes 2023-08-23 18:16:35 +02:00
Anonymous Contributor da225d7213 Do not include unused discord class 2023-08-23 18:16:35 +02:00
Anonymous Contributor 08f29758c0 Only include and construct OpenSim when needed 2023-08-23 18:16:35 +02:00
Anonymous Contributor c9cad23e2c Only include PHPMailer when needed 2023-08-23 18:16:35 +02:00
Anonymous Contributor 59c9e53f1e Rename PHPMailer directory 2023-08-23 18:16:35 +02:00
Anonymous Contributor 45a33c2b9f Remove unused GoogleAuthenticator class 2023-08-23 18:16:35 +02:00
Anonymous Contributor 11c8fa3471 Change dir structure of class files 2023-08-23 18:16:34 +02:00
2042 changed files with 7131 additions and 195094 deletions

3
.gitignore vendored
View File

@ -1 +1,2 @@
config.php
config.php
node_modules

View File

@ -1,2 +1,41 @@
# OpenSim-Gridverwaltung
# MCP: OpenSim-Gridverwaltung
Das MCP ist ein PHP-Webinterface für Benutzer und Administratoren von OpenSimulator-Grids. Es ermöglicht Benutzern die Registrierung (auf Einladung) und Verwaltung des eigenen OpenSimulator-Accounts im Self-Service-Verfahren. Administratoren können Accounts und Regionen einfacher verwalten.
## Installation
Voraussetzung ist, dass die Datenbankstruktur eines OpenSimulator-Grids bereits existiert. Das MCP muss vor der Nutzung mit den Zugangsdaten derselben Datenbank konfiguriert werden. Eigene Tabellen des MCP besitzen zur Vermeidung von Konflikten den Präfix `mcp_`.
Folgende PHP-Erweiterungen werden benötigt:
1. php-curl
2. php-mysql (PDO)
3. php-xml
4. php-xmlrpc
Für bessere Performance kann optional `php-apcu` installiert werden.
Die Installation läuft folgendermaßen ab:
1. Gewünschtes Release als ZIP/TAR-Archiv oder per `git clone` herunterladen
2. Verzeichnisse `app`, `data`, `lib`, `public` und `templates` in das Verzeichnis des Webservers entpacken
3. Öffentliche Stammverzeichnis des Webservers (Apache: `DocumentRoot`, nginx: `root`) auf Pfad zum Verzeichnis `public` ändern
4. Index des Webservers auf index.php ändern, falls erforderlich
5. Beispielkonfiguration `config.example.ini` anpassen, in `config.ini` umbenennen und in das Verzeichnis der in Schritt 2 entpackten Verzeichnisse verschieben
## Aktualisierung
Zur Aktualisierung müssen lediglich die Verzeichnisse `app`, `lib`, `public` und `templates`, sowie der Inhalt von `data` (außer `iars`) ersetzt werden.
Die Migration der Datenbankstruktur erfolgt automatisch. Möglicherweise erforderliche Änderungen an der Konfiguration sind den Release-Informationen zu entnehmen.
## Entwickler
Die Abhängigkeiten des Frontends werden über [npm](https://www.npmjs.com/) verwaltet. Alle erforderlichen Pakete können nach dem Download des Repository über `npm install` heruntergeladen werden.
In `package.json` ist zudem ein Buildprozess (Befehl: `npm build`) definiert, durch den die Sass-Stylesheets im Verzeichnis `scss` kompiliert und optimiert und zusammen mit Schriftarten und Skripts im öffentlichen Webverzeichnis abgelegt werden.
Das Backend besitzt kein Dependency Management. Die einzige Abhängigkeit, [PHPMailer](https://github.com/PHPMailer/PHPMailer), wurde manuell in das Repository eingefügt. Der Autoloader sucht im Verzeichnis `lib` nach solchen externen Klassen.
### Verarbeitung von Anfragen
Das Skript `index.php` wird bei allen Anfragen aufgerufen. Die angeforderte Route wird als GET-Parameter `<Gruppe>=<Name>` übermittelt. Gruppen (momentan `api` und `page`), zugehörige Seiten und die assoziierte `RequestHandler`-Subklasse sind in `Mcp.php` definiert.
Ist zu einer Anfrage eine Route definiert, wird der zugehörige `RequestHandler` erzeugt. Ist eine `Middleware`-Klasse für diesen definiert, ist die weitere Verarbeitung von dem Rückgabewert von `Middleware::canAccess()` abhängig.
Schließlich wird je nach Methode der Anfrage `RequestHandler::get()` bzw. `RequestHandler::post()` aufgerufen.

View File

@ -1 +0,0 @@
Deny from all

View File

@ -1,110 +0,0 @@
<?php
# Copyright (c)Melanie Thielker and Teravus Ovares (http://opensimulator.org/)
#
# 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.
#
# updated for Robust installations: BlueWall 2011
# further minor changes by justincc (http://justincc.org)
# Tables
$presence = "Presence";
# XMLRPC
$xmlrpc_server = xmlrpc_server_create();
xmlrpc_server_register_method($xmlrpc_server, "preflightBuyLandPrep", "buy_land_prep");
function validate_user($agent_id, $s_session_id)
{
$stmt = $RUNTIME['PDO']->prepare("SELECT UserID FROM Presence WHERE UserID=? AND SecureSessionID = ?");
$stmt->execute(array($agent_id, $s_session_id));
if($stmt->rowCount() == 0) {
return false;
}
$res = $stmt->fetch();
return $res['UserID'];
}
function buy_land_prep($method_name, $params, $app_data)
{
$confirmvalue = "";
$req = $params[0];
$agentid = $req['agentId'];
$sessionid = $req['secureSessionId'];
$amount = $req['currencyBuy'];
$billableArea = $req['billableArea'];
$ID = validate_user($agentid, $sessionid);
if($ID)
{
$membership_levels = array(
'levels' => array(
'id' => "00000000-0000-0000-0000-000000000000",
'description' => "some level"));
$landUse = array(
'upgrade' => False,
'action' => "".SYSURL."");
$currency = array(
'estimatedCost' => "200.00"); // 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));
header("Content-type: text/xml");
print $response_xml;
}
else
{
header("Content-type: text/xml");
$response_xml = xmlrpc_encode(array(
'success' => False,
'errorMessage' => "\n\nUnable to Authenticate\n\nClick URL for more info.",
'errorURI' => "".SYSURL.""));
print $response_xml;
}
return "";
}
$request_xml = file_get_contents('php://input');
xmlrpc_server_call_method($xmlrpc_server, $request_xml, '');
xmlrpc_server_destroy($xmlrpc_server);
?>

View File

@ -1,29 +0,0 @@
<?php
$membership_levels = array(
'levels' => array(
'id' => "00000000-0000-0000-0000-000000000000",
'description' => "some level"));
$landUse = array(
'upgrade' => False,
'action' => "");
$currency = array(
'estimatedCost' => "200.00"); // convert_to_real($amount));
$membership = array(
'upgrade' => False,
'action' => "",
'levels' => $membership_levels);
$response_xml = xmlrpc_encode(array(
'success' => True,
'currency' => $currency,
'membership' => $membership,
'landUse' => $landUse,
'currency' => $currency,
'confirm' => "200.00"));
header("Content-type: text/xml");
print $response_xml;
?>

View File

@ -1,5 +0,0 @@
1148b04d-7a93-19e9-b3c9-ea1cdeec38f7
1148b04d-7a93-29e9-b3c9-ea1cdeec38f7
1148b04d-7a93-39e9-b3c9-ea1cdeec38f7
1148b04d-7a93-49e9-b3c9-ea1cdeec38f7
1148b04d-7a93-59e9-b3c9-ea1cdeec38f7

View File

@ -1,34 +0,0 @@
<html>
<head>
<meta http-equiv="refresh" content="15">
</head>
<body style="background-image: url('./style/images/fabric-pattern.png')">
<?php
$statement = $RUNTIME['PDO']->prepare("SELECT UserID,RegionID FROM Presence WHERE RegionID != '00000000-0000-0000-0000-000000000000' ORDER BY RegionID ASC");
$statement->execute();
if($statement->rowCount() == 0)
{
echo "<h1>Es ist niemand online!</h1>";
}else{
echo '<table style="width:350px;margin-left:auto;margin-right:auto;margin-top:25px"><tr><th align="left" style="background-color: #FF8000;">Name</th><th align="left" style="background-color: #FF8000;">Region</th></tr>';
$entryColor = TRUE;
while($row = $statement->fetch())
{
if($entryColor == TRUE)
$entry = '<tr style="background-color: #F2F2F2;"><td>'.trim($RUNTIME['OPENSIM']->getUserName($row['UserID'])).'</td><td>'.$RUNTIME['OPENSIM']->getRegionName($row['RegionID']).'</td></tr>';
if($entryColor == FALSE)
$entry = '<tr style="background-color: #E6E6E6;"><td>'.trim($RUNTIME['OPENSIM']->getUserName($row['UserID'])).'</td><td>'.$RUNTIME['OPENSIM']->getRegionName($row['RegionID']).'</td></tr>';
echo $entry;
$entryColor = !$entryColor;
}
echo '</table>';
}
?>
</body>
</html>

View File

@ -1,34 +0,0 @@
<?php
$HTML = new HTML();
$HTML->setHTMLTitle("Spalsh");
$HTML->importHTML("style/viewerWelcomeSite/default.html");
$IMAGES = array();
if ($handle = opendir('./data/viewerWelcomeImages'))
{
while (false !== ($entry = readdir($handle)))
{
if ($entry != "." && $entry != "..")
{
$IMAGES = array_merge($IMAGES, array("./data/viewerWelcomeImages/".$entry));
}
}
closedir($handle);
}
shuffle($IMAGES);
$HTML->importSeitenInhalt("pages/HTML/viewerWelcomeImages.html");
$HTML->ReplaceSeitenInhalt("%%JSONIMAGEARRAY%%", json_encode($IMAGES));
$HTML->ReplaceSeitenInhalt("%%GRIDNAME%%", $RUNTIME['GRID']['NAME']);
$HTML->ReplaceSeitenInhalt("%%SHOWNEWS%%", $RUNTIME['GRID']['MAIN_NEWS']);
$HTML->ReplaceSeitenInhalt("%%SHOWSTATS%%", "Registrierte User: ".$RUNTIME['OPENSIM']->getUserCount()."<br>Regionen: ".$RUNTIME['OPENSIM']->getRegionCount()."<br>Aktuell Online: ".($RUNTIME['OPENSIM']->getOnlineCount()-1));
$HTML->build();
echo $HTML->ausgabe();
?>

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Mcp;
use PDO;
interface ConnectionProvider
{
public function db(): PDO;
}

40
app/FormValidator.php Normal file
View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Mcp;
class FormValidator {
private array $fieldValidation;
public function __construct(array $fieldValidation)
{
$this->fieldValidation = $fieldValidation;
}
public function isValid(array $req): bool
{
if (!isset($req['csrf']) || $req['csrf'] !== $_SESSION['csrf']) {
return false;
}
foreach ($this->fieldValidation as $field => $params) {
if (isset($req[$field]) && strlen(trim($req[$field])) > 0) {
if (isset($params['regex'])) {
if (!preg_match($params['regex'], $req[$field])) {
return false;
}
}
elseif (isset($params['equals']) && $params['equals'] !== $req[$field]) {
return false;
}
}
elseif (isset($params['required']) && $params['required']) {
return false;
}
}
return true;
}
}

149
app/Mcp.php Normal file
View File

@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
namespace Mcp;
use Exception;
use PDO;
class Mcp implements ConnectionProvider
{
private ?PDO $db = null;
private array $config;
private string $dataDir;
private string $templateDir;
const ROUTES = [
'api' => [
'economy' => 'Api\\Economy',
'economylandtool' => 'Api\\EconomyLandTool',
'economylandtool.php' => 'Api\\EconomyLandTool',
'getAccessList' => 'Api\\GetAccessList',
'onlineDisplay' => 'Api\\OnlineDisplay',
'viewerWelcomeSite' => 'Api\\ViewerWelcomePage',
'runCron' => 'Api\\CronStarter',
'downloadIar' => 'Api\\DownloadIar'
],
'page' => [
'dashboard' => 'Page\\Dashboard',
'forgot' => 'Page\\ForgotPassword',
'friends' => 'Page\\Friends',
'groups' => 'Page\\Groups',
'identities' => 'Page\\Identities',
'login' => 'Page\\Login',
'profile' => 'Page\\Profile',
'regions' => 'Page\\Regions',
'register' => 'Page\\Register',
'reset-password' => 'Page\\ResetPassword',
'user-online-state' => 'Page\\OnlineUsers',
'users' => 'Page\\ManageUsers'
]
];
public function __construct($basedir)
{
$this->templateDir = $basedir.DIRECTORY_SEPARATOR.'templates';
$this->dataDir = $basedir.DIRECTORY_SEPARATOR.'data';
$this->config = array();
try {
$config = parse_ini_file($basedir.DIRECTORY_SEPARATOR.'config.ini', true);
foreach ($config['general'] as $key => $val) {
$this->config[$key] = $val;
}
unset($config['general']);
$this->config = array_merge($config, $this->config);
} catch (Exception $e) {
error_log('Could not load config, aborting. Error: '.$e->getMessage());
http_response_code(503);
exit();
}
$migrate = new MigrationManager($basedir.DIRECTORY_SEPARATOR.'data'.DIRECTORY_SEPARATOR.'migrate_ver');
if (!$migrate->isMigrated() && !$migrate->migrate($this->db())) {
error_log('Migration to latest DB structure version failed, aborting.');
http_response_code(503);
exit();
}
}
/**
* Connects to the MySQL database (if not done already) and returns the connection.
*/
public function db(): PDO
{
if ($this->db == null) {
$this->db = new PDO('mysql:host='.$this->config['mysql']['host'].';dbname='.$this->config['mysql']['db'],
$this->config['mysql']['user'],
$this->config['mysql']['password']);
}
return $this->db;
}
/**
* Returns the value associated with the specified key in this config, as either a string, an integer or an array.
* Keys are lower-cased for compatibility reasons.
*
* If there is no entry with this key, an empty array is returned.
*/
public function config($key): string|array|int
{
$realKey = strtolower($key);
return isset($this->config[$realKey]) ? $this->config[$realKey] : array();
}
/**
* Returns a hidden form field with the current CSRF token set.
*/
public function csrfField(): string
{
return '<input type="hidden" name="csrf" value="'.(isset($_SESSION['csrf']) ? $_SESSION['csrf'] : '').'">';
}
/**
* Creates a TemplateBuilder instance for the specified template file, setting some basic variables.
*/
public function template($name): TemplateBuilder
{
return (new TemplateBuilder($this->templateDir, $name))->vars([
'domain' => $this->config['domain'],
'title' => 'MCP',
'admin' => isset($_SESSION['LEVEL']) && $_SESSION['LEVEL'] > 100
])->unsafeVar('csrf', $this->csrfField());
}
/**
* Returns the path of the data/ directory, mostly used for dynamically created assets.
*/
public function getDataDir(): string
{
return $this->dataDir;
}
public function handleRequest()
{
$reqClass = 'Mcp\\Page\\Error';
if (empty($_GET)) {
$reqClass = 'Mcp\\'.$this::ROUTES['page'][array_key_first($this::ROUTES['page'])];
} else {
if (isset($_GET['logout'])) {
session_start();
session_destroy();
header('Location: /');
return;
}
foreach ($this::ROUTES as $type => $routes) {
if (isset($_GET[$type])) {
if (strlen($_GET[$type]) <= 100 && preg_match('/^[0-9a-zA-Z-_.]+$/', $_GET[$type]) && isset($routes[$_GET[$type]])) {
$reqClass = 'Mcp\\'.$routes[$_GET[$type]];
}
break;
}
}
}
(new $reqClass($this))->handleRequest();
}
}

126
app/MigrationManager.php Normal file
View File

@ -0,0 +1,126 @@
<?php
declare(strict_types=1);
namespace Mcp;
use Exception;
use PDO;
class MigrationManager
{
private const INIT = [
'CREATE TABLE IF NOT EXISTS `mcp_user_identities` (`PrincipalID` CHAR(36) NOT NULL, `IdentityID` CHAR(36) NOT NULL, PRIMARY KEY (`PrincipalID`, `IdentityID`)) ENGINE=MyISAM CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci',
'CREATE TABLE IF NOT EXISTS `mcp_password_reset` (`PrincipalID` CHAR(36) NOT NULL, `Token` CHAR(32) NOT NULL, `RequestTime` BIGINT NOT NULL, PRIMARY KEY(`PrincipalID`), UNIQUE(`Token`)) ENGINE MyISAM DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci',
'CREATE TABLE IF NOT EXISTS `mcp_invites` (`InviteCode` CHAR(64) NOT NULL, PRIMARY KEY (`InviteCode`)) ENGINE InnoDB',
'CREATE TABLE IF NOT EXISTS `mcp_offlineim_send` (`id` int(6) NOT NULL DEFAULT 0) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci',
'CREATE TABLE IF NOT EXISTS `mcp_regions_info` (`regionID` CHAR(36) NOT NULL COLLATE utf8_unicode_ci, `RegionVersion` VARCHAR(128) NOT NULL DEFAULT "" COLLATE utf8_unicode_ci, `ProcMem` INT(11) NOT NULL, `Prims` INT(11) NOT NULL, `SimFPS` INT(11) NOT NULL, `PhyFPS` INT(11) NOT NULL, `OfflineTimer` INT(11) NOT NULL DEFAULT 0, PRIMARY KEY (`regionID`) USING BTREE) COLLATE=utf8_unicode_ci ENGINE=InnoDB',
'CREATE TABLE IF NOT EXISTS `mcp_cron_runs` (`Name` VARCHAR(50) NOT NULL, `LastRun` INT(11) UNSIGNED NOT NULL, PRIMARY KEY(`Name`)) ENGINE InnoDB',
'CREATE TABLE IF NOT EXISTS `mcp_iar_state` (`userID` CHAR(36) NOT NULL COLLATE utf8_unicode_ci, `filesize` BIGINT(20) NOT NULL DEFAULT 0, `iarfilename` VARCHAR(64) NOT NULL COLLATE utf8_unicode_ci, `state` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, `created` INT(11) UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`userID`) USING BTREE) COLLATE=utf8_unicode_ci ENGINE=InnoDB',
'CREATE TRIGGER IF NOT EXISTS del_id_trig AFTER DELETE ON UserAccounts FOR EACH ROW DELETE FROM mcp_user_identities WHERE mcp_user_identities.PrincipalID = OLD.PrincipalID OR mcp_user_identities.IdentityID = OLD.PrincipalID',
'CREATE TRIGGER IF NOT EXISTS del_pwres_trig AFTER DELETE ON UserAccounts FOR EACH ROW DELETE FROM mcp_password_reset WHERE mcp_password_reset.PrincipalID = OLD.PrincipalID'
];
private const MIGRATIONS = [
1 => [
'RENAME TABLE IF EXISTS UserIdentitys TO mcp_user_identities, PasswordResetTokens TO mcp_password_reset, InviteCodes TO mcp_invites, im_offline_send TO mcp_offlineim_send, regions_info TO mcp_regions_info',
'ALTER TABLE mcp_invites MODIFY COLUMN InviteCode CHAR(64) NOT NULL',
'ALTER TABLE mcp_regions_info MODIFY COLUMN regionID CHAR(36), MODIFY COLUMN ProcMem INT(11) UNSIGNED NOT NULL, MODIFY COLUMN Prims INT(11) UNSIGNED NOT NULL, MODIFY COLUMN SimFPS FLOAT NOT NULL, MODIFY COLUMN PhyFPS FLOAT NOT NULL, MODIFY COLUMN OfflineTimer BIGINT UNSIGNED NOT NULL DEFAULT 0',
'ALTER TABLE mcp_user_identities MODIFY COLUMN PrincipalID CHAR(36) COLLATE utf8mb3_general_ci, MODIFY COLUMN IdentityID CHAR(36) COLLATE utf8mb3_general_ci',
'CREATE TRIGGER IF NOT EXISTS del_id_trig AFTER DELETE ON UserAccounts FOR EACH ROW DELETE FROM mcp_user_identities WHERE mcp_user_identities.PrincipalID = OLD.PrincipalID OR mcp_user_identities.IdentityID = OLD.PrincipalID',
'CREATE TRIGGER IF NOT EXISTS del_pwres_trig AFTER DELETE ON UserAccounts FOR EACH ROW DELETE FROM mcp_password_reset WHERE mcp_password_reset.PrincipalID = OLD.PrincipalID'
],
2 => [
'CREATE TABLE IF NOT EXISTS `mcp_cron_runs` (`Name` VARCHAR(50) NOT NULL, `LastRun` INT(11) UNSIGNED NOT NULL, PRIMARY KEY(`Name`)) ENGINE InnoDB'
],
3 => [
'RENAME TABLE IF EXISTS iarstates TO mcp_iar_state',
'ALTER TABLE mcp_iar_state MODIFY COLUMN userID CHAR(36) NOT NULL COLLATE utf8_unicode_ci, DROP COLUMN running, ADD COLUMN `state` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 AFTER iarfilename, ADD COLUMN created INT(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `state`'
]
];
private const MIGRATE_VERSION_CURRENT = 4;
private int $migrateVersion;
private string $migrateVersionFile;
public function __construct(string $migrateVersionFile)
{
$this->migrateVersionFile = $migrateVersionFile;
$this->migrateVersion = 0;
$apcu = false;
if ($this->apcuAvailable()) {
$this->migrateVersion = apcu_exists('mcp_migrate_version') ? apcu_fetch('mcp_migrate_version') : 0;
$apcu = true;
}
if ($this->migrateVersion == 0 && file_exists($migrateVersionFile)) {
$migrateVer = file_get_contents($migrateVersionFile);
if (preg_match('/^\d+$/', $migrateVer)) {
$this->migrateVersion = intval($migrateVer);
if ($apcu) {
apcu_store('mcp_migrate_version', $this->migrateVersion);
}
}
}
}
public function isMigrated(): bool
{
return $this->migrateVersion == $this::MIGRATE_VERSION_CURRENT;
}
public function migrate(PDO $db): bool
{
if ($this->migrateVersion == 0) {
// MCP < v2.x.x might be initialized without migration data
$checkLegacy = $db->prepare('SHOW TABLES LIKE ?');
$checkLegacy->execute(['UserIdentitys']);
if ($checkLegacy->rowCount() > 0) {
$this->migrateVersion = 1;
}
else {
return $this->runStatements($db, $this::INIT) && $this->updateVersion();
}
}
for ($ver = $this->migrateVersion; $ver <= count($this::MIGRATIONS); $ver++) {
if (!isset($this::MIGRATIONS[$ver])) {
break;
}
if (!$this->runStatements($db, $this::MIGRATIONS[$ver])) {
return false;
}
}
$this->updateVersion();
return true;
}
private function updateVersion(): bool
{
file_put_contents($this->migrateVersionFile, $this::MIGRATE_VERSION_CURRENT);
if ($this->apcuAvailable()) {
apcu_store('mcp_migrate_version', $this::MIGRATE_VERSION_CURRENT);
}
return true;
}
private function runStatements(PDO $db, array $stmts): bool
{
try {
foreach ($stmts as $stmt) {
$db->exec($stmt);
}
return true;
} catch (Exception $e) {
error_log('Could not execute statements: '.$e->getMessage()."\n".$e->getTraceAsString());
return false;
}
}
private function apcuAvailable(): bool
{
return function_exists('apcu_fetch') && apcu_enabled();
}
}

280
app/OpenSim.php Normal file
View File

@ -0,0 +1,280 @@
<?php
declare(strict_types=1);
namespace Mcp;
use Exception;
use PDO;
class OpenSim
{
private PDO $pdo;
private bool $apcu;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
$this->apcu = function_exists('apcu_fetch') && apcu_enabled();
}
private function getUserNameFromGridData($userID, $table, $row): ?string
{
$statementGridUser = $this->pdo->prepare('SELECT '.$row.' FROM '.$table.' WHERE '.$row.' LIKE ?');
$statementGridUser->execute(array($userID.';%'));
while ($rowGridUser = $statementGridUser->fetch()) {
$userData = explode(";", $rowGridUser[$row]);
if (count($userData) >= 3) {
$dbUserID = $userData[0];
$dbUserName = $userData[2];
if ($dbUserID == $userID) {
if ($this->apcu) {
apcu_store('os_username_'.$userID, $dbUserName, 600);
}
return $dbUserName;
}
}
}
return null;
}
public function getUserName($userID): string
{
if ($userID == "00000000-0000-0000-0000-000000000000") {
return "Unknown User";
}
if ($this->apcu && apcu_exists('os_username_'.$userID)) {
return apcu_fetch('os_username_'.$userID);
}
$statementUser = $this->pdo->prepare('SELECT FirstName,LastName FROM UserAccounts WHERE PrincipalID = ?');
$statementUser->execute(array($userID));
if ($rowUser = $statementUser->fetch()) {
$name = $rowUser['FirstName'].' '.$rowUser['LastName'];
if ($this->apcu) {
apcu_store('os_username_'.$userID, $name, 300);
}
return $name;
}
$res = $this->getUserNameFromGridData($userID, 'GridUser', 'UserID');
if ($res == null) {
$res = $this->getUserNameFromGridData($userID, 'Friends', 'PrincipalID');
}
return $res == null ? "Unknown User" : $res;
}
public function getUserUUID($userName): ?string
{
$statementUser = $this->pdo->prepare('SELECT PrincipalID,FirstName,LastName FROM UserAccounts WHERE FirstName = ? AND LastName = ?');
$statementUser->execute(explode(' ', $userName));
if ($rowUser = $statementUser->fetch()) {
return $rowUser['PrincipalID'];
}
return null;
}
public function getRegionName($regionID): string
{
$statementRegion = $this->pdo->prepare("SELECT regionName FROM regions WHERE uuid = ?");
$statementRegion->execute(array($regionID));
if ($rowRegion = $statementRegion->fetch()) {
return $rowRegion['regionName'];
}
return "Unknown Region";
}
public function getPartner($userID): string
{
$statement = $this->pdo->prepare("SELECT profilePartner FROM userprofile WHERE useruuid = ?");
$statement->execute(array($userID));
while ($row = $statement->fetch()) {
if ($row['profilePartner'] != "00000000-0000-0000-0000-000000000000") {
return $row['profilePartner'];
}
}
return '';
}
public function allowOfflineIM($userID): string
{
$statement = $this->pdo->prepare("SELECT imviaemail FROM usersettings WHERE useruuid = ?");
$statement->execute(array($userID));
if ($row = $statement->fetch()) {
return strtoupper($row['imviaemail']);
}
return "FALSE";
}
public function getUserMail($userID): string
{
$statement = $this->pdo->prepare("SELECT Email FROM UserAccounts WHERE PrincipalID = ?");
$statement->execute(array($userID));
if ($row = $statement->fetch()) {
return $row['Email'];
}
return "";
}
private function getEntryCount($table): int
{
$statementCount = $this->pdo->prepare('SELECT COUNT(*) AS Count FROM '.$table);
$statementCount->execute();
if ($row = $statementCount->fetch()) {
return $row['Count'];
}
return 0;
}
public function getUserCount(): int
{
return $this->getEntryCount('UserAccounts');
}
public function getRegionCount(): int
{
return $this->getEntryCount('regions');
}
public function getOnlineCount(): int
{
return $this->getEntryCount('Presence');
}
public function deleteUser($uuid): bool
{
try {
$this->pdo->beginTransaction();
$statementAuth = $this->pdo->prepare('DELETE FROM auth WHERE UUID = ?');
$statementAuth->execute([$uuid]);
$statementAgentPrefs = $this->pdo->prepare('DELETE FROM AgentPrefs WHERE PrincipalID = ?');
$statementAgentPrefs->execute([$uuid]);
$statementAvatars = $this->pdo->prepare('DELETE FROM Avatars WHERE PrincipalID = ?');
$statementAvatars->execute([$uuid]);
$statementGridUser = $this->pdo->prepare('DELETE FROM GridUser WHERE UserID = ?');
$statementGridUser->execute([$uuid]);
$statementEstateUser = $this->pdo->prepare('DELETE FROM estate_users WHERE uuid = ?');
$statementEstateUser->execute([$uuid]);
$statementEstateBan = $this->pdo->prepare('DELETE FROM estateban WHERE bannedUUID = ?');
$statementEstateBan->execute([$uuid]);
$statementHgTraveling = $this->pdo->prepare('DELETE FROM hg_traveling_data WHERE UserID = ?');
$statementHgTraveling->execute([$uuid]);
$statementUserIdentitys = $this->pdo->prepare('DELETE FROM mcp_user_identities WHERE PrincipalID = ?');
$statementUserIdentitys->execute([$uuid]);
$statementFriends = $this->pdo->prepare('DELETE FROM Friends WHERE PrincipalID = ? OR Friend = ?');
$statementFriends->execute([$uuid, $uuid]);
$statementImOffline = $this->pdo->prepare('DELETE FROM im_offline WHERE PrincipalID = ?');
$statementImOffline->execute([$uuid]);
$statementInventoryFolders = $this->pdo->prepare('DELETE FROM inventoryfolders WHERE agentID = ?');
$statementInventoryFolders->execute([$uuid]);
$statementInventoryItems = $this->pdo->prepare('DELETE FROM inventoryitems WHERE avatarID = ?');
$statementInventoryItems->execute([$uuid]);
$statementGroupMembership = $this->pdo->prepare('DELETE FROM os_groups_membership WHERE PrincipalID = ?');
$statementGroupMembership->execute([$uuid]);
$statementGroupRoles = $this->pdo->prepare('DELETE FROM os_groups_rolemembership WHERE PrincipalID = ?');
$statementGroupRoles->execute([$uuid]);
$statementGroupRoles = $this->pdo->prepare('DELETE FROM Presence WHERE UserID = ?');
$statementGroupRoles->execute([$uuid]);
$statementMute = $this->pdo->prepare('DELETE FROM MuteList WHERE AgentID = ? OR MuteID = ?');
$statementMute->execute([$uuid, $uuid]);
$statementUserAccounts = $this->pdo->prepare('DELETE FROM UserAccounts WHERE PrincipalID = ?');
$statementUserAccounts->execute([$uuid]);
$statementUserData = $this->pdo->prepare('DELETE FROM userdata WHERE UserId = ?');
$statementUserData->execute([$uuid]);
$statementUserNotes = $this->pdo->prepare('DELETE FROM usernotes WHERE targetuuid = ?');
$statementUserNotes->execute([$uuid]);
$statementUserProfile = $this->pdo->prepare('DELETE FROM userprofile WHERE useruuid = ?');
$statementUserProfile->execute([$uuid]);
$statementUserSettings = $this->pdo->prepare('DELETE FROM usersettings WHERE useruuid = ?');
$statementUserSettings->execute([$uuid]);
$this->pdo->commit();
return true;
} catch (Exception $pdoException) {
$this->pdo->rollBack();
error_log('Could not delete account '.$uuid.': '.$pdoException->getMessage());
return false;
}
}
public function deleteIdentity($uuid, $identId): bool
{
$statementValidate = $this->pdo->prepare('SELECT 1 FROM mcp_user_identities WHERE PrincipalID = ? AND IdentityID = ?');
$statementValidate->execute([$uuid, $identId]);
if($statementValidate->fetch()) {
$statementDelete = $this->pdo->prepare('DELETE FROM UserAccounts WHERE PrincipalID = ?');
$statementDelete->execute([$identId]);
$statementUserProfile = $this->pdo->prepare('DELETE FROM userprofile WHERE useruuid = ?');
$statementUserProfile->execute([$identId]);
return true;
}
return false;
}
public function generateUuid(): string
{
return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
// 32 bits for "time_low"
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
// 16 bits for "time_mid"
mt_rand( 0, 0xffff ),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 4
mt_rand( 0, 0x0fff ) | 0x4000,
// 16 bits, 8 bits for "clk_seq_hi_res",
// 8 bits for "clk_seq_low",
// two most significant bits holds zero and one for variant DCE1.1
mt_rand( 0, 0x3fff ) | 0x8000,
// 48 bits for "node"
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
);
}
}

54
app/RequestHandler.php Normal file
View File

@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Mcp;
use Exception;
use Mcp\Middleware\Middleware;
abstract class RequestHandler
{
protected Mcp $app;
private ?Middleware $middleware;
public function __construct(Mcp $app, Middleware $mw = null)
{
$this->app = $app;
$this->middleware = $mw;
}
public function handleRequest(): void
{
if ($_SERVER['REQUEST_METHOD'] != 'GET' && $_SERVER['REQUEST_METHOD'] != 'POST') {
http_response_code(400);
exit();
}
if ($this->middleware != null) {
try {
if (!$this->middleware->canAccess()) {
$this->middleware->handleUnauthorized();
exit();
}
} catch (Exception $e) {
error_log("Middleware handling raised an exception: " + $e->getMessage());
http_response_code(500);
exit();
}
}
$_SERVER['REQUEST_METHOD'] == 'GET' ? $this->get() : $this->post();
}
public function get(): void
{
http_response_code(405);
}
public function post(): void
{
http_response_code(405);
}
}

78
app/TemplateBuilder.php Normal file
View File

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace Mcp;
use Mcp\Util\TemplateVarArray;
class TemplateBuilder
{
private string $basedir;
private string $name;
private ?string $parent = null;
private array $vars = [];
public function __construct(string $basedir, string $name)
{
$this->basedir = $basedir;
$this->name = $name;
}
/**
* Sets another template to be the "parent" of this one.
*
* The template specified in this TemplateBuilder's constructor will be included into the parent.
*/
public function parent(string $parent): TemplateBuilder
{
$this->parent = $parent;
return $this;
}
/**
* Sets multiple variables after escaping them.
*/
public function vars(array $vars): TemplateBuilder
{
foreach ($vars as $key => $val) {
$this->vars[$key] = htmlspecialchars(strval($val));
}
return $this;
}
/**
* Sets the specified variable for this template, after escaping it.
*/
public function var(string $key, string $val): TemplateBuilder
{
$this->vars[$key] = htmlspecialchars($val);
return $this;
}
/**
* Sets the specified variable for this template WITHOUT escaping it.
*
* User input included this way has to be manually sanitized before.
*/
public function unsafeVar(string $key, string $val): TemplateBuilder
{
$this->vars[$key] = $val;
return $this;
}
/**
* Displays the template(s) with the current set of variables.
*/
public function render(): void
{
$v = new TemplateVarArray($this->vars);
$basepath = $this->basedir.DIRECTORY_SEPARATOR;
if ($this->parent == null) {
require $basepath.$this->name;
} else {
$v['child-template'] = $basepath.$this->name;
require $basepath.$this->parent;
}
}
}

50
app/api/CronStarter.php Normal file
View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Mcp\Api;
use Mcp\RequestHandler;
class CronStarter extends RequestHandler
{
private const CRONJOBS_INTERNAL = ['SessionCleanup'];
public function get(): void
{
if ($this->app->config('cron-restriction') == 'key' && !(isset($_GET['key']) && hash_equals($this->app->config('cron-key'), $_GET['key']))) {
http_response_code(403);
return;
}
$cronJobs = array_merge($this->app->config('cronjobs'), $this::CRONJOBS_INTERNAL);
$cronStatement = $this->app->db()->prepare('SELECT Name,LastRun FROM mcp_cron_runs');
$cronStatement->execute();
$jobRuns = array();
while ($row = $cronStatement->fetch()) {
$jobRuns[$row['Name']] = $row['LastRun'];
}
$resArray = [];
$cronUpdateStatement = $this->app->db()->prepare('REPLACE INTO mcp_cron_runs(Name,LastRun) VALUES (?,?)');
foreach ($cronJobs as $jobName) {
$jobClass = "Mcp\\Cron\\".$jobName;
if (in_array($jobName, $cronJobs)) {
$job = (new $jobClass($this->app));
$now = time();
$nextRun = $job->getNextRun(isset($jobRuns[$jobName]) ? $jobRuns[$jobName] : $now - 60);
if ($now >= $nextRun && $job->run()) {
$cronUpdateStatement->execute([$jobName, time()]);
$resArray[$jobName] = ['result' => 'ok', 'nextRun' => $job->getNextRun(time())];
}
else {
$resArray[$jobName] = ['result' => 'failed'];
}
}
}
echo json_encode($resArray);
}
}

24
app/api/DownloadIar.php Normal file
View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Mcp\Api;
class DownloadIar extends \Mcp\RequestHandler
{
public function get(): void
{
if (preg_match('/^[a-f0-9]{32}$/', $_GET['id'])) {
$path = $this->app->getDataDir().DIRECTORY_SEPARATOR.'iars'.DIRECTORY_SEPARATOR.$_GET['id'].'.iar';
if (file_exists($path)) {
header('Content-Type: '.mime_content_type($path));
header('Content-Disposition: attachment; filename='.$_GET['id'].'.iar');
header('Content-Length: '.filesize($path));
readfile($path);
return;
}
}
http_response_code(404);
}
}

119
app/api/Economy.php Normal file
View File

@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
namespace Mcp\Api;
class Economy extends \Mcp\RequestHandler
{
private const SYSURL = ""; // ???
# Copyright (c)Melanie Thielker and Teravus Ovares (http://opensimulator.org/)
#
# 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.
#
# updated for Robust installations: BlueWall 2011
# further minor changes by justincc (http://justincc.org)
# Adapted to use PDO and the MCP API by a 4Creative contributor
public function handleRequest(): void
{
# XMLRPC
$xmlrpc_server = xmlrpc_server_create();
xmlrpc_server_register_method($xmlrpc_server, "preflightBuyLandPrep", function($method_name, $params, $app_data) {
$confirmvalue = "";
$req = $params[0];
$agentid = $req['agentId'];
$sessionid = $req['secureSessionId'];
$amount = $req['currencyBuy'];
$billableArea = $req['billableArea'];
$id = $this->validateUser($agentid, $sessionid);
if ($id) {
$membership_levels = array(
'levels' => array(
'id' => "00000000-0000-0000-0000-000000000000",
'description' => "some level"
)
);
$landUse = array(
'upgrade' => false,
'action' => "" . $this::SYSURL . ""
);
$currency = array(
'estimatedCost' => "200.00"
); // convert_to_real($amount));
$membership = array(
'upgrade' => false,
'action' => "" . $this::SYSURL . "",
'levels' => $membership_levels
);
$response_xml = xmlrpc_encode(array(
'success' => true,
'currency' => $currency,
'membership' => $membership,
'landUse' => $landUse,
'currency' => $currency,
'confirm' => $confirmvalue
));
header("Content-type: text/xml");
print $response_xml;
} else {
header("Content-type: text/xml");
$response_xml = xmlrpc_encode(array(
'success' => false,
'errorMessage' => "\n\nUnable to Authenticate\n\nClick URL for more info.",
'errorURI' => "" . $this::SYSURL . ""
));
print $response_xml;
}
return "";
});
$request_xml = file_get_contents('php://input');
xmlrpc_server_call_method($xmlrpc_server, $request_xml, '');
xmlrpc_server_destroy($xmlrpc_server);
}
private function validateUser($agent_id, $s_session_id)
{
$stmt = $this->app->db()->prepare("SELECT UserID FROM Presence WHERE UserID=? AND SecureSessionID = ?");
$stmt->execute(array($agent_id, $s_session_id));
if ($stmt->rowCount() == 0) {
return false;
}
$res = $stmt->fetch();
return $res['UserID'];
}
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Mcp\Api;
class EconomyLandTool extends \Mcp\RequestHandler
{
public function handleRequest(): void
{
$membership_levels = array(
'levels' => array(
'id' => "00000000-0000-0000-0000-000000000000",
'description' => "some level"));
$landUse = array(
'upgrade' => false,
'action' => "");
$currency = array(
'estimatedCost' => "200.00"); // convert_to_real($amount));
$membership = array(
'upgrade' => false,
'action' => "",
'levels' => $membership_levels);
$response_xml = xmlrpc_encode(array(
'success' => true,
'currency' => $currency,
'membership' => $membership,
'landUse' => $landUse,
'currency' => $currency,
'confirm' => "200.00"));
header("Content-type: text/xml");
print $response_xml;
}
}

14
app/api/GetAccessList.php Normal file
View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Mcp\Api;
class GetAccessList extends \Mcp\RequestHandler
{
public function handleRequest(): void
{
for ($i = 0; $i < 5; $i++) {
echo "1148b04d-7a93-29e9-b3c9-ea1cdeec38f7\n";
}
}
}

33
app/api/OnlineDisplay.php Normal file
View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Mcp\Api;
use \Mcp\OpenSim;
class OnlineDisplay extends \Mcp\RequestHandler
{
public function get(): void
{
$statement = $this->app->db()->prepare("SELECT UserID,RegionID FROM Presence WHERE RegionID != '00000000-0000-0000-0000-000000000000' ORDER BY RegionID ASC");
$statement->execute();
$tpl = $this->app->template('online-display.php');
if ($statement->rowCount() == 0) {
$tpl->unsafeVar('online-users', '<h1 style="text-align: center; margin-top: 60px">Es ist niemand online!</h1>');
} else {
$table = '<table style="width:350px;margin-left:auto;margin-right:auto;margin-top:25px"><tr><th align="left" style="background-color: #FF8000;">Name</th><th align="left" style="background-color: #FF8000;">Region</th></tr>';
$entryColor = true;
$opensim = new OpenSim($this->app->db());
while ($row = $statement->fetch()) {
$table = $table.'<tr style="background-color: '.($entryColor ? '#F2F2F2' : '#E6E6E6').';"><td>'.trim($opensim->getUserName($row['UserID'])).'</td><td>'.$opensim->getRegionName($row['RegionID']).'</td></tr>';
$entryColor = !$entryColor;
}
$tpl->unsafeVar('online-users', $table.'</table>');
}
$tpl->render();
}
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Mcp\Api;
use Mcp\OpenSim;
class ViewerWelcomePage extends \Mcp\RequestHandler
{
public function get(): void
{
$images = array();
if ($handle = opendir('./img/viewerWelcomeImages')) {
while (false !== ($entry = readdir($handle))) {
if ($entry != "." && $entry != "..") {
$images[] = "./img/viewerWelcomeImages/".$entry;
}
}
closedir($handle);
}
shuffle($images);
$opensim = new OpenSim($this->app->db());
$this->app->template('viewerWelcomeImages.php')->vars([
'title' => 'Splash',
'grid-name' => $this->app->config('grid')['name'],
'news' => $this->app->config('grid')['main-news']
])->unsafeVar('json-image-array', json_encode($images))
->unsafeVar('image-1', $images[0])->unsafeVar('image-2', $images[1])
->unsafeVar('stats', "Registrierte User: ".$opensim->getUserCount()."<br>Regionen: ".$opensim->getRegionCount()."<br>Aktuell Online: ".($opensim->getOnlineCount()-1))
->render();
}
}

27
app/autoload.php Normal file
View File

@ -0,0 +1,27 @@
<?php
namespace Mcp;
class Autoloader {
private string $appPath;
private string $libPath;
public function __construct($basedir)
{
$this->appPath = $basedir.DIRECTORY_SEPARATOR.'app'.DIRECTORY_SEPARATOR;
$this->libPath = $basedir.DIRECTORY_SEPARATOR.'lib'.DIRECTORY_SEPARATOR;
}
public function load($className) {
$parts = explode('\\', $className);
$len = count($parts);
$res = $parts[0] === 'Mcp' ? $this->appPath : $this->libPath;
for ($i = 1; $i < $len - 1; $i++) {
$res = $res.strtolower($parts[$i]).DIRECTORY_SEPARATOR;
}
require $res.$parts[$len - 1].'.php';
}
}

51
app/cron/AssetChecker.php Normal file
View File

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Mcp\Cron;
class AssetChecker extends CronJob
{
public function __construct(\Mcp\Mcp $app)
{
parent::__construct($app, Frequency::MONTHLY);
}
public function run(): bool
{
$statement = $this->app->db()->prepare("SELECT id,hash FROM fsassets ORDER BY create_time DESC");
$statement->execute();
$count = 0;
while ($row = $statement->fetch()) {
$fileNameParts = array();
$fileNameParts[0] = substr($row['hash'], 0, 2);
$fileNameParts[1] = substr($row['hash'], 2, 2);
$fileNameParts[2] = substr($row['hash'], 4, 2);
$fileNameParts[3] = substr($row['hash'], 6, 4);
$fileNameParts[4] = $row['hash'] . ".gz";
$fileNameParts['UUID'] = $row['id'];
$fileNameParts['FilePath'] = "/data/assets/base/" . $fileNameParts[0] . "/" . $fileNameParts[1] . "/" . $fileNameParts[2] . "/" . $fileNameParts[3] . "/" . $fileNameParts[4];
if (file_exists($fileNameParts['FilePath'])) {
$filesize = filesize($fileNameParts['FilePath']);
if ($filesize === false) {
continue;
}
} else {
$filesize = 0;
}
$fileNameParts['FileSize'] = $filesize;
$fileNameParts['Count'] = $count++;
if ($fileNameParts['FileSize'] == 0) {
$add = $this->app->db()->prepare('DELETE FROM fsassets WHERE hash = :fileHash');
$add->execute(['fileHash' => $row['hash']]);
}
}
return true;
}
}

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Mcp\Cron;
class CheckInventory extends CronJob
{
public function __construct(\Mcp\Mcp $app)
{
parent::__construct($app, Frequency::MONTHLY);
}
public function run(): bool
{
$invCheckStatement = $this->app->db()->prepare("UPDATE inventoryitems i SET i.inventoryName = concat('[DEFEKT] ', i.inventoryName)
WHERE i.assetID IN (
SELECT i.assetID FROM inventoryitems i WHERE
NOT EXISTS(
SELECT * FROM fsassets fs WHERE fs.id = i.assetID)
AND NOT i.inventoryName LIKE '[DEFEKT] %' AND i.assetType <> 24
)");
$invCheckStatement->execute();
return true;
}
}

52
app/cron/CronJob.php Normal file
View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Mcp\Cron;
use DateInterval;
use DateTime;
use Mcp\Mcp;
abstract class CronJob {
protected Mcp $app;
private Frequency $freq;
public function __construct(Mcp $app, Frequency $freq)
{
$this->app = $app;
$this->freq = $freq;
}
public function getNextRun(int $lastRun)
{
$prevDate = getdate($lastRun);
$res = new DateTime('@'.$lastRun);
switch($this->freq) {
case Frequency::EACH_MINUTE:
$res->add(DateInterval::createFromDateString('1 minute'));
break;
case Frequency::HOURLY:
$res->add(DateInterval::createFromDateString('1 hour'));
break;
case Frequency::DAILY:
$res->add(DateInterval::createFromDateString('1 day'));
$res->setTime(0, 0, 0);
break;
case Frequency::WEEKLY:
$res->add(DateInterval::createFromDateString('1 week'));
break;
case Frequency::MONTHLY:
$res->setDate($prevDate['year'] + ($prevDate['mon'] == 12 ? 1 : 0), $prevDate['mon'] == 12 ? 1 : $prevDate['mon'] + 1, 1);
break;
case Frequency::YEARLY:
$res->setDate($prevDate['year'] + 1, 1, 1);
break;
default: break;
}
return $res->getTimestamp();
}
abstract public function run(): bool;
}

14
app/cron/Frequency.php Normal file
View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Mcp\Cron;
enum Frequency
{
case YEARLY; // 01.01. of each year
case MONTHLY; // 1st of each month
case WEEKLY; // 1 week after last run
case DAILY; // Next day after last run, at 00:00
case HOURLY; // One hour after last run
case EACH_MINUTE; // One minute after last run
}

103
app/cron/IarMonitor.php Normal file
View File

@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
namespace Mcp\Cron;
use Mcp\OpenSim;
use Mcp\Opensim\RestConsole;
use Mcp\Util\Util;
class IarMonitor extends CronJob
{
private ?RestConsole $console = null;
private bool $consoleAvailable = true;
public function __construct(\Mcp\Mcp $app)
{
parent::__construct($app, Frequency::EACH_MINUTE);
}
public function run(): bool
{
$opensim = new OpenSim($this->app->db());
$dirPath = $this->app->getDataDir().DIRECTORY_SEPARATOR.'iars';
if (!is_dir($dirPath)) {
mkdir($dirPath);
}
$statement = $this->app->db()->prepare("SELECT userID,iarfilename,filesize,state FROM mcp_iar_state WHERE state < ?");
$statement->execute([2]);
if ($statement->rowCount() > 0) {
while ($row = $statement->fetch()) {
if ($row['state'] == 0) { // 0 - Request to OS pending
if ($this->console() === false) {
continue;
}
$name = explode(' ', $opensim->getUserName($row['userID']));
if ($this->console()->sendCommand('save iar '.$name[0].' '.$name[1].' /* password '.$this->app->config('iarfetcher')['os-iar-path'].$row['iarfilename'])) {
$statementUpdate = $this->app->db()->prepare('UPDATE mcp_iar_state SET state = ? WHERE userID = ?');
$statementUpdate->execute([1, $row['userID']]);
}
} elseif ($row['state'] == 1) { // 1 - IAR Creation in progress
$fullFilePath = $dirPath.DIRECTORY_SEPARATOR.$row['iarfilename'];
if (file_exists($fullFilePath)) {
$filesize = filesize($fullFilePath);
if ($filesize != $row['filesize']) {
$statementUpdate = $this->app->db()->prepare('UPDATE mcp_iar_state SET filesize = ? WHERE userID = ?');
$statementUpdate->execute([$filesize, $row['userID']]);
} else {
$statementUpdate = $this->app->db()->prepare('UPDATE mcp_iar_state SET state = ?, created = ? WHERE userID = ?');
$statementUpdate->execute([2, time(), $row['userID']]);
Util::sendInworldIM("00000000-0000-0000-0000-000000000000", $row['userID'], "Inventory", $this->app->config('grid')['homeurl'], "Deine IAR ist fertig zum Download: https://".$this->app->config('domain').'/index.php?api=downloadIar&id='.substr($row['iarfilename'], 0, strlen($row['iarfilename']) - 4).' , sie ist mit dem Passwort "password" geschützt.');
}
}
}
}
if ($this->console != null) {
$this->console->closeSession();
}
}
// 2 - IAR creation finished; delete if expired
$weekOld = time() - 604800;
$statementExpired = $this->app->db()->prepare('SELECT userID,iarfilename FROM mcp_iar_state WHERE state = ? AND created < ?');
$statementExpired->execute([2, $weekOld]);
$statementDeleteExpired = $this->app->db()->prepare('DELETE FROM mcp_iar_state WHERE state = ? AND userID = ?');
while ($row = $statementExpired->fetch()) {
$fullFilePath = $dirPath.DIRECTORY_SEPARATOR.$row['iarfilename'];
if (file_exists($fullFilePath) && unlink($fullFilePath)) {
$statementDeleteExpired->execute([2, $row['userID']]);
}
}
return true;
}
private function console(): RestConsole|bool
{
if (!$this->consoleAvailable) {
return false;
}
if ($this->console == null) {
$restCfg = $this->app->config('iarfetcher');
$console = new RestConsole($restCfg['host'], intval($restCfg['port']));
if ($console->startSession($restCfg['user'], $restCfg['password'])) {
$this->console = $console;
}
else {
$this->consoleAvailable = false;
return false;
}
}
return $this->console;
}
}

89
app/cron/OfflineIm.php Normal file
View File

@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace Mcp\Cron;
use Mcp\OpenSim;
use Mcp\Util\SmtpClient;
use SimpleXMLElement;
class OfflineIm extends CronJob
{
private const IM_TYPE = array(
"0" => "eine Nachricht",
"3" => "eine Gruppeneinladung",
"4" => "ein Inventaritem",
"5" => "eine Bestätigung zur Annahme von Inventar",
"6" => "eine Information zur Ablehnung von Inventar",
"7" => "eine Aufforderung zur Gruppenwahl",
"9" => "ein Inventaritem von einem Script",
"19" => "eine Nachricht von einem Script",
"32" => "eine Gruppennachricht",
"38" => "eine Freundschaftsanfrage",
"39" => "eine Bestätigung über die Annahme der Freundschaft",
"40" => "eine Information über das Ablehnen der Freundschaft"
);
public function __construct(\Mcp\Mcp $app)
{
parent::__construct($app, Frequency::EACH_MINUTE);
}
public function run(): bool
{
$statement = $this->app->db()->prepare("SELECT ID,PrincipalID,Message FROM im_offline");
$statement->execute();
while ($row = $statement->fetch()) {
$opensim = new OpenSim($this->app->db());
$email = $opensim->getUserMail($row['PrincipalID']);
$allowOfflineIM = $opensim->allowOfflineIM($row['PrincipalID']);
if ($email != "" && $allowOfflineIM == "TRUE" && !$this->isMailAlreadySent($row['ID'])) {
$statementSend = $this->app->db()->prepare('INSERT INTO mcp_offlineim_send (id) VALUES (:idnummer)');
$statementSend->execute(['idnummer' => $row['ID']]);
$mailcfg = $this->app->config('smtp');
$smtpClient = new SmtpClient($mailcfg['host'], intval($mailcfg['port']), $mailcfg['address'], $mailcfg['password']);
$xmlMessage = new SimpleXMLElement($row['Message']);
$imType = $this::IM_TYPE["" . $xmlMessage->dialog . ""];
$htmlMessage = "Du hast " . $imType . " in " . $this->app->config('grid')['name'] . " bekommen. <br><p><ul><li>" . htmlspecialchars($xmlMessage->message) . "</li></ul></p>Gesendet von: ";
if (isset($xmlMessage->fromAgentName)) {
$htmlMessage .= $xmlMessage->fromAgentName;
}
if (isset($xmlMessage->RegionID) && isset($xmlMessage->Position)) {
if ($xmlMessage->Position->X != 0 || $xmlMessage->Position->Y != 0 || $xmlMessage->Position->Z != 0) { //TODO
$htmlMessage .= " @ " . $opensim->getRegionName($xmlMessage->RegionID) . "/" . $xmlMessage->Position->X . "/" . $xmlMessage->Position->Y . "/" . $xmlMessage->Position->Z;
} else {
$htmlMessage .= " @ " . $opensim->getRegionName($xmlMessage->RegionID);
}
}
$tpl = $this->app->template('mail.php')->vars([
'title' => substr($imType, strpos($imType, ' '))
])->unsafeVar('message', $htmlMessage);
$smtpClient->sendHtml($mailcfg['address'], $mailcfg['name'], $email, "Du hast " . $imType . " in " . $this->app->config('grid')['name'] . ".", $tpl);
}
}
return true;
}
private function isMailAlreadySent($id): bool
{
$statement = $this->app->db()->prepare("SELECT 1 FROM mcp_offlineim_send WHERE id = ? LIMIT 1");
$statement->execute(array($id));
if ($statement->rowCount() != 0) {
return true;
}
return false;
}
}

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Mcp\Cron;
use Mcp\OpenSim;
use Mcp\Util\Util;
class RegionChecker extends CronJob
{
public function __construct(\Mcp\Mcp $app)
{
parent::__construct($app, Frequency::DAILY);
}
public function run(): bool
{
$statement = $this->app->db()->prepare("SELECT uuid,regionName,owner_uuid,serverURI,OfflineTimer FROM regions LEFT JOIN mcp_regions_info ON regions.uuid = mcp_regions_info.regionID COLLATE utf8mb3_unicode_ci");
$statement->execute();
while ($row = $statement->fetch()) {
$curl = curl_init($row['serverURI'] . 'jsonSimStats');
curl_setopt($curl, CURLOPT_TIMEOUT, 15);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($curl);
if ($result === false || strlen($result) == 0) {
if ($row['OfflineTimer'] == null) {
continue;
}
$opensim = new OpenSim($this->app->db());
$longOffline = ($row['OfflineTimer'] + 3600) <= time();
echo "Die Region " . $row['regionName'] . " von " . $opensim->getUserName($row['owner_uuid']) . " ist " . ($longOffline ? "seit über einer Stunde" : "") . " nicht erreichbar.\n"; //TODO: Increase to 1-3 months
if ($longOffline) {
if ($this->app->config('grid')['delete-inactive-regions']) {
Util::sendInworldIM("00000000-0000-0000-0000-000000000000", $row['owner_uuid'], "Region", $this->app->config('grid')['homeurl'], "WARNUNG: Deine Region '" . $row['regionName'] . "' ist nicht erreichbar und wurde deshalb aus dem Grid entfernt.");
$statementUpdate = $this->app->db()->prepare('DELETE FROM regions WHERE uuid = :uuid');
$statementUpdate->execute(['uuid' => $row['uuid']]);
}
$statementRemoveStats = $this->app->db()->prepare('DELETE FROM mcp_regions_info WHERE regionID = :uuid');
$statementRemoveStats->execute(['uuid' => $row['uuid']]);
} elseif ($this->app->config('grid')['warn-inactive-regions']) {
Util::sendInworldIM("00000000-0000-0000-0000-000000000000", $row['owner_uuid'], "Region", $this->app->config('grid')['homeurl'], "WARNUNG: Deine Region '" . $row['regionName'] . "' ist nicht erreichbar!");
}
} else {
$regionData = json_decode($result);
$statementAccounts = $this->app->db()->prepare('REPLACE INTO `mcp_regions_info` (`regionID`, `RegionVersion`, `ProcMem`, `Prims`, `SimFPS`, `PhyFPS`, `OfflineTimer`) VALUES (:regionID, :RegionVersion, :ProcMem, :Prims, :SimFPS, :PhyFPS, :OfflineTimer)');
$statementAccounts->execute(['regionID' => $row['uuid'], 'RegionVersion' => $regionData->Version, 'ProcMem' => intval(str_replace(',', '', $regionData->ProcMem)), 'Prims' => $regionData->Prims, 'SimFPS' => $regionData->SimFPS, 'PhyFPS' => $regionData->PhyFPS, 'OfflineTimer' => time()]);
}
}
return true;
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Mcp\Cron;
class SessionCleanup extends CronJob
{
public function __construct(\Mcp\Mcp $app)
{
parent::__construct($app, Frequency::HOURLY);
}
public function run(): bool
{
error_log("Session GC");
session_start();
session_gc();
return true;
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Mcp\Middleware;
class AdminMiddleware extends LoginRequiredMiddleware
{
public function canAccess(): bool
{
if (parent::canAccess()) {
return $_SESSION['LEVEL'] > 100;
}
return false;
}
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Mcp\Middleware;
use Mcp\ConnectionProvider;
class LoginRequiredMiddleware extends SessionMiddleware
{
private ConnectionProvider $connProvider;
public function __construct(ConnectionProvider $connProvider, string $cookieDomain)
{
parent::__construct($cookieDomain, 3600);
$this->connProvider = $connProvider;
}
public function canAccess(): bool
{
parent::handleSession();
if (isset($_SESSION['UUID'])) {
// User level or existence of account may have changed since session was created
$getLevel = $this->connProvider->db()->prepare('SELECT UserLevel FROM UserAccounts WHERE PrincipalID = ?');
$getLevel->execute([$_SESSION['UUID']]);
if ($row = $getLevel->fetch()) {
$_SESSION['LEVEL'] = $row['UserLevel'];
return true;
}
else {
session_unset();
session_destroy();
return false;
}
}
return false;
}
public function handleUnauthorized(): void
{
header('Location: index.php?page=login');
}
}

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Mcp\Middleware;
/**
* Middleware implementations can be registered for a RequestHandler.
*
* If this is done, request processing continues only if Middleware::canAccess() returns true.
*/
interface Middleware
{
/**
* Returns true if the request should be processed, i.e. if the client has permissionn to perform this request.
*/
public function canAccess(): bool;
/**
* Called when canAcces() returns false, e.g. to redirect unauthorized users.
*/
public function handleUnauthorized(): void;
}

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Mcp\Middleware;
class PreSessionMiddleware extends SessionMiddleware
{
public function __construct(string $cookieDomain)
{
parent::__construct($cookieDomain, 0);
}
public function canAccess(): bool
{
parent::handleSession();
return !isset($_SESSION['LOGIN']);
}
public function handleUnauthorized(): void
{
header('Location: index.php');
}
}

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Mcp\Middleware;
use UnexpectedValueException;
abstract class SessionMiddleware implements Middleware
{
private string $cookieDomain;
private int $cookieLifetime;
public function __construct(string $cookieDomain, int $cookieLifetime)
{
$this->cookieDomain = $cookieDomain;
$this->cookieLifetime = $cookieLifetime;
}
protected function handleSession(): void
{
switch(session_status()) {
case PHP_SESSION_DISABLED:
throw new UnexpectedValueException("Session functionality is disabled");
break;
case PHP_SESSION_NONE:
session_set_cookie_params([
'lifetime' => $this->cookieLifetime,
'path' => '/',
'domain' => $this->cookieDomain,
'httponly' => true,
'secure' => true,
'samesite' => 'Strict'
]);
session_start();
break;
default:
break;
}
if(!isset($_SESSION['csrf']) || !preg_match('/^[0-9a-f]{64}$/', $_SESSION['csrf'])) {
$_SESSION['csrf'] = bin2hex(random_bytes(32));
}
}
}

133
app/opensim/RestConsole.php Normal file
View File

@ -0,0 +1,133 @@
<?php
declare(strict_types=1);
namespace Mcp\Opensim;
use XMLReader;
class RestConsole
{
private string $baseUrl;
private ?string $sessionId;
public function __construct(string $host, int $port)
{
$this->baseUrl = "http://".$host.':'.$port;
}
public function startSession(string $username, string $password): bool
{
$response = $this->sendRequest('/StartSession/', [
'USER' => $username,
'PASS' => $password
]);
if ($this->detectError($response, '/StartSession/')) {
return false;
}
$this->sessionId = $response['SessionID'];
$this->readResponses();
return true;
}
public function closeSession(): void
{
$response = $this->sendRequest('/CloseSession/', [
'ID' => $this->sessionId
]);
$this->detectError($response, '/CloseSession/');
}
public function readResponses(): array
{
if ($this->sessionId == null) {
return array();
}
$response = $this->sendRequest('/ReadResponses/'.$this->sessionId);
if ($this->detectError($response, '/ReadResponses/')) {
return array();
}
return $response['Line'];
}
public function sendCommand(string $command): bool
{
$response = $this->sendRequest('/SessionCommand/', [
'ID' => $this->sessionId,
'COMMAND' => $command
]);
if ($this->detectError($response, '/SessionCommand/')) {
return false;
}
return $response['Result'] == 'OK';
}
private function detectError(array|int $response, string $request): bool
{
if (gettype($response) == 'integer') {
error_log('OS RestConsole request '.$this->baseUrl.$request.' failed, status: '.$response);
return true;
}
return false;
}
private function sendRequest(string $request, array $data = array()): array|int
{
$curl = curl_init($this->baseUrl.$request);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_USERAGENT, 'mcp-restconsole/0.0.1');
curl_setopt($curl, CURLOPT_HTTPHEADER, [
'Accept' => 'text/xml'
]);
$postData = '';
foreach ($data as $key => $val) {
$postData = $postData.(strlen($postData) > 0 ? '&' : '').$key.'='.$val;
}
curl_setopt($curl, CURLOPT_POSTFIELDS, $postData);
$res = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($status != 200) {
return $status;
}
return $this->parseXml($res);
}
private function parseXml(string $response): array
{
$xmlReader = XMLReader::XML($response, "UTF-8");
$res = array();
if ($xmlReader->next()) {
$consoleSession = $xmlReader->expand();
if ($consoleSession->nodeName == 'ConsoleSession') {
foreach ($consoleSession->childNodes as $childNode) {
$name = $childNode->nodeName;
if (isset($res[$name])) {
if (gettype($res[$name]) == 'string') {
$res[$name] = array($res[$name], $childNode->nodeValue);
}
else {
$res[$name][] = $childNode->nodeValue;
}
}
else {
$res[$name] = $childNode->nodeValue;
}
}
}
}
return $res;
}
}

26
app/page/Dashboard.php Normal file
View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Mcp\Page;
use Mcp\OpenSim;
use Mcp\Middleware\LoginRequiredMiddleware;
class Dashboard extends \Mcp\RequestHandler
{
public function __construct(\Mcp\Mcp $app)
{
parent::__construct($app, new LoginRequiredMiddleware($app, $app->config('domain')));
}
public function get(): void
{
$opensim = new OpenSim($this->app->db());
$this->app->template('dashboard-home.php')->parent('__dashboard.php')->vars([
'title' => 'Dashboard',
'username' => $_SESSION['DISPLAYNAME'],
'global-user-count' => $opensim->getUserCount(),
'global-region-count' => $opensim->getRegionCount()
])->render();
}
}

15
app/page/Error.php Normal file
View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Mcp\Page;
use Mcp\Util\Util;
class Error extends \Mcp\RequestHandler
{
public function get(): void
{
http_response_code(404);
Util::displayError($this->app, 'Die gewünschte Seite wurde nicht gefunden.');
}
}

View File

@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace Mcp\Page;
use Mcp\FormValidator;
use Mcp\Middleware\PreSessionMiddleware;
use Mcp\Util\SmtpClient;
use Mcp\Util\Util;
class ForgotPassword extends \Mcp\RequestHandler
{
const MESSAGE = 'Hallo %%NAME%%,<br/><br/>wir haben soeben eine Anfrage zur Zurücksetzung des Passworts für deinen 4Creative-Account erhalten.<br/><br/>Klicke <a href="%%RESET_LINK%%">hier</a>, um ein neues Passwort festzulegen. Dieser Link läuft in 24 Stunden ab.<br/><br/>Falls du diese Anfrage nicht gesendet hast, ignoriere sie einfach. Bei weiteren Fragen kannst du uns unter info@4creative.net oder über unseren Discord-Server erreichen.';
public function __construct(\Mcp\Mcp $app)
{
parent::__construct($app, new PreSessionMiddleware($app->config('domain')));
}
public function get(): void
{
$this->app->template('forgot.php')->parent('__presession.php')->vars([
'title' => 'Passwort vergessen',
'message-color' => 'red'
])->render();
}
public function post(): void
{
$validator = new FormValidator(array(
'username' => array('required' => true, 'regex' => '/^[^\\/<>\s]{1,64} [^\\/<>\s]{1,64}$/'),
'email' => array('required' => true, 'regex' => '/^\S{1,64}@\S{1,250}.\S{2,64}$/')
));
$tpl = $this->app->template('forgot.php')->parent('__presession.php')->var('title', 'Passwort vergessen');
if (!$validator->isValid($_POST)) {
$tpl->vars([
'message' => 'Bitte gebe deinen Benutzernamen (Vor- und Nachname) und die dazugehörige E-Mail-Adresse ein',
'message-color' => 'red'
])->render();
} else {
$nameParts = explode(" ", $_POST['username']);
$email = strtolower(trim($_POST['email']));
$getAccount = $this->app->db()->prepare('SELECT Email,FirstName,LastName,PrincipalID FROM UserAccounts WHERE FirstName = ? AND LastName = ? AND Email = ?');
$getAccount->execute([trim($nameParts[0]), trim($nameParts[1]), $email]);
$validRequest = $getAccount->rowCount() == 1;
$uuid = null;
$name = null;
if ($res = $getAccount->fetch()) {
$email = $res['Email'];
$uuid = $res['PrincipalID'];
$name = $res['FirstName'].' '.$res['LastName'];
}
foreach ($this->app->config('reset-blocked-domains') as $domain) {
if (str_ends_with($email, $domain)) {
$validRequest = false;
}
}
$tpl->vars([
'message' => 'Falls Name und E-Mail-Adresse bei uns registriert sind, erhältst du in Kürze eine E-Mail mit weiteren Informationen.',
'message-color' => 'green'
])->render();
fastcgi_finish_request();
if ($validRequest) {
$getReqTime = $this->app->db()->prepare('SELECT RequestTime FROM mcp_password_reset WHERE PrincipalID=?');
$getReqTime->execute([$uuid]);
if (($res = $getReqTime->fetch()) && time() - $res['RequestTime'] < 900) {
return;
}
$token = Util::generateToken(32);
$setToken = $this->app->db()->prepare('REPLACE INTO mcp_password_reset(PrincipalID,Token,RequestTime) VALUES(?,?,?)');
$setToken->execute([$uuid, $token, time()]);
$smtp = $this->app->config('smtp');
$tplMail = $this->app->template('mail.php')->vars([
'title' => 'Dein Passwort zurücksetzen',
'preheader' => 'So kannst du ein neues Passwort für deinen 4Creative-Account festlegen'
])->unsafeVar('message', str_replace('%%NAME%%', $name, str_replace('%%RESET_LINK%%', 'https://'.$this->app->config('domain').'/index.php?page=reset-password&token='.$token, $this::MESSAGE)));
(new SmtpClient($smtp['host'], intval($smtp['port']), $smtp['address'], $smtp['password']))->sendHtml($smtp['address'], $smtp['name'], $email, 'Zurücksetzung des Passworts für '.$name, $tplMail);
}
}
}
}

66
app/page/Friends.php Normal file
View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Mcp\Page;
use Mcp\FormValidator;
use Mcp\OpenSim;
use Mcp\Middleware\LoginRequiredMiddleware;
class Friends extends \Mcp\RequestHandler
{
public function __construct(\Mcp\Mcp $app)
{
parent::__construct($app, new LoginRequiredMiddleware($app, $app->config('domain')));
}
public function get(): void
{
$table = '<table class="table"><thead><tr><th scope="col">Name</th><th scope="col">Optionen</th></thead><tbody>';
$statement = $this->app->db()->prepare("SELECT PrincipalID,Friend FROM Friends WHERE PrincipalID = ? ORDER BY Friend ASC");
$statement->execute([$_SESSION['UUID']]);
$opensim = new OpenSim($this->app->db());
$csrf = $this->app->csrfField();
while ($row = $statement->fetch()) {
$friendData = explode(";", $row['Friend']);
$friend = $friendData[0];
$name = trim($opensim->getUserName($friend));
if (count($friendData) > 1) {
$friendData[1] = str_replace("http://", "", $friendData[1]);
$friendData[1] = str_replace("https://", "", $friendData[1]);
$friendData[1] = str_replace("/", "", $friendData[1]);
$name = $name.' @ '.strtolower($friendData[1]);
}
$table = $table.'<tr><td>'.htmlspecialchars($name).'</td><td><form action="index.php?page=friends" method="post">'.$csrf.'<input type="hidden" name="uuid" value="'.htmlspecialchars($row['Friend']).'"><button type="submit" name="remove" class="btn btn-danger btn-sm">LÖSCHEN</button></form></td></tr>';
}
$this->app->template('__dashboard.php')->vars([
'title' => 'Deine Freunde',
'username' => $_SESSION['DISPLAYNAME']
])->unsafeVar('child-content', $table.'</tbody></table>')->render();
}
public function post(): void
{
if (isset($_POST['remove'])) {
$validator = new FormValidator(array(
'uuid' => array('required' => true, 'regex' => '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/')
));
if ($validator->isValid($_POST)) {
$statementMembership = $this->app->db()->prepare("DELETE FROM Friends WHERE Friend = ? AND PrincipalID = ?");
$statementMembership->execute(array($_REQUEST['uuid'], $_SESSION['UUID']));
$statementMembership = $this->app->db()->prepare("DELETE FROM Friends WHERE PrincipalID = ? AND Friend = ?");
$statementMembership->execute(array($_REQUEST['uuid'], $_SESSION['UUID']));
}
}
header('Location: index.php?page=friends');
}
}

52
app/page/Groups.php Normal file
View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Mcp\Page;
use Mcp\FormValidator;
use Mcp\OpenSim;
use Mcp\Middleware\LoginRequiredMiddleware;
class Groups extends \Mcp\RequestHandler
{
public function __construct(\Mcp\Mcp $app)
{
parent::__construct($app, new LoginRequiredMiddleware($app, $app->config('domain')));
}
public function get(): void
{
$opensim = new OpenSim($this->app->db());
$table = '<table class="table"><thead><tr><th scope="col">Name</th><th scope="col">Gründer</th><th scope="col">Aktionen</th></thead><tbody>';
$statementGroups = $this->app->db()->prepare("SELECT Name,FounderID,os_groups_membership.GroupID FROM os_groups_groups JOIN os_groups_membership ON os_groups_groups.GroupID = os_groups_membership.GroupID WHERE PrincipalID = ?");
$statementGroups->execute(array($_SESSION['UUID']));
$csrf = $this->app->csrfField();
while ($rowGroups = $statementGroups->fetch()) {
$table = $table.'<tr><td>'.htmlspecialchars($rowGroups['Name']).'</td><td>'.htmlspecialchars($opensim->getUserName($rowGroups['FounderID'])).'</td><td><form action="index.php?page=groups" method="post">'.$csrf.'<input type="hidden" name="group" value="'.htmlspecialchars($rowGroups['GroupID']).'"><button type="submit" name="leave" class="btn btn-danger btn-sm">VERLASSEN</button></form></td></tr>';
}
$this->app->template('__dashboard.php')->vars([
'title' => 'Gruppen',
'username' => $_SESSION['DISPLAYNAME']
])->unsafeVar('child-content', $table.'</tbody></table>')->render();
}
public function post(): void
{
if (isset($_POST['leave'])) {
$validator = new FormValidator(array(
'group' => array('required' => true, 'regex' => '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/')
));
if ($validator->isValid($_POST)) {
$statementMembership = $this->app->db()->prepare("DELETE FROM os_groups_membership WHERE GroupID = ? AND PrincipalID = ?");
$statementMembership->execute(array($_REQUEST['group'], $_SESSION['UUID']));
}
}
header('Location: index.php?page=groups');
}
}

150
app/page/Identities.php Normal file
View File

@ -0,0 +1,150 @@
<?php
declare(strict_types=1);
namespace Mcp\Page;
use Mcp\FormValidator;
use Mcp\OpenSim;
use Mcp\Middleware\LoginRequiredMiddleware;
class Identities extends \Mcp\RequestHandler
{
public function __construct(\Mcp\Mcp $app)
{
parent::__construct($app, new LoginRequiredMiddleware($app, $app->config('domain')));
}
public function get(): void
{
$statementCheckForEntry = $this->app->db()->prepare("SELECT 1 FROM mcp_user_identities WHERE PrincipalID = ? LIMIT 1");
$statementCheckForEntry->execute(array($_SESSION['UUID']));
if ($statementCheckForEntry->rowCount() == 0) {
$statement = $this->app->db()->prepare('INSERT INTO `mcp_user_identities` (PrincipalID, IdentityID) VALUES (:PrincipalID, :IdentityID)');
$statement->execute(['PrincipalID' => $_SESSION['UUID'], 'IdentityID' => $_SESSION['UUID']]);
}
$table = '<table class="table"><thead><tr><th scope="col">Name</th><th scope="col">Aktionen</th></thead><tbody>';
$statement = $this->app->db()->prepare("SELECT IdentityID FROM mcp_user_identities WHERE PrincipalID = ? ORDER BY IdentityID ASC");
$statement->execute(array($_SESSION['UUID']));
$opensim = new OpenSim($this->app->db());
while ($row = $statement->fetch()) {
if ($row['IdentityID'] == $_SESSION['UUID']) {
$entry = '<tr><td>'.htmlspecialchars(trim($opensim->getUserName($row['IdentityID']))).' <span class="badge badge-info">Aktiv</span></td><td>-</td></tr>';
} else {
$entry = '<tr><td>'.htmlspecialchars(trim($opensim->getUserName($row['IdentityID']))).'</td><td data-uuid="'.htmlspecialchars($row['IdentityID']).'"><button name="enableIdent" class="btn btn-success btn-sm" data-toggle="modal" data-target="#isc">Aktivieren</button> <button type="submit" name="deleteIdent" class="btn btn-danger btn-sm" data-toggle="modal" data-target="#idc">Löschen</button></td></tr>';
}
$table = $table.$entry;
}
$message = '';
if (isset($_SESSION['identities_err'])) {
$message = '<div class="alert alert-danger" role="alert">'.$_SESSION['identities_err'].'</div>';
unset($_SESSION['identities_err']);
}
$this->app->template('identities.php')->parent('__dashboard.php')->vars([
'title' => 'Identitäten',
'username' => $_SESSION['DISPLAYNAME'],
'activeIdent' => $_SESSION['FIRSTNAME'].' '.$_SESSION['LASTNAME'],
'message' => $message
])->unsafeVar('ident-list', $table.'</tbody></table>')->render();
}
public function post(): void
{
if (isset($_POST['enableIdent'])) {
$validator = new FormValidator(array(
'uuid' => array('required' => true, 'regex' => '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/')
));
if ($validator->isValid($_POST)) {
$statement = $this->app->db()->prepare("SELECT 1 FROM mcp_user_identities WHERE PrincipalID = :PrincipalID AND IdentityID = :IdentityID LIMIT 1");
$statement->execute(['PrincipalID' => $_SESSION['UUID'], 'IdentityID' => $_POST['uuid']]);
$statementPresence = $this->app->db()->prepare("SELECT 1 FROM Presence WHERE UserID = :PrincipalID LIMIT 1");
$statementPresence->execute(['PrincipalID' => $_SESSION['UUID']]);
if ($statementPresence->rowCount() == 0) {
if ($statement->rowCount() == 1) {
$statementAuth = $this->app->db()->prepare('UPDATE auth SET UUID = :IdentityID WHERE UUID = :PrincipalID');
$statementAuth->execute(['IdentityID' => $_POST['uuid'], 'PrincipalID' => $_SESSION['UUID']]);
$statementUserIdentitys = $this->app->db()->prepare('UPDATE mcp_user_identities SET PrincipalID = :IdentityID WHERE PrincipalID = :PrincipalID');
$statementUserIdentitys->execute(['IdentityID' => $_POST['uuid'], 'PrincipalID' => $_SESSION['UUID']]);
$statementFriends = $this->app->db()->prepare('UPDATE Friends SET PrincipalID = :IdentityID WHERE PrincipalID = :PrincipalID');
$statementFriends->execute(['IdentityID' => $_POST['uuid'], 'PrincipalID' => $_SESSION['UUID']]);
//$statementReFriends = $this->app->db()->prepare('UPDATE Friends SET Friend = :IdentityID WHERE Friend = :PrincipalID');
//$statementReFriends->execute(['IdentityID' => $_POST['uuid'], 'PrincipalID' => $_SESSION['UUID']]);
$statementInventoryFolders = $this->app->db()->prepare('UPDATE inventoryfolders SET agentID = :IdentityID WHERE agentID = :PrincipalID AND type != :InventarTyp');
$statementInventoryFolders->execute(['IdentityID' => $_POST['uuid'], 'PrincipalID' => $_SESSION['UUID'], 'InventarTyp' => 46]);
$statementInventoryItems = $this->app->db()->prepare('UPDATE inventoryitems SET avatarID = :IdentityID WHERE avatarID = :PrincipalID');
$statementInventoryItems->execute(['IdentityID' => $_POST['uuid'], 'PrincipalID' => $_SESSION['UUID']]);
$statementGroupMembership = $this->app->db()->prepare('UPDATE os_groups_membership SET PrincipalID = :IdentityID WHERE PrincipalID = :PrincipalID');
$statementGroupMembership->execute(['IdentityID' => $_POST['uuid'], 'PrincipalID' => $_SESSION['UUID']]);
$statementGroupRoles = $this->app->db()->prepare('UPDATE os_groups_rolemembership SET PrincipalID = :IdentityID WHERE PrincipalID = :PrincipalID');
$statementGroupRoles->execute(['IdentityID' => $_POST['uuid'], 'PrincipalID' => $_SESSION['UUID']]);
$statementGroupRoles = $this->app->db()->prepare('DELETE FROM Presence WHERE UserID = :PrincipalID');
$statementGroupRoles->execute(['PrincipalID' => $_SESSION['UUID']]);
$_SESSION['LOGIN'] = 'false';
session_destroy();
}
} else {
$_SESSION['identities_err'] = 'Du kannst die Identität nicht ändern, während du angemeldet bist. Bitte schließe den Viewer.';
}
}
} elseif (isset($_POST['createIdent'])) {
$validator = new FormValidator(array(
'newName' => array('required' => true, 'regex' => '/^[^\\/<>\s]{1,64} [^\\/<>\s]{1,64}$/')
));
if ($validator->isValid($_POST)) {
$avatarNameParts = explode(" ", trim($_POST['newName']));
if (count($avatarNameParts) == 2) {
$statement = $this->app->db()->prepare("SELECT 1 FROM UserAccounts WHERE FirstName = :FirstName AND LastName = :LastName LIMIT 1");
$statement->execute(['FirstName' => trim($avatarNameParts[0]), 'LastName' => trim($avatarNameParts[1])]);
if ($statement->rowCount() == 0) {
$avatarUUID = (new OpenSim($this->app->db()))->generateUuid();
$statementAccounts = $this->app->db()->prepare('INSERT INTO UserAccounts (PrincipalID, ScopeID, FirstName, LastName, Email, ServiceURLs, Created, UserLevel, UserFlags, UserTitle, active) VALUES (:PrincipalID, :ScopeID, :FirstName, :LastName, :Email, :ServiceURLs, :Created, :UserLevel, :UserFlags, :UserTitle, :active )');
$statementAccounts->execute(['PrincipalID' => $avatarUUID, 'ScopeID' => "00000000-0000-0000-0000-000000000000", 'FirstName' => $avatarNameParts[0], 'LastName' => $avatarNameParts[1], 'Email' => $_SESSION['EMAIL'], 'ServiceURLs' => "HomeURI= GatekeeperURI= InventoryServerURI= AssetServerURI= ", 'Created' => time(), 'UserLevel' => 0, 'UserFlags' => 0, 'UserTitle' => "", 'active' => 1]);
$statementProfile = $this->app->db()->prepare('INSERT INTO `userprofile` (`useruuid`, `profilePartner`, `profileImage`, `profileURL`, `profileFirstImage`, `profileAllowPublish`, `profileMaturePublish`, `profileWantToMask`, `profileWantToText`, `profileSkillsMask`, `profileSkillsText`, `profileLanguages`, `profileAboutText`, `profileFirstText`) VALUES (:useruuid, :profilePartner, :profileImage, :profileURL, :profileFirstImage, :profileAllowPublish, :profileMaturePublish, :profileWantToMask, :profileWantToText, :profileSkillsMask, :profileSkillsText, :profileLanguages, :profileAboutText, :profileFirstText)');
$statementProfile->execute(['useruuid' => $avatarUUID, 'profilePartner' => "00000000-0000-0000-0000-000000000000", 'profileImage' => "00000000-0000-0000-0000-000000000000", 'profileURL' => '', 'profileFirstImage' => "00000000-0000-0000-0000-000000000000", "profileAllowPublish" => "0", "profileMaturePublish" => "0", "profileWantToMask" => "0", "profileWantToText" => "", "profileSkillsMask" => "0", "profileSkillsText" => "", "profileLanguages" => "", "profileAboutText" => "", "profileFirstText" => ""]);
$statementUserIdentitys = $this->app->db()->prepare('INSERT INTO mcp_user_identities (PrincipalID, IdentityID) VALUES (:PrincipalID, :IdentityID)');
$statementUserIdentitys->execute(['PrincipalID' => $_SESSION['UUID'], 'IdentityID' => $avatarUUID]);
} else {
$_SESSION['identities_err'] = 'Dieser Name ist schon in Benutzung.';
}
} else {
$_SESSION['identities_err'] = 'Der Name muss aus einem Vor- und einem Nachnamen bestehen.';
}
}
}
elseif (isset($_POST['deleteIdent'])) {
$validator = new FormValidator(array(
'uuid' => array('required' => true, 'regex' => '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/')
));
if ($validator->isValid($_POST)) {
(new OpenSim($this->app->db()))->deleteIdentity($_SESSION['UUID'], $_POST['uuid']);
}
}
header('Location: index.php?page=identities');
}
}

93
app/page/Login.php Normal file
View File

@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Mcp\Page;
use Mcp\FormValidator;
use Mcp\Middleware\PreSessionMiddleware;
class Login extends \Mcp\RequestHandler
{
public function __construct(\Mcp\Mcp $app)
{
parent::__construct($app, new PreSessionMiddleware($app->config('domain')));
}
public function get(): void
{
$tpl = $this->app->template('login.php')->parent('__presession.php')->var('title', 'Login')->var('last-username', '');
if (isset($_SESSION) && isset($_SESSION['loginMessage'])) {
$tpl->vars([
'message' => $_SESSION['loginMessage'],
'message-color' => $_SESSION['loginMessageColor']
]);
unset($_SESSION['loginMessage']);
unset($_SESSION['loginMessageColor']);
} else {
$tpl->vars([
'message' => '',
'message-color' => 'red'
]);
}
$tpl->render();
}
public function post(): void
{
$validator = new FormValidator(array(
'username' => array('required' => true, 'regex' => '/^[^\\/<>\s]{1,64} [^\\/<>\s]{1,64}$/'),
'password' => array('required' => true, 'regex' => '/^.{1,1000}$/')
));
$tpl = $this->app->template('login.php')->parent('__presession.php')->var('title', 'Login');
if (!$validator->isValid($_POST)) {
$tpl->vars([
'message' => 'Bitte gebe Benutzername (Vor- und Nachname) und Passwort ein.',
'message-color' => 'red',
'last-username'=> ''
])->render();
} else {
$statementUser = $this->app->db()->prepare("SELECT PrincipalID,FirstName,LastName,Email,UserLevel,passwordHash,passwordSalt FROM UserAccounts JOIN auth ON UserAccounts.PrincipalID = auth.UUID WHERE FirstName = ? AND LastName = ? LIMIT 1");
$statementUser->execute(explode(" ", trim($_POST['username'])));
$res = ['passwordHash' => '', 'passwordSalt' => ''];
if ($rowUser = $statementUser->fetch()) {
$res = $rowUser;
}
if (hash_equals(md5(md5($_POST['password']).":".$res['passwordSalt']), $res['passwordHash'])) {
session_abort();
session_set_cookie_params([
'lifetime' => 86400,
'path' => '/',
'domain' => $this->app->config('domain'),
'httponly' => true,
'secure' => true,
'samesite' => 'Strict'
]);
session_start();
session_regenerate_id(true);
$_SESSION['FIRSTNAME'] = $rowUser['FirstName'];
$_SESSION['LASTNAME'] = $rowUser['LastName'];
$_SESSION['EMAIL'] = $rowUser['Email'];
$_SESSION['PASSWORD'] = $rowUser['passwordHash'];
$_SESSION['SALT'] = $rowUser['passwordSalt'];
$_SESSION['UUID'] = $rowUser['PrincipalID'];
$_SESSION['LEVEL'] = $rowUser['UserLevel'];
$_SESSION['DISPLAYNAME'] = strtoupper($rowUser['FirstName'].' '.$rowUser['LastName']);
$_SESSION['LOGIN'] = 'true';
header("Location: index.php?page=dashboard");
die();
}
$tpl->vars([
'message' => 'Benutzername und/oder Passwort falsch.',
'message-color' => 'red',
'last-username' => $_POST['username']
])->render();
}
}
}

112
app/page/ManageUsers.php Normal file
View File

@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
namespace Mcp\Page;
use Mcp\FormValidator;
use Mcp\OpenSim;
use Mcp\RequestHandler;
use Mcp\Util\Util;
use Mcp\Middleware\AdminMiddleware;
class ManageUsers extends RequestHandler
{
public function __construct(\Mcp\Mcp $app)
{
parent::__construct($app, new AdminMiddleware($app, $app->config('domain')));
}
public function get(): void
{
$table = '<table class="table"><thead><tr><th scope="col">Vorname</th><th scope="col">Nachname</th><th scope="col">Status</th><th scope="col">Aktionen</th></thead><tbody>';
// Only select current primary account
$statement = $this->app->db()->prepare("SELECT FirstName,LastName,UserLevel,PrincipalID FROM UserAccounts JOIN auth ON auth.UUID = UserAccounts.PrincipalID ORDER BY Created ASC");
$statement->execute();
$statementIdent = $this->app->db()->prepare("SELECT FirstName,LastName,UserLevel,IdentityID FROM mcp_user_identities JOIN UserAccounts ON UserAccounts.PrincipalID = mcp_user_identities.IdentityID WHERE mcp_user_identities.PrincipalID = ? AND mcp_user_identities.PrincipalID != mcp_user_identities.IdentityID");
$csrf = $this->app->csrfField();
while ($row = $statement->fetch()) {
$entry = '<tr><td>'.htmlspecialchars($row['FirstName']).'</td><td>'.htmlspecialchars($row['LastName']).'</td><td>'.htmlspecialchars(strval($row['UserLevel'])).'</td><td><form action="index.php?page=users" method="post">'.$csrf.'<input type="hidden" name="userid" value="'.htmlspecialchars($row['PrincipalID']).'"><button type="submit" name="genpw" class="btn btn-link btn-sm">PASSWORT ZURÜCKSETZEN</button> <button type="submit" name="deluser" class="btn btn-link btn-sm" style="color: red">LÖSCHEN</button></form></td></tr>';
$statementIdent->execute([$row['PrincipalID']]);
while ($identRow = $statementIdent->fetch()) {
$entry = $entry.'<tr class="ident-row"><td>'.htmlspecialchars($identRow['FirstName']).'</td><td>'.htmlspecialchars($identRow['LastName']).'</td><td>'.htmlspecialchars(strval($identRow['UserLevel'])).'</td><td><form action="index.php?page=users" method="post">'.$csrf.'<input type="hidden" name="userid" value="'.htmlspecialchars($row['PrincipalID']).'"><input type="hidden" name="identid" value="'.htmlspecialchars($identRow['IdentityID']).'"><button type="submit" name="delident" class="btn btn-link btn-sm">Identität löschen</button></form></td></tr>';
}
$table = $table.$entry;
}
$tpl = $this->app->template('users.php')->parent('__dashboard.php')->vars([
'title' => 'Benutzer',
'username' => $_SESSION['DISPLAYNAME']
])->unsafeVar('user-list', $table.'</tbody></table>');
if (isset($_SESSION['users-message'])) {
$tpl->unsafeVar('message', $_SESSION['users-message']);
unset($_SESSION['users-message']);
}
if (isset($_SESSION['invite-id'])) {
$tpl->var('invite-link', 'https://'.$this->app->config('domain').'/index.php?page=register&code='.$_SESSION['invite-id']);
unset($_SESSION['invite-id']);
}
$tpl->render();
}
public function post(): void
{
if (isset($_POST['generateLink'])) {
$validator = new FormValidator(array()); // Needed only for CSRF token validation
if ($validator->isValid($_POST)) {
$inviteID = bin2hex(random_bytes(16));
$statement = $this->app->db()->prepare('INSERT INTO `mcp_invites` (`InviteCode`) VALUES (:InviteCode)');
$statement->execute(['InviteCode' => $inviteID]);
$_SESSION['invite-id'] = $inviteID;
}
} elseif (isset($_POST['delident'])) {
$validator = new FormValidator(array(
'userid' => array('required' => true, 'regex' => '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/'),
'identid' => array('required' => true, 'regex' => '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/')
));
if ($validator->isValid($_POST)) {
$os = new OpenSim($this->app->db());
$identName = $os->getUserName($_POST['identid']);
$userName = $os->getUserName($_POST['userid']);
if ($os->deleteIdentity($_POST['userid'], $_POST['identid'])) {
$_SESSION['users-message'] = 'Identität <b>'.$identName.'</b> von <b>'.$userName.'</b> wurde gelöscht.';
} else {
$_SESSION['users-message'] = 'Identität <b>'.$identName.'</b> konnte nicht gelöscht werden.';
}
}
} else {
$validator = new FormValidator(array(
'userid' => array('required' => true, 'regex' => '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/')
));
if ($validator->isValid($_POST)) {
$opensim = new OpenSim($this->app->db());
if (isset($_POST['genpw'])) {
$token = Util::generateToken(32);
$setToken = $this->app->db()->prepare('REPLACE INTO mcp_password_reset(PrincipalID,Token,RequestTime) VALUES(?,?,?)');
$setToken->execute([$_POST['userid'], $token, time()]);
$resetLink = "https://".$this->app->config('domain').'/index.php?page=reset-password&token='.$token;
$_SESSION['users-message'] = 'Das Passwort für '.htmlspecialchars($opensim->getUserName($_POST['userid'])).' kann in den nächsten 24 Stunden über diesen Link zurückgesetzt werden: <b>'.$resetLink.'</b>';
} elseif (isset($_POST['deluser'])) {
$name = $opensim->getUserName($_POST['userid']);
if ($opensim->deleteUser($_POST['userid'])) {
$_SESSION['users-message'] = 'Der Account <b>'.$name.'</b> wurde gelöscht.';
} else {
$_SESSION['users-message'] = 'Der Account <b>'.$name.'</b> konnte nicht gelöscht werden.';
}
}
}
}
header('Location: index.php?page=users');
}
}

36
app/page/OnlineUsers.php Normal file
View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Mcp\Page;
use Mcp\OpenSim;
use Mcp\Middleware\LoginRequiredMiddleware;
class OnlineUsers extends \Mcp\RequestHandler
{
public function __construct(\Mcp\Mcp $app)
{
parent::__construct($app, new LoginRequiredMiddleware($app, $app->config('domain')));
}
public function get(): void
{
$opensim = new OpenSim($this->app->db());
$table = '<table class="table"><thead><tr><th scope="col">Benutzername</th><th scope="col">Region</th></thead><tbody>';
$statement = $this->app->db()->prepare("SELECT RegionID,UserID FROM Presence ORDER BY RegionID ASC");
$statement->execute();
while ($row = $statement->fetch()) {
if ($row['RegionID'] != "00000000-0000-0000-0000-000000000000") {
$table = $table.'<tr><td>'.htmlspecialchars(trim($opensim->getUserName($row['UserID']))).'</td><td>'.htmlspecialchars($opensim->getRegionName($row['RegionID'])).'</td></tr>';
}
}
$this->app->template('__dashboard.php')->vars([
'title' => 'Online Anzeige',
'username' => $_SESSION['DISPLAYNAME']
])->unsafeVar('child-content', $table.'</tbody></table>')->render();
}
}

260
app/page/Profile.php Normal file
View File

@ -0,0 +1,260 @@
<?php
declare(strict_types=1);
namespace Mcp\Page;
use Mcp\FormValidator;
use Mcp\OpenSim;
use Mcp\Middleware\LoginRequiredMiddleware;
class Profile extends \Mcp\RequestHandler
{
public function __construct(\Mcp\Mcp $app)
{
parent::__construct($app, new LoginRequiredMiddleware($app, $app->config('domain')));
}
public function get(): void
{
$tpl = $this->app->template('profile.php')->parent('__dashboard.php');
//Prüfe ob IAR grade erstellt wird.
$iarRunning = false;
if (isset($_SESSION['iar_created'])) {
$tpl->unsafeVar('iar-message', '<div class="alert alert-success" role="alert">Deine IAR wird jetzt erstellt und der Download Link wird dir per PM zugesendet.</div>');
unset($_SESSION['iar_created']);
$iarRunning = true;
} else {
$statementIARCheck = $this->app->db()->prepare('SELECT iarfilename,state,created FROM mcp_iar_state WHERE userID =:userID');
$statementIARCheck->execute(['userID' => $_SESSION['UUID']]);
if ($row = $statementIARCheck->fetch()) {
if ($row['state'] < 2) {
$tpl->unsafeVar('iar-message', '<div class="alert alert-danger" role="alert">Aktuell wird eine IAR erstellt.<br>Warte bitte bis du eine PM bekommst.</div>');
$iarRunning = true;
}
else {
$tpl->unsafeVar('iar-message', '<div class="alert alert-success role="alert">Du kannst dir deine IAR (erstellt am '.date('d.m.Y', $row['created']).') <a href="https://'.$this->app->config('domain').'/index.php?api=downloadIar&id='.substr($row['iarfilename'], 0, strlen($row['iarfilename']) - 4).'">hier</a> herunterladen. Sie ist mit dem Passwort <i>password</i> geschützt.</div>');
}
}
$statementIARCheck->closeCursor();
}
if ($iarRunning) {
$tpl->var('iar-button-state', 'disabled');
}
$opensim = new OpenSim($this->app->db());
$partnerUUID = $opensim->getPartner($_SESSION['UUID']);
$partnerName = "";
if ($partnerUUID != null) {
$partnerName = $opensim->getUserName($partnerUUID);
}
$profileInfo = '';
if (isset($_SESSION['profile_info'])) {
$profileInfo = $_SESSION['profile_info'];
unset($_SESSION['profile_info']);
}
$tpl->vars([
'title' => 'Dein Profil',
'offline-im-state' => $opensim->allowOfflineIM($_SESSION['UUID']) == "TRUE" ? ' checked' : ' ',
'firstname' => $_SESSION['FIRSTNAME'],
'lastname' => $_SESSION['LASTNAME'],
'username' => $_SESSION['DISPLAYNAME'],
'partner' => $partnerName,
'email' => $opensim->getUserMail($_SESSION['UUID']),
'residents-js-array' => '',
'message' => $profileInfo
])->render();
}
public function post(): void
{
if (isset($_POST['createIAR'])) {
$validator = new FormValidator(array()); // CSRF validation only
if($validator->isValid($_POST)) {
$validRequest = true;
$statementIarFile = $this->app->db()->prepare('SELECT iarfilename,state,created FROM mcp_iar_state WHERE userID = ?');
$statementIarFile->execute([$_SESSION['UUID']]);
if ($row = $statementIarFile->fetch()) {
if ($row['state'] == 2) {
unlink($this->app->getDataDir().DIRECTORY_SEPARATOR.'iars'.DIRECTORY_SEPARATOR.$row['iarfilename']);
}
else {
$validRequest = false;
}
}
if ($validRequest) {
$iarname = md5(time().$_SESSION['UUID'] . rand()).".iar";
$statementIARSTART = $this->app->db()->prepare('INSERT INTO mcp_iar_state (userID, filesize, iarfilename) VALUES (:userID, :filesize, :iarfilename) ON DUPLICATE KEY UPDATE filesize = :replFilesize, state = :replState');
$statementIARSTART->execute(['userID' => $_SESSION['UUID'], 'filesize' => 0, 'iarfilename' => $iarname, 'replFilesize' => 0, 'replState' => 0]);
$_SESSION['iar_created'] = true;
}
}
}
elseif (isset($_POST['saveProfileData'])) {
$validator = new FormValidator(array(
'formInputFeldVorname' => array('regex' => '/^[^\\/<>\s]{1,64}$/'),
'formInputFeldNachname' => array('regex' => '/^[^\\/<>\s]{1,64}$/'),
'formInputFeldEMail' => array('regex' => '/^\S{1,64}@\S{1,250}.\S{2,64}$/'),
'formInputFeldOfflineIM' => array('regex' => '/^(|on)$/'),
'formInputFeldPartnerName' => array('regex' => '/^[^\\/<>\s]{1,64} [^\\/<>\s]{1,64}$/')
));
if ($validator->isValid($_POST)) {
if(isset($_POST['formInputFeldVorname'])) {
$newFirstName = trim($_POST['formInputFeldVorname']);
if($newFirstName != "" && $_SESSION['FIRSTNAME'] != $newFirstName) {
if($this->setNamePart('FirstName', $newFirstName, 'LastName', isset($_POST['formInputFeldNachname']) && strlen(trim($_POST['formInputFeldNachname'])) > 0 ? $_POST['formInputFeldNachname'] : $_SESSION['LASTNAME'])) {
$_SESSION['FIRSTNAME'] = $newFirstName;
$_SESSION['USERNAME'] = $_SESSION['FIRSTNAME']." ".$_SESSION['LASTNAME'];
$_SESSION['DISPLAYNAME'] = strtoupper($_SESSION['USERNAME']);
}
else {
$_SESSION['profile_info'] = 'Der gewählte Name ist bereits vergeben.';
}
}
}
if (isset($_POST['formInputFeldNachname'])) {
$newLastName = trim($_POST['formInputFeldNachname']);
if ($newLastName != "" && $_SESSION['LASTNAME'] != $newLastName) {
if ($this->setNamePart('LastName', $newLastName, 'FirstName', isset($_POST['formInputFeldVorname']) && strlen(trim($_POST['formInputFeldVorname'])) > 0 ? $_POST['formInputFeldVorname'] : $_SESSION['FIRSTNAME'])) {
$_SESSION['LASTNAME'] = $newLastName;
$_SESSION['USERNAME'] = $_SESSION['FIRSTNAME']." ".$_SESSION['LASTNAME'];
$_SESSION['DISPLAYNAME'] = strtoupper($_SESSION['USERNAME']);
} else {
$_SESSION['profile_info'] = 'Der gewählte Name ist bereits vergeben.';
}
}
}
if (isset($_POST['formInputFeldEMail'])) {
$newEmail = trim($_POST['formInputFeldEMail']);
if ($newEmail != "" && $_SESSION['EMAIL'] != $newEmail) {
$statement = $this->app->db()->prepare('UPDATE UserAccounts SET Email = :Email WHERE PrincipalID = :PrincipalID');
$statement->execute(['Email' => $newEmail, 'PrincipalID' => $_SESSION['UUID']]);
$statement = $this->app->db()->prepare('UPDATE usersettings SET email = :Email WHERE useruuid = :PrincipalID');
$statement->execute(['Email' => $newEmail, 'PrincipalID' => $_SESSION['UUID']]);
$_SESSION['EMAIL'] = $newEmail;
}
}
if (isset($_POST['formInputFeldOfflineIM']) && $_POST['formInputFeldOfflineIM'] == "on") {
$statement = $this->app->db()->prepare('UPDATE usersettings SET imviaemail = :IMState WHERE useruuid = :PrincipalID');
$statement->execute(['IMState' => 'true', 'PrincipalID' => $_SESSION['UUID']]);
} else {
$statement = $this->app->db()->prepare('UPDATE usersettings SET imviaemail = :IMState WHERE useruuid = :PrincipalID');
$statement->execute(['IMState' => 'false', 'PrincipalID' => $_SESSION['UUID']]);
}
if (isset($_POST['formInputFeldPartnerName']) && $_POST['formInputFeldPartnerName'] != "") {
$opensim = new OpenSim($this->app->db());
$newPartner = trim($_POST['formInputFeldPartnerName']);
$currentPartner = $opensim->getPartner($_SESSION['UUID']);
if ($currentPartner != "") {
$currentPartner = $opensim->getUserName($currentPartner);
}
if ($newPartner != "" && $currentPartner != $newPartner) {
$newPartnerUUID = $opensim->getUserUUID($newPartner);
if ($newPartnerUUID != null) {
$statement = $this->app->db()->prepare('UPDATE userprofile SET profilePartner = :profilePartner WHERE useruuid = :PrincipalID');
$statement->execute(['profilePartner' => $newPartnerUUID, 'PrincipalID' => $_SESSION['UUID']]);
}
} else {
$statement = $this->app->db()->prepare('UPDATE userprofile SET profilePartner = :profilePartner WHERE useruuid = :PrincipalID');
$statement->execute(['profilePartner' => '00000000-0000-0000-0000-000000000000', 'PrincipalID' => $_SESSION['UUID']]);
}
}
}
} elseif (isset($_POST['savePassword'])) {
$validator = new FormValidator(array(
'oldPassword' => array('required' => true, 'regex' => '/^.{1,1000}$/'),
'newPassword' => array('required' => true, 'regex' => '/^.{1,1000}$/'),
'newPasswordRepeat' => array('required' => true, 'regex' => '/^.{1,1000}$/')
));
if ($validator->isValid($_POST)) {
if ($_POST['newPasswordRepeat'] == $_POST['newPassword']) {
if (strlen(trim($_POST['newPassword'])) >= $this->app->config('password-min-length')) {
if (md5(md5($_POST['oldPassword']).':'.$_SESSION['SALT']) == $_SESSION['PASSWORD']) {
$salt = bin2hex(random_bytes(16));
$hash = md5(md5(trim($_POST['newPassword'])).':'.$salt);
$statement = $this->app->db()->prepare('UPDATE auth SET passwordHash = :PasswordHash, passwordSalt = :PasswordSalt WHERE UUID = :PrincipalID');
$statement->execute(['PasswordHash' => $hash, 'PasswordSalt' => $salt, 'PrincipalID' => $_SESSION['UUID']]);
$_SESSION['PASSWORD'] = $hash;
$_SESSION['SALT'] = $salt;
$_SESSION['profile_info'] = 'Neues Passwort gespeichert.';
} else {
$_SESSION['profile_info'] = 'Das alte Passwort ist nicht richtig!';
}
} else {
$_SESSION['profile_info'] = 'Das neue Passwort muss mindestens '.$this->app->config('password-min-length').' Zeichen lang sein.';
}
} else {
$_SESSION['profile_info'] = 'Die neuen Passwörter stimmen nicht überein!';
}
} else {
$_SESSION['profile_info'] = 'Bitte fülle das Formular vollständig aus.';
}
} elseif (isset($_POST['deleteAccount'])) {
$validator = new FormValidator(array(
'delete-confirm-password' => array('required' => true, 'regex' => '/^.{1,1000}$/'),
'delete-confirm' => array('required' => true, 'regex' => '/^(|on)$/')
));
if ($validator->isValid($_POST)) {
if (hash_equals(md5(md5($_POST['delete-confirm-password']).':'.$_SESSION['SALT']), $_SESSION['PASSWORD'])) {
$os = new OpenSim($this->app->db());
if ($os->deleteUser($_SESSION['UUID'])) {
$_SESSION['LOGIN'] = false;
session_destroy();
header('Location: index.php');
die();
} else {
$_SESSION['profile_info'] = 'Bei der Accountlöschung ist ein Fehler aufgetreten. Bitte versuche es später erneut.';
}
}
else {
$_SESSION['profile_info'] = 'Zur Bestätigung der Accountlöschung musst du dein Passwort richtig eingeben.';
}
}
else {
$_SESSION['profile_info'] = 'Um deinen Account zu löschen, ist dein aktuelles Passwort und die Bestätigung des Vorgangs erforderlich.';
}
}
header('Location: index.php?page=profile');
}
private function setNamePart(string $part, string $value, string $otherPart, string $otherValue): bool
{
$query = $this->app->db()->prepare('SELECT 1 FROM UserAccounts WHERE '.$part.' = ? AND '.$otherPart.' = ?');
$query->execute(array($value, $otherValue));
if ($query->rowCount() == 0) {
$statement = $this->app->db()->prepare('UPDATE UserAccounts SET '.$part.' = ? WHERE PrincipalID = ?');
$statement->execute(array($value, $_SESSION['UUID']));
return true;
}
return false;
}
}

96
app/page/Regions.php Normal file
View File

@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace Mcp\Page;
use Mcp\FormValidator;
use Mcp\OpenSim;
use Mcp\Util\Util;
use Mcp\Middleware\LoginRequiredMiddleware;
use Mcp\Middleware\AdminMiddleware;
class Regions extends \Mcp\RequestHandler
{
private bool $showAll;
public function __construct(\Mcp\Mcp $app)
{
$this->showAll = isset($_GET['SHOWALL']) && $_GET['SHOWALL'] == "1";
parent::__construct($app, $this->showAll ? new AdminMiddleware($app, $app->config('domain')) : new LoginRequiredMiddleware($app, $app->config('domain')));
}
public function get(): void
{
$table = '<table class="table"><thead><tr><th scope="col">Region Name</th><th scope="col">Eigentümer</th><th scope="col">Position</th><th scope="col">Aktionen</th></thead><tbody>';
$statement = $this->app->db()->prepare("SELECT uuid,regionName,owner_uuid,locX,locY FROM regions ".($this->showAll ? "ORDER BY owner_uuid ASC" : "WHERE owner_uuid = ? ORDER BY uuid ASC"));
$statement->execute($this->showAll ? array() : array($_SESSION['UUID']));
$opensim = new OpenSim($this->app->db());
$csrf = $this->app->csrfField();
$urlShowall = $this->showAll ? '&SHOWALL=1' : '';
while ($row = $statement->fetch()) {
$stats = $this->getRegionStatsData($row['uuid']);
$table = $table.'<tr><td>'.htmlspecialchars($row['regionName']).'<div class="blockquote-footer">'.(!empty($stats) ? 'Prims: '.$stats['Prims'].'; RAM-Nutzung: '.$stats['ProcMem'].'; SIM/PHYS FPS: '.$stats['SimFPS'].'/'.$stats['PhyFPS'].' ('.$stats['RegionVersion'].')' : 'Keine Statistik verfügbar').'</div></td><td>'.htmlspecialchars($opensim->getUserName($row['owner_uuid'])).'</td><td>'.Util::fillString(($row['locX'] / 256), 4).' / '.Util::fillString(($row['locY'] / 256), 4).'</td><td><form action="index.php?page=regions'.$urlShowall.'" method="post">'.$csrf.'<input type="hidden" name="region" value="'.$row['uuid'].'"><button type="submit" name="remove" class="btn btn-link btn-sm">LÖSCHEN</button></form></td></tr>';
}
$this->app->template('__dashboard.php')->vars([
'title' => $this->showAll ? 'Regionen verwalten' : 'Deine Regionen',
'username' => $_SESSION['DISPLAYNAME']
])->unsafeVar('child-content', $table.'</tbody></table>')->render();
}
public function post(): void
{
$validator = new FormValidator(array(
'region' => array('required' => true, 'regex' => '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/')
));
if (isset($_POST['remove']) && $validator->isValid($_POST)) {
if (isset($_GET['SHOWALL'])) {
$statementMembership = $this->app->db()->prepare("DELETE FROM regions WHERE uuid = ?");
$statementMembership->execute(array($_POST['region']));
} else {
$statementMembership = $this->app->db()->prepare("DELETE FROM regions WHERE uuid = ? AND owner_uuid = ?");
$statementMembership->execute(array($_POST['region'], $_SESSION['UUID']));
}
}
header('Location: index.php?page=regions'.($this->showAll ? '&SHOWALL=1' : ''));
}
private function cleanSize($bytes)
{
if ($bytes > 0) {
$unit = intval(log($bytes, 1024));
$units = array('B', 'KB', 'MB', 'GB');
if (array_key_exists($unit, $units) === true) {
return sprintf('%d %s', $bytes / pow(1024, $unit), $units[$unit]);
}
}
return $bytes;
}
private function getRegionStatsData($regionID)
{
$statement = $this->app->db()->prepare("SELECT Prims,SimFPS,PhyFPS,ProcMem,RegionVersion FROM mcp_regions_info WHERE regionID = ?");
$statement->execute([$regionID]);
if ($row = $statement->fetch()) {
$return = array();
$return['Prims'] = strval($row['Prims']);
$return['SimFPS'] = strval($row['SimFPS']);
$return['PhyFPS'] = strval($row['PhyFPS']);
$return['ProcMem'] = $this->cleanSize($row['ProcMem']);
$return['RegionVersion'] = trim($row['RegionVersion']);
return $return;
}
return array();
}
}

180
app/page/Register.php Normal file
View File

@ -0,0 +1,180 @@
<?php
declare(strict_types=1);
namespace Mcp\Page;
use Mcp\FormValidator;
use Mcp\OpenSim;
use Mcp\RequestHandler;
use Mcp\Middleware\PreSessionMiddleware;
use Exception;
use Mcp\Util\Util;
class Register extends RequestHandler
{
public function __construct(\Mcp\Mcp $app)
{
parent::__construct($app, new PreSessionMiddleware($app->config('domain')));
}
public function get(): void
{
if ($this->checkInvite()) {
$this->displayPage();
}
}
public function post(): void
{
$validator = new FormValidator(array(
'tos' => array('required' => true, 'equals' => 'on'),
'username' => array('required' => true, 'regex' => '/^[^\\/<>\s]{1,64}( [^\\/<>\s]{1,64})?$/'),
'password' => array('required' => true, 'regex' => '/^.{1,1000}$/'),
'email' => array('required' => true, 'regex' => '/^\S{1,64}@\S{1,250}.\S{2,64}$/'),
'avatar' => array('required' => true)
));
if (!$validator->isValid($_POST)) {
if (!isset($_POST['tos']) || $_POST['tos'] !== true) {
$this->displayPage("Du musst die Nutzungsbedingungen lesen und Akzeptieren.");
} else {
$this->displayPage("Ups da stimmt was nicht. Versuche es bitte noch mal.");
}
return;
}
$name = trim($_POST['username']);
$nameParts = explode(" ", $name);
if ($name != "") {
if (count($nameParts) == 1) {
$name .= " Resident";
$nameParts = explode(" ", $name);
}
$statementAvatarName = $this->app->db()->prepare("SELECT 1 FROM UserAccounts WHERE FirstName = :FirstName AND LastName = :LastName LIMIT 1");
$statementAvatarName->execute(['FirstName' => $nameParts[0], 'LastName' => $nameParts[1]]);
if ($statementAvatarName->rowCount() > 0) {
$this->displayPage("Der gewählte Name ist bereits vergeben.");
return;
}
}
$pass = trim($_POST['password']);
if (strlen($pass) < $this->app->config('password-min-length')) {
$this->displayPage('Dein Passwort muss mindestens '.$this->app->config('password-min-length').' Zeichen lang sein.');
return;
}
$email = trim($_POST['email']);
$avatar = null;
if (isset($this->app->config('default-avatar')[$_POST['avatar']])) {
$avatar = trim($_POST['avatar']);
} else {
$this->displayPage("Der gewählte Standardavatar existiert nicht.");
return;
}
$opensim = new OpenSim($this->app->db());
$avatarUUID = $opensim->generateUuid();
$salt = bin2hex(random_bytes(16));
$passwordHash = md5(md5($pass).':'.$salt);
$statementInviteDeleter = $this->app->db()->prepare('DELETE FROM mcp_invites WHERE InviteCode = :code');
$statementInviteDeleter->execute(['code' => $_REQUEST['code']]);
if ($statementInviteDeleter->rowCount() == 0) {
Util::displayError($this->app, "Der angegebene Einladungscode ist nicht mehr gültig.");
return;
}
try {
$this->app->db()->beginTransaction();
$statementAuth = $this->app->db()->prepare('INSERT INTO `auth` (`UUID`, `passwordHash`, `passwordSalt`, `webLoginKey`, `accountType`) VALUES (:UUID, :HASHVALUE, :SALT, :WEBKEY, :ACCTYPE)');
$statementAuth->execute(['UUID' => $avatarUUID, 'HASHVALUE' => $passwordHash, 'SALT' => $salt, 'WEBKEY' => "00000000-0000-0000-0000-000000000000", 'ACCTYPE' => "UserAccount"]);
$statementAccounts = $this->app->db()->prepare('INSERT INTO `UserAccounts` (`PrincipalID`, `ScopeID`, `FirstName`, `LastName`, `Email`, `ServiceURLs`, `Created`, `UserLevel`, `UserFlags`, `UserTitle`, `active`) VALUES (:PrincipalID, :ScopeID, :FirstName, :LastName, :Email, :ServiceURLs, :Created, :UserLevel, :UserFlags, :UserTitle, :active )');
$statementAccounts->execute(['PrincipalID' => $avatarUUID, 'ScopeID' => "00000000-0000-0000-0000-000000000000", 'FirstName' => $nameParts[0], 'LastName' => $nameParts[1], 'Email' => $email, 'ServiceURLs' => "HomeURI= GatekeeperURI= InventoryServerURI= AssetServerURI= ", 'Created' => time(), 'UserLevel' => 0, 'UserFlags' => 0, 'UserTitle' => "", 'active' => 1]);
$statementProfile = $this->app->db()->prepare('INSERT INTO `userprofile` (`useruuid`, `profilePartner`, `profileImage`, `profileURL`, `profileFirstImage`, `profileAllowPublish`, `profileMaturePublish`, `profileWantToMask`, `profileWantToText`, `profileSkillsMask`, `profileSkillsText`, `profileLanguages`, `profileAboutText`, `profileFirstText`) VALUES (:useruuid, :profilePartner, :profileImage, :profileURL, :profileFirstImage, :profileAllowPublish, :profileMaturePublish, :profileWantToMask, :profileWantToText, :profileSkillsMask, :profileSkillsText, :profileLanguages, :profileAboutText, :profileFirstText)');
$statementProfile->execute(['useruuid' => $avatarUUID, 'profilePartner' => "00000000-0000-0000-0000-000000000000", 'profileImage' => "00000000-0000-0000-0000-000000000000", 'profileURL' => '', 'profileFirstImage' => "00000000-0000-0000-0000-000000000000", "profileAllowPublish" => "0", "profileMaturePublish" => "0", "profileWantToMask" => "0", "profileWantToText" => "", "profileSkillsMask" => "0", "profileSkillsText" => "", "profileLanguages" => "", "profileAboutText" => "", "profileFirstText" => ""]);
$statementInventoryFolder = $this->app->db()->prepare('INSERT INTO `inventoryfolders` (`folderName`, `type`, `version`, `folderID`, `agentID`, `parentFolderID`) VALUES (:folderName, :folderTyp, :folderVersion, :folderID, :agentID, :parentFolderID)');
$inventory = array('Calling Cards' => 2, 'Objects' => 6, 'Landmarks' => 3, 'Clothing' => 5, 'Gestures' => 21, 'Body Parts' => 13, 'Textures' => 0, 'Scripts' => 10, 'Photo Album' => 15, 'Lost And Found' => 16, 'Trash' => 14, 'Notecards' => 7, 'My Inventory' => 8, 'Sounds' => 1, 'Animations' => 20);
$inventoryRootFolder = $opensim->generateUuid();
foreach ($inventory as $folderName => $inventoryType) {
$folderUUID = $opensim->generateUuid();
if ($inventoryType == 8) {
$folderUUID = $inventoryRootFolder;
$folderParent = "00000000-0000-0000-0000-000000000000";
} else {
$folderParent = $inventoryRootFolder;
}
$statementInventoryFolder->execute(['agentID' => $avatarUUID, 'folderName' => $folderName, 'folderTyp' => $inventoryType, 'folderVersion' => 1, 'folderID' => $folderUUID, 'parentFolderID' => $folderParent]);
}
$this->app->db()->commit();
} catch (Exception $pdoException) {
$this->app->db()->rollBack();
error_log('Could not create Account: '.$pdoException->getMessage());
$this->displayPage('Fehler bei der Erstellung deines Accounts. Bitte versuche es später erneut.');
return;
}
session_abort();
session_set_cookie_params([
'lifetime' => 86400,
'path' => '/',
'domain' => $this->app->config('domain'),
'httponly' => true,
'secure' => true,
'samesite' => 'Strict'
]);
session_start();
session_regenerate_id(true);
$_SESSION['FIRSTNAME'] = trim($nameParts[0]);
$_SESSION['LASTNAME'] = trim($nameParts[1]);
$_SESSION['EMAIL'] = $email;
$_SESSION['PASSWORD'] = $passwordHash;
$_SESSION['SALT'] = $salt;
$_SESSION['UUID'] = $avatarUUID;
$_SESSION['LEVEL'] = 0;
$_SESSION['DISPLAYNAME'] = strtoupper($name);
$_SESSION['LOGIN'] = 'true';
header('Location: index.php?page=dashboard');
}
private function displayPage(string $message = ''): void
{
$this->app->template('register.php')->parent('__presession.php')->vars([
'title' => 'Registrieren',
'message' => $message,
'tos-url' => $this->app->config('tos-url'),
'invcode' => $_REQUEST['code']
])->render();
}
private function checkInvite(): bool
{
if (!isset($_REQUEST['code'])) {
Util::displayError($this->app, "Du benötigst einen Einladungscode, um dich bei 4Creative zu registrieren.");
} elseif (strlen($_REQUEST['code']) != 32 || !preg_match('/^[a-f0-9]+$/', $_REQUEST['code'])) {
Util::displayError($this->app, "Der angegebene Einladungscode ist nicht gültig. Nutze genau den Link, der dir zugeschickt wurde.");
} else {
$statementInviteCode = $this->app->db()->prepare("SELECT 1 FROM mcp_invites WHERE InviteCode = ? LIMIT 1");
$statementInviteCode->execute([$_REQUEST['code']]);
if ($statementInviteCode->rowCount() == 0) {
Util::displayError($this->app, "Der angegebene Einladungscode ist nicht gültig. Nutze genau den Link, der dir zugeschickt wurde.");
return false;
}
return true;
}
return false;
}
}

111
app/page/ResetPassword.php Normal file
View File

@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace Mcp\Page;
use Mcp\FormValidator;
use Mcp\Middleware\PreSessionMiddleware;
use Mcp\Util\SmtpClient;
use Mcp\Util\Util;
class ResetPassword extends \Mcp\RequestHandler
{
private const MESSAGE = 'Hallo %%NAME%%,<br/><br/>das Passwort für deinen 4Creative-Account wurde soeben über die Funktion "Passwort vergessen" geändert.<br/><br/>Solltest du diese Änderung nicht selbst durchgeführt haben, wende dich bitte umgehend per E-Mail (info@4creative.net) oder über unseren Discord-Server an uns.';
private const TOKEN_INVALID = 'Dieser Link zur Passwortzurücksetzung ist nicht gültig. Bitte klicke oder kopiere den Link aus der E-Mail, die du erhalten hast.';
private const TOKEN_EXPIRED = 'Dein Link zur Passwortzurücksetzung ist abgelaufen. Klicke <a href="index.php?page=forgot">hier</a>, um eine neue Anfrage zu senden.';
public function __construct(\Mcp\Mcp $app)
{
parent::__construct($app, new PreSessionMiddleware($app->config('domain')));
}
public function get(): void
{
$this->displayPage();
}
public function post(): void
{
$validator = new FormValidator(array(
'password' => array('required' => true, 'regex' => '/^.{1,1000}$/'),
'passwordRepeat' => array('required' => true, 'regex' => '/^.{1,1000}$/'),
'resetToken' => array('required' => true, 'regex' => '/^[a-zA-Z0-9]{32}$/')
));
if ($validator->isValid($_POST)) {
if ($_POST['password'] !== $_POST['passwordRepeat']) {
$this->displayPage('Du musst in beiden Feldern das gleiche Passwort eingeben');
return;
}
if (strlen($_POST['password']) < $this->app->config('password-min-length')) {
$this->displayPage('Dein Passwort muss mindestens '.$this->app->config('password-min-length').' Zeichen lang sein.');
return;
}
$getReq = $this->app->db()->prepare('SELECT UserAccounts.PrincipalID AS UUID,FirstName,LastName,Email,Token,RequestTime FROM mcp_password_reset JOIN UserAccounts ON UserAccounts.PrincipalID = mcp_password_reset.PrincipalID WHERE Token = ?');
$getReq->execute([$_POST['resetToken']]);
$res = $getReq->fetch();
if (!$res || !hash_equals($res['Token'], $_POST['resetToken'])) {
$this->displayTokenError($this::TOKEN_INVALID);
return;
}
$uuid = $res['UUID'];
$name = $res['FirstName'].' '.$res['LastName'];
$getToken = $this->app->db()->prepare('DELETE FROM mcp_password_reset WHERE PrincipalID = ? AND Token = ?');
$getToken->execute([$uuid, $_POST['resetToken']]);
if ($getToken->rowCount() == 0) {
$this->displayTokenError($this::TOKEN_INVALID);
return;
}
if (time() - $res['RequestTime'] > 86400) {
$this->displayTokenError($this::TOKEN_EXPIRED);
return;
}
$salt = bin2hex(random_bytes(16));
$hash = md5(md5(trim($_POST['password'])).':'.$salt);
$statement = $this->app->db()->prepare('UPDATE auth SET passwordHash = :PasswordHash, passwordSalt = :PasswordSalt WHERE UUID = :PrincipalID');
$statement->execute(['PasswordHash' => $hash, 'PasswordSalt' => $salt, 'PrincipalID' => $uuid]);
session_unset();
$_SESSION['loginMessage'] = 'Du kannst dich jetzt mit deinem neuen Passwort einloggen!';
$_SESSION['loginMessageColor'] = 'darkgreen';
$smtp = $this->app->config('smtp');
$tplMail = $this->app->template('mail.php')->vars([
'title' => 'Passwort geändert',
'preheader' => 'Das Passwort für deinen 4Creative-Account wurde soeben zurückgesetzt'
])->unsafeVar('message', str_replace('%%NAME%%', $name, $this::MESSAGE));
(new SmtpClient($smtp['host'], intval($smtp['port']), $smtp['address'], $smtp['password']))->sendHtml($smtp['address'], $smtp['name'], $res['Email'], 'Passwort für '.$name.' zurückgesetzt', $tplMail);
header('Location: index.php?page=login');
}
}
private function displayTokenError(string $message): void
{
$this->app->template('error.php')->parent('__presession.php')->vars([
'title' => 'Fehler',
'error-message' => $message
])->render();
}
private function displayPage(string $message = ''): void
{
if (!isset($_GET['token']) || !preg_match('/^[a-z0-9A-Z]{32}$/', $_GET['token'])) {
$this->displayTokenError($this::TOKEN_INVALID);
return;
}
$this->app->template('reset-password.php')->parent('__presession.php')->vars([
'title' => 'Neues Passwort festlegen',
'message' => $message,
'reset-token' => $_GET['token']
])->render();
}
}

25
app/util/DiscordUtil.php Normal file
View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Mcp\Util;
class DiscordUtil
{
public static function sendMessageToWebhook($webhook, $title, $message): void
{
$rawMessage = file_get_contents("data/discordMessage.json");
$rawMessage = str_replace("%%message%%", $message, $rawMessage);
$rawMessage = str_replace("%%title%%", $title, $rawMessage);
$options = [
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/json',
'timeout' => 3,
'content' => $rawMessage
]
];
file_get_contents($webhook, false, stream_context_create($options));
}
}

63
app/util/SmtpClient.php Normal file
View File

@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace Mcp\Util;
use PHPMailer\PHPMailer\PHPMailer;
use Exception;
use Mcp\TemplateBuilder;
class SmtpClient
{
private PHPMailer $mailer;
public function __construct(string $host, int $port, string $username, string $password)
{
$mailer = new PHPMailer(true);
$mailer->isSMTP();
$mailer->CharSet = 'UTF-8';
$mailer->Host = $host;
$mailer->Port = $port;
$mailer->Username = $username;
$mailer->Password = $password;
$mailer->SMTPAuth = true;
$mailer->SMTPSecure = $port == 465 ? PHPMailer::ENCRYPTION_SMTPS : PHPMailer::ENCRYPTION_STARTTLS;
$this->mailer = $mailer;
}
public function sendHtml(string $fromAddr, string $fromName, string $to, string $subject, TemplateBuilder $tpl): bool
{
try {
$this->mailer->setFrom($fromAddr, $fromName);
$this->mailer->addAddress($to);
} catch (Exception $e) {
error_log('Failed to prepare mail client (from: '.$fromAddr.', to: '.$to.')');
return false;
}
$this->mailer->isHTML(true);
$this->mailer->Subject = $subject;
ob_start();
$tpl->render();
$tplOut = ob_get_flush();
$this->mailer->Body = $tplOut;
$this->mailer->AltBody = $this::htmlToPlain($tplOut);
try {
$this->mailer->send();
return true;
} catch (Exception $e) {
error_log('Could not send email: '.$this->mailer->ErrorInfo);
return false;
}
}
private static function htmlToPlain($message): string
{
$messageNew = str_replace('<br/>', "\n", $message);
$messageNew = strip_tags(preg_replace('/<a href="(.*)">(.*)<\\/a>/', "$2: $1", $messageNew));
return $messageNew;
}
}

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Mcp\Util;
/**
* This class can be used like a regular array, but is guaranteed to return a value.
* Keys not set in the underlying array return an empty string.
*/
class TemplateVarArray implements \ArrayAccess
{
private array $vars;
public function __construct(array $vars)
{
$this->vars = $vars;
}
public function offsetExists(mixed $offset): bool
{
return true;
}
public function offsetGet(mixed $offset): mixed
{
return isset($this->vars[$offset]) ? $this->vars[$offset] : '';
}
public function offsetSet(mixed $offset, mixed $value): void
{
$this->vars[$offset] = $value;
}
public function offsetUnset(mixed $offset): void
{
unset($this->vars[$offset]);
}
}

74
app/util/Util.php Normal file
View File

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Mcp\Util;
use Exception;
class Util
{
public static function fillString($string, $targetlength)
{
if (gettype($string) != 'string') {
$string = strval($string);
}
while(strlen($string) < $targetlength)
{
$string = "0".$string;
}
return $string;
}
public static function left($str, $length)
{
return substr($str, 0, $length);
}
public static function right($str, $length)
{
return substr($str, -$length);
}
public static function generateToken($length): string
{
$chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
$res = "";
for($i = 0; $i < $length; $i++) {
$index = random_int(0, strlen($chars) - 1);
$res = $res.substr($chars, $index, 1);
}
return $res;
}
public static function getDataFromHTTP($url, $content = "", $requestType = "application/text")
{
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, 1);
if ($content != "") {
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $content);
curl_setopt($curl, CURLOPT_USERAGENT, 'mcp/0.0.1');
curl_setopt($curl, CURLOPT_HTTPHEADER, [
'Content-Type' => $requestType
]);
}
return curl_exec($curl);
}
public static function sendInworldIM($fromUUID, $toUUID, $fromName, $targetURL, $text)
{
$rawXML = "<?xml version=\"1.0\" encoding=\"utf-8\"?><methodCall><methodName>grid_instant_message</methodName><params><param><value><struct><member><name>position_x</name><value><string>0</string></value></member><member><name>position_y</name><value><string>0</string></value></member><member><name>position_z</name><value><string>0</string></value></member><member><name>to_agent_id</name><value><string>".$toUUID."</string></value></member><member><name>from_agent_session</name><value><string>00000000-0000-0000-0000-000000000000</string></value></member><member><name>im_session_id</name><value><string>".$fromUUID."</string></value></member><member><name>from_agent_name</name><value><string>".$fromName."</string></value></member><member><name>from_agent_id</name><value><string>".$fromUUID."</string></value></member><member><name>binary_bucket</name><value><string>AA==</string></value></member><member><name>region_handle</name><value><i4>0</i4></value></member><member><name>region_id</name><value><string>00000000-0000-0000-0000-000000000000</string></value></member><member><name>parent_estate_id</name><value><string>1</string></value></member><member><name>timestamp</name><value><string>".time()."</string></value></member><member><name>dialog</name><value><string>AA==</string></value></member><member><name>offline</name><value><string>AA==</string></value></member><member><name>from_group</name><value><string>FALSE</string></value></member><member><name>message</name><value><string>".$text."</string></value></member></struct></value></param></params></methodCall>";
Util::getDataFromHTTP($targetURL, $rawXML, "text/xml");
}
public static function displayError($app, $message): void
{
$app->template('error.php')->parent('__presession.php')->vars([
'title' => 'Fehler',
'error-message' => $message
])->render();
}
}

View File

@ -1,34 +0,0 @@
<?php
class FormValidator {
private array $fieldValidation;
public function __construct(array $fieldValidation) {
$this->fieldValidation = $fieldValidation;
}
public function isValid(array $req) {
if(!isset($req['csrf']) || $req['csrf'] !== $_SESSION['csrf']) {
return false;
}
foreach($fieldValidation as $field => $params) {
if($params['required'] && (!isset($req[$field]) || strlen($req[$field]) == 0)) {
return false;
}
if(isset($params['regex'])) {
if(!preg_match($params['regex'], $req[$field])) {
return false;
}
}
else if(isset($params['equals']) && $params['equals'] !== $req[$field]) {
return false;
}
}
return true;
}
}
?>

View File

@ -1,252 +0,0 @@
<?php
/**
* PHP Class for handling Google Authenticator 2-factor authentication.
*
* @author Michael Kliewe
* @copyright 2012 Michael Kliewe
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*
* @link http://www.phpgangsta.de/
*/
class PHPGangsta_GoogleAuthenticator
{
protected $_codeLength = 6;
/**
* Create new secret.
* 16 characters, randomly chosen from the allowed base32 characters.
*
* @param int $secretLength
*
* @return string
*/
public function createSecret($secretLength = 16)
{
$validChars = $this->_getBase32LookupTable();
// Valid secret lengths are 80 to 640 bits
if ($secretLength < 16 || $secretLength > 128) {
throw new Exception('Bad secret length');
}
$secret = '';
$rnd = false;
if (function_exists('random_bytes')) {
$rnd = random_bytes($secretLength);
} elseif (function_exists('mcrypt_create_iv')) {
$rnd = mcrypt_create_iv($secretLength, MCRYPT_DEV_URANDOM);
} elseif (function_exists('openssl_random_pseudo_bytes')) {
$rnd = openssl_random_pseudo_bytes($secretLength, $cryptoStrong);
if (!$cryptoStrong) {
$rnd = false;
}
}
if ($rnd !== false) {
for ($i = 0; $i < $secretLength; ++$i) {
$secret .= $validChars[ord($rnd[$i]) & 31];
}
} else {
throw new Exception('No source of secure random');
}
return $secret;
}
/**
* Calculate the code, with given secret and point in time.
*
* @param string $secret
* @param int|null $timeSlice
*
* @return string
*/
public function getCode($secret, $timeSlice = null)
{
if ($timeSlice === null) {
$timeSlice = floor(time() / 30);
}
$secretkey = $this->_base32Decode($secret);
// Pack time into binary string
$time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice);
// Hash it with users secret key
$hm = hash_hmac('SHA1', $time, $secretkey, true);
// Use last nipple of result as index/offset
$offset = ord(substr($hm, -1)) & 0x0F;
// grab 4 bytes of the result
$hashpart = substr($hm, $offset, 4);
// Unpak binary value
$value = unpack('N', $hashpart);
$value = $value[1];
// Only 32 bits
$value = $value & 0x7FFFFFFF;
$modulo = pow(10, $this->_codeLength);
return str_pad($value % $modulo, $this->_codeLength, '0', STR_PAD_LEFT);
}
/**
* Get QR-Code URL for image, from google charts.
*
* @param string $name
* @param string $secret
* @param string $title
* @param array $params
*
* @return string
*/
public function getQRCodeGoogleUrl($name, $secret, $title = null, $params = array())
{
$width = !empty($params['width']) && (int) $params['width'] > 0 ? (int) $params['width'] : 200;
$height = !empty($params['height']) && (int) $params['height'] > 0 ? (int) $params['height'] : 200;
$level = !empty($params['level']) && array_search($params['level'], array('L', 'M', 'Q', 'H')) !== false ? $params['level'] : 'M';
$urlencoded = urlencode('otpauth://totp/'.$name.'?secret='.$secret.'');
if (isset($title)) {
$urlencoded .= urlencode('&issuer='.urlencode($title));
}
return "https://api.qrserver.com/v1/create-qr-code/?data=$urlencoded&size=${width}x${height}&ecc=$level";
}
/**
* Check if the code is correct. This will accept codes starting from $discrepancy*30sec ago to $discrepancy*30sec from now.
*
* @param string $secret
* @param string $code
* @param int $discrepancy This is the allowed time drift in 30 second units (8 means 4 minutes before or after)
* @param int|null $currentTimeSlice time slice if we want use other that time()
*
* @return bool
*/
public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = null)
{
if ($currentTimeSlice === null) {
$currentTimeSlice = floor(time() / 30);
}
if (strlen($code) != 6) {
return false;
}
for ($i = -$discrepancy; $i <= $discrepancy; ++$i) {
$calculatedCode = $this->getCode($secret, $currentTimeSlice + $i);
if ($this->timingSafeEquals($calculatedCode, $code)) {
return true;
}
}
return false;
}
/**
* Set the code length, should be >=6.
*
* @param int $length
*
* @return PHPGangsta_GoogleAuthenticator
*/
public function setCodeLength($length)
{
$this->_codeLength = $length;
return $this;
}
/**
* Helper class to decode base32.
*
* @param $secret
*
* @return bool|string
*/
protected function _base32Decode($secret)
{
if (empty($secret)) {
return '';
}
$base32chars = $this->_getBase32LookupTable();
$base32charsFlipped = array_flip($base32chars);
$paddingCharCount = substr_count($secret, $base32chars[32]);
$allowedValues = array(6, 4, 3, 1, 0);
if (!in_array($paddingCharCount, $allowedValues)) {
return false;
}
for ($i = 0; $i < 4; ++$i) {
if ($paddingCharCount == $allowedValues[$i] &&
substr($secret, -($allowedValues[$i])) != str_repeat($base32chars[32], $allowedValues[$i])) {
return false;
}
}
$secret = str_replace('=', '', $secret);
$secret = str_split($secret);
$binaryString = '';
for ($i = 0; $i < count($secret); $i = $i + 8) {
$x = '';
if (!in_array($secret[$i], $base32chars)) {
return false;
}
for ($j = 0; $j < 8; ++$j) {
$x .= str_pad(base_convert(@$base32charsFlipped[@$secret[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT);
}
$eightBits = str_split($x, 8);
for ($z = 0; $z < count($eightBits); ++$z) {
$binaryString .= (($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48) ? $y : '';
}
}
return $binaryString;
}
/**
* Get array with all 32 characters for decoding from/encoding to base32.
*
* @return array
*/
protected function _getBase32LookupTable()
{
return array(
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23
'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31
'=', // padding char
);
}
/**
* A timing safe equals comparison
* more info here: http://blog.ircmaxell.com/2014/11/its-all-about-time.html.
*
* @param string $safeString The internal (safe) value to be checked
* @param string $userString The user submitted (unsafe) value
*
* @return bool True if the two strings are identical
*/
private function timingSafeEquals($safeString, $userString)
{
if (function_exists('hash_equals')) {
return hash_equals($safeString, $userString);
}
$safeLen = strlen($safeString);
$userLen = strlen($userString);
if ($userLen != $safeLen) {
return false;
}
$result = 0;
for ($i = 0; $i < $userLen; ++$i) {
$result |= (ord($safeString[$i]) ^ ord($userString[$i]));
}
// They are only identical strings if $result is exactly 0...
return $result === 0;
}
}

View File

@ -1,245 +0,0 @@
<?php
class HTML
{
//Hier wird der HTML Code zum Ausgeben vorbereitet.
//Dieser kann aus einer fertigen HTML Seite ausgelesen werden, oder aber auch st<73>ck f<>r St<53>ck
//Zusammen gebaut werden.
//Die Einzelnen Daten k<>nnen nicht direkt von Au<41>en ver<65>ndert werden, sondern m<>ssen durch die Bereitgestellten Optionen gesetzt werden.
private $HTMLTitle = " "; //Wird in den <header> als <title> Geschrieben.
private $StatusMeldung = " "; //Falls Vorhenden eine Statusmeldung vom Script im HTML Text.
private $DasMenu = " "; //Beinhaltet das Fertige Men<65>
private $DerInhalt = " "; //Beinhaltet den Fertigen Inhalt
private $HTMLDatei = " "; //Der inhalt der eingelesen wurde.
private $HTMLHeader = " "; //Der HTML HEADER der eingelesen wurde.
private $FertigesHTML = " "; //Das Fertige HTML bereit zum Ausgeben.
private $isBuild = false; //Hier wird festgehalten ob $FertigesHTML aktuell ist oder nicht.
//Der <title> wird Generiert.(%%EchoTitle%%)
//Dieser wird im HTML Code sp<73>ter als %%HTMLTitle%% aufgerufen.
public function setHTMLTitle($neuerTitle){
//Der Bisherige Title wird komplett <20>berschrieben und gleichzeitig ein neuer Gesetzt.
$this->HTMLTitle = $neuerTitle;
$this->isBuild = false;
}
public function addHTMLTitle($Hinzufugen){
//Zu dem Bisherigen Titel wird noch etwas am ende hinzugef<65>gt.
$this->HTMLTitle = $this->$HTMLTitle.$Hinzufugen;
$this->isBuild = false;
}
public function RemoveHTMLTitle(){
//Der Titel wird Komplett gel<65>scht.
$this->HTMLTitle = " ";
$this->isBuild = false;
}
//Der HTML HEADER wird Generiert.(%%echoHeader%%)
//Dieser wird im HTML Code sp<73>ter als %%echoHeader%% aufgerufen.
public function setHTMLHeader($neuerHeader){
//Der Bisherige Header wird komplett <20>berschrieben und gleichzeitig ein neuer Gesetzt.
$this->HTMLHeader = $neuerHeader;
$this->isBuild = false;
}
public function addHTMLHeader($Hinzufugen){
//Zu dem Bisherigen Header wird noch etwas am ende hinzugef<65>gt.
$this->HTMLHeader = $this->HTMLHeader.$Hinzufugen;
$this->isBuild = false;
}
public function RemoveHTMLHeader(){
//Der Header wird Komplett gel<65>scht.
$this->HTMLHeader = " ";
$this->isBuild = false;
}
public function importHTMLHeader($file){
//Der HTML Header wird aus einer Datei eingelesen und der bisherige gel<65>scht.
$this->HTMLHeader = file_get_contents($file);
$this->isBuild = false;
}
//Der StatusText wird ge<67>ndert.(%%StatusMeldung%%)
//Dieser wird im HTML Code sp<73>ter als %%StatusMeldung%% aufgerufen.
public function setStatusMeldung($neueMeldung){
//Die bisherige Status meldung wird komplett <20>berschrieben und gleichzeitig ein neuer Gesetzt.
$this->StatusMeldung = $neueMeldung;
$this->isBuild = false;
}
public function RemoveStatusMeldung(){
//Die Meldung wird Komplett gel<65>scht.
$this->StatusMeldung = " ";
$this->isBuild = false;
}
//Ab hier wird das Men<65> Zusammengebaut. (%%EchoMenu%%)
public function importTextMenu($neuesMenu){
//Das Komplette Men<65> wird direkt importiert und das alte <20>berschreiben.
$this->DasMenu = $neuesMenu;
$this->isBuild = false;
}
public function importHTMLMenu($file){
//Das Komplette Men<65> wird aus einer Datei ausgelesen und das alte <20>berschrieben.
$this->DasMenu = file_get_contents($file);
$this->isBuild = false;
}
public function addToMenu($html){
//Es wird noch etwas ans Men<65> angehengt.
$this->DasMenu = $this->$DasMenu.$html;
$this->isBuild = false;
}
//Der Seiten HTML Quelcode wird eingelesen.
public function importHTML($file){
//Der HTML Quelltext wird aus einer Datei eingelesen.
$this->HTMLDatei = file_get_contents($file);
$this->isBuild = false;
}
public function setHTML($htmlCode){
//Der HTML Quelltext wird direkt gesetzt.
$this->HTMLDatei = $htmlCode;
$this->isBuild = false;
}
public function addNachHTML($htmlCode){
//Der HTML Quelltext wird direkt gesetzt.
$this->HTMLDatei = $this->HTMLDatei.$htmlCode;
$this->isBuild = false;
}
public function addVorHTML($htmlCode){
//Der HTML Quelltext wird direkt gesetzt.
$this->HTMLDatei = $htmlCode.$this->HTMLDatei;
$this->isBuild = false;
}
public function DeleteHTML(){
//Der HTML Quelltext wird gel<65>scht.
$this->HTMLDatei = " ";
$this->isBuild = false;
}
//Der inhalt der Seite wird zusammen gesetzt (nicht der quelltext) (%%EchoInhalt%%)
public function importSeitenInhalt($file){
//L<>d einen fertigen Text aus einer datei.
$this->DerInhalt = file_get_contents($file);
$this->isBuild = false;
}
public function setSeitenInhalt($html){
//Setz den Seiteninhalt und L<>scht den alten Komplett.
$this->DerInhalt = $html;
$this->isBuild = false;
}
public function importAndAddSeitenInhalt($file){
//L<>d einen fertigen Text aus einer datei.
$this->DerInhalt = $this->DerInhalt.file_get_contents($file);
$this->isBuild = false;
}
public function addToSeitenInhalt($html){
//Es wird noch weitere Text an den Seiteninhalt angeh<65>ngt.
$this->DerInhalt = $this->DerInhalt.$html;
$this->isBuild = false;
}
public function GetSeitenInhalt(){
//Der Seiteninhalt wird zur<75>ckgegeben.
return $this->DerInhalt;
}
public function DeleteSeitenInhalt(){
//L<>scht den Seiten inhalt.
$this->DerInhalt = " ";
$this->isBuild = false;
}
public function ReplaceSeitenInhalt($tag, $text){
//Ersezt Seiten Inhalt
$this->DerInhalt = str_replace($tag, $text, $this->DerInhalt);
}
public function ReplaceLayoutInhalt($tag, $text){
//Ersezt Layout Inhalt
$this->HTMLDatei = str_replace($tag, $text, $this->HTMLDatei);
}
public function CompressHTML(){
if($this->isBuild){
$this->FertigesHTML = str_replace(" ", "", $this->FertigesHTML);
$this->FertigesHTML = str_replace(" ", "", $this->FertigesHTML);
}else{
die("Es kann nur Fertiger HTML Code kompremiert werden.");
return false;
}
}
//Hier wird der Fertige HTML Code generiert.
//Und alle 3 Teile, Men<65> Titel und inhalt zusammengef<65>gt.
public function build(){
//Der HTML Code wird zusammen gesetzt.
$this->FertigesHTML = null; //Der Speicher wird gellert, falls schon einmal Quelltext generiert wurde.
$this->FertigesHTML = $this->HTMLDatei; //Und der Unverarbeitete HTML Quelltext eingelesen.
//Das Men<65> wird in den HTML Quellcode eingef<65>gt.
$this->FertigesHTML = str_replace("%%EchoMenu%%", $this->DasMenu, $this->FertigesHTML);
//Der inhalt wird in den HTML Quellcode eingef<65>gt.
$this->FertigesHTML = str_replace("%%EchoInhalt%%", $this->DerInhalt, $this->FertigesHTML);
//Die Status Meldung wird in den HTML Quellcode eingef<65>gt.
$this->FertigesHTML = str_replace("%%StatusMeldung%%", $this->StatusMeldung, $this->FertigesHTML);
//Der Titel wird in den HTML Quellcode eingef<65>gt.
$this->FertigesHTML = str_replace("%%EchoTitle%%", $this->HTMLTitle, $this->FertigesHTML);
//Der HTML Header wird in den HTML Quellcode eingef<65>gt.
$this->FertigesHTML = str_replace("%%echoHeader%%", $this->HTMLHeader, $this->FertigesHTML);
//Der Titel wird in den HTML Quellcode eingef<65>gt.
$this->FertigesHTML = str_replace("%%datum%%", date("Y-m-dTH:i+2"), $this->FertigesHTML);
//Der Counter wird in den HTML Quellcode eingef<65>gt.
$this->FertigesHTML = str_replace("%%GET_SITE%%", @$_GET['seite'], $this->FertigesHTML);
//Die IP Adresse wird in den HTML Quellcode eingef<65>gt.
$this->FertigesHTML = str_replace("%%GET_IP%%", @$_SERVER["REMOTE_ADDR"], $this->FertigesHTML);
// Add CSRF token
$this->FertigesHTML = str_replace("%%CSRF%%", '<input type="hidden" name="csrf" value="'.(isset($_SESSION['csrf']) ? $_SESSION['csrf'] : '').'">', $this->FertigesHTML);
$this->isBuild = true;
}
//Hier wird der Fertige HTML ausgegeben
public function ausgabe(){
if($this->isBuild){
return $this->FertigesHTML;
}else{
die("Bitte erst den HTML Code zusammensetzen.");
return false;
}
}
}
?>

View File

@ -1,230 +0,0 @@
<?php
class OpenSim
{
public function isLoginValid($name, $password)
{
global $RUNTIME;
$statementUser = $RUNTIME['PDO']->prepare("SELECT PrincipalID FROM UserAccounts WHERE FirstName = ? AND LastName = ? LIMIT 1");
$statementUser->execute(explode(" ", trim($name)));
while($rowUser = $statementUser->fetch())
{
$statementAuth = $RUNTIME['PDO']->prepare("SELECT passwordHash,passwordSalt FROM auth WHERE UUID = ? LIMIT 1");
$statementAuth->execute(array($rowUser['PrincipalID']));
while($rowAuth = $statementAuth->fetch())
{
$passwordCorrect = false;
if(strlen($rowAuth['passwordHash']) == 32) {
if(md5(md5($password).":".$rowAuth['passwordSalt']) == $rowAuth['passwordHash']) {
$passwordCorrect = true;
$newHash = password_hash($password, PASSWORD_ARGON2ID);
$updateHash = $RUNTIME['PDO']->prepare("UPDATE auth SET passwordHash = ?, passwordSalt = ? WHERE UUID = ?");
$updateHash->execute(array($newHash, '', $rowUser['PrincipalID']));
}
}
else {
$passwordCorrect = password_verify($password, $rowAuth['passwordHash']);
}
return $passwordCorrect;
}
}
return false;
}
public function getUserName($userID)
{
global $RUNTIME;
if($userID == "00000000-0000-0000-0000-000000000000")
return "Unknown User";
if(isset($RUNTIME['CACHE']['USERNAME'][$userID]))
return $RUNTIME['CACHE']['USERNAME'][$userID];
$statementUser = $RUNTIME['PDO']->prepare("SELECT FirstName,LastName FROM UserAccounts WHERE PrincipalID = ?");
$statementUser->execute(array($userID));
while($rowUser = $statementUser->fetch())
{
$RUNTIME['CACHE']['USERNAME'][$userID] = $rowUser['FirstName']." ".$rowUser['LastName'];
return $rowUser['FirstName']." ".$rowUser['LastName'];
}
$statementGridUser = $RUNTIME['PDO']->prepare("SELECT UserID FROM GridUser");
$statementGridUser->execute(array($userID));
while($rowGridUser = $statementGridUser->fetch())
{
$UserData = explode(";", $rowGridUser['UserID']);
if(count($UserData) >= 3)
{
$DBUserID = $UserData[0];
$DBUserName = $UserData[2];
$RUNTIME['CACHE']['USERNAME'][$userID] = $DBUserName;
if($DBUserID == $userID)
return $DBUserName;
}
}
$statementFriends = $RUNTIME['PDO']->prepare("SELECT PrincipalID FROM Friends");
$statementFriends->execute(array($userID));
while($rowFriends = $statementFriends->fetch())
{
$UserData = explode(";", $rowFriends['PrincipalID']);
if(count($UserData) == 4)
{
$DBUserID = $UserData[0];
$DBUserName = $UserData[2];
$RUNTIME['CACHE']['USERNAME'][$userID] = $DBUserName;
if($DBUserID == $userID)
return $DBUserName;
}
}
return "Unknown User";
}
public function getUserUUID($UserName)
{
global $RUNTIME;
$statementUser = $RUNTIME['PDO']->prepare("SELECT PrincipalID,FirstName,LastName FROM UserAccounts");
$statementUser->execute();
while($rowUser = $statementUser->fetch())
{
$SQLUserName = $rowUser['FirstName']." ".$rowUser['LastName'];
if($SQLUserName == $UserName)
{
return $rowUser['PrincipalID'];
}
}
return null;
}
public function getRegionName($regionID)
{
global $RUNTIME;
$statementRegion = $RUNTIME['PDO']->prepare("SELECT regionName FROM regions WHERE uuid = ?");
$statementRegion->execute(array($regionID));
while($rowRegion = $statementRegion->fetch())
{
return $rowRegion['regionName'];
}
return "Unknown Region";
}
public function getPartner($userID)
{
global $RUNTIME;
$statement = $RUNTIME['PDO']->prepare("SELECT profilePartner FROM userprofile WHERE useruuid = ?");
$statement->execute(array($userID));
while($row = $statement->fetch())
{
if($row['profilePartner'] != "00000000-0000-0000-0000-000000000000")
return $row['profilePartner'];
}
return null;
}
public function allowOfflineIM($userID)
{
global $RUNTIME;
$statement = $RUNTIME['PDO']->prepare("SELECT imviaemail FROM usersettings WHERE useruuid = ?");
$statement->execute(array($userID));
while($row = $statement->fetch())
{
return strtoupper($row['imviaemail']);
}
return "FALSE";
}
public function getUserMail($userID)
{
global $RUNTIME;
$statement = $RUNTIME['PDO']->prepare("SELECT Email FROM UserAccounts WHERE PrincipalID = ?");
$statement->execute(array($userID));
while($row = $statement->fetch())
{
return $row['Email'];
}
return "";
}
public function getUserCount()
{
global $RUNTIME;
$statementUser = $RUNTIME['PDO']->prepare("SELECT COUNT(*) FROM UserAccounts");
$statementUser->execute();
return $statementUser->fetchColumn();
}
public function getRegionCount()
{
global $RUNTIME;
$statementUser = $RUNTIME['PDO']->prepare("SELECT COUNT(*) FROM regions");
$statementUser->execute();
return $statementUser->fetchColumn();
}
public function getOnlineCount()
{
global $RUNTIME;
$statementUser = $RUNTIME['PDO']->prepare("SELECT COUNT(*) FROM Presence");
$statementUser->execute();
return $statementUser->fetchColumn();
}
public function gen_uuid()
{
return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
// 32 bits for "time_low"
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
// 16 bits for "time_mid"
mt_rand( 0, 0xffff ),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 4
mt_rand( 0, 0x0fff ) | 0x4000,
// 16 bits, 8 bits for "clk_seq_hi_res",
// 8 bits for "clk_seq_low",
// two most significant bits holds zero and one for variant DCE1.1
mt_rand( 0, 0x3fff ) | 0x8000,
// 48 bits for "node"
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
);
}
}
?>

View File

@ -1,19 +0,0 @@
<?php
function sendMessageToWebhook($webhook, $title, $message)
{
$RAWmessage = file_get_contents("style/discordMessage.json");
$RAWmessage = str_replace("%%message%%", $message, $RAWmessage);
$RAWmessage = str_replace("%%title%%", $title, $RAWmessage);
$options = [
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/json',
'timeout' => 3,
'content' => $RAWmessage
]
];
$result = file_get_contents($webhook, false, stream_context_create($options));
}
?>

View File

@ -1,42 +0,0 @@
<?php
function fillString($string, $targetlength)
{
while(strlen($string) < $targetlength)
{
$string = "0".$string;
}
return $string;
}
function left($str, $length)
{
return substr($str, 0, $length);
}
function right($str, $length)
{
return substr($str, -$length);
}
function getDataFromHTTP($URL, $contend = "", $requestTyp = "application/text")
{
try
{
if($contend != "")
{
return @file_get_contents($URL, true, stream_context_create(array('http' => array('header' => 'Content-type: '.$requestTyp, 'method' => 'POST', 'timeout' => 0.5, 'content' => $contend))));
}else{
return @file_get_contents($URL);
}
} catch (Exception $e) {
echo "(HTTP REQUEST) error while conntect to remote server. : ".$URL;
}
}
function sendInworldIM($fromUUID, $toUUID, $fromName, $targetURL, $text)
{
$rawXML = "<?xml version=\"1.0\" encoding=\"utf-8\"?><methodCall><methodName>grid_instant_message</methodName><params><param><value><struct><member><name>position_x</name><value><string>0</string></value></member><member><name>position_y</name><value><string>0</string></value></member><member><name>position_z</name><value><string>0</string></value></member><member><name>to_agent_id</name><value><string>".$toUUID."</string></value></member><member><name>from_agent_session</name><value><string>00000000-0000-0000-0000-000000000000</string></value></member><member><name>im_session_id</name><value><string>".$fromUUID."</string></value></member><member><name>from_agent_name</name><value><string>".$fromName."</string></value></member><member><name>from_agent_id</name><value><string>".$fromUUID."</string></value></member><member><name>binary_bucket</name><value><string>AA==</string></value></member><member><name>region_handle</name><value><i4>0</i4></value></member><member><name>region_id</name><value><string>00000000-0000-0000-0000-000000000000</string></value></member><member><name>parent_estate_id</name><value><string>1</string></value></member><member><name>timestamp</name><value><string>".time()."</string></value></member><member><name>dialog</name><value><string>AA==</string></value></member><member><name>offline</name><value><string>AA==</string></value></member><member><name>from_group</name><value><string>FALSE</string></value></member><member><name>message</name><value><string>".$text."</string></value></member></struct></value></param></params></methodCall>";
getDataFromHTTP($targetURL, $rawXML, "text/xml");
}
?>

48
config.example.ini Normal file
View File

@ -0,0 +1,48 @@
[general]
; Domain, unter der das MCP erreichbar ist
domain = example.com
; Mindestlänge des Passworts, wird bei Registrierung und Änderung überprüft
password-min-length = 8
; URL der Nutzungsbedingungen des Grids
tos-url = https://example.com/tos.html
; Name und UUID eines oder mehrerer Standardavatare
default-avatar[Twinster Kid] = 0817c915-293a-4041-b5a4-c7c53666bcc6
; Liste von Domains, deren E-Mail-Adressen als nicht für Password-Reset-Mails nutzbar zu behandeln sind
reset-blocked-domains[] = example.com
reset-blocked-domains[] = invalid.net
; Art der Einschränkung des Aufrufs von Cronjobs: key (API-Key benötigt) oder none (keine Einschränkung)
cron-restriction = key
; API-Key, der zum erfolgreichen Aufruf von Cronjobs übermittelt werden muss.
cron-key = changeme
; Host, Name und Zugangsdaten zur MySQL-Datenbank. Muss mit der Datenbank der Robust-Instanz übereinstimmen.
[mysql]
host = localhost
db = Robust
user = OpenSim
password = secret
; Zugangsdaten zum Mailserver
[smtp]
host = smtp.example.com
port = 465
address = noreply@example.com
; Name, der neben der Absenderadresse stehen soll
name = MyGrid Support
password = secret
; Daten des Grids, die u.a. auf der Welcome-Page angezeigt werden
[grid]
name = OpenSim
main-news = Yet another OpenSim Grid.
homeurl = http://...:8002
; Benötigt für die Anforderung von IARs
[iarfetcher]
; Zugangsdaten der REST-Konsole von OpenSimulator
host = example.com
port = 9001 ; Einstellung console_port
user = mcp
password = secret
; IAR-Verzeichnis aus Sicht der OpenSimulator-Instanz
os-iar-path = /opt/opensim/iars/

View File

@ -1,25 +0,0 @@
<?php
$RUNTIME['PDO'] = new PDO('mysql:host=...;dbname=...', '...', '...');
$RUNTIME['GRID']['NAME'] = "OpenSim";
$RUNTIME['GRID']['MAIN_NEWS'] = "Yet an other OpenSim Grid.";
$RUNTIME['GRID']['HOMEURL'] = "http://...:8002";
$RUNTIME['SMTP']['SERVER'] = "localhost";
$RUNTIME['SMTP']['PORT'] = 25;
$RUNTIME['SMTP']['ADRESS'] = "noreplay@localhost";
$RUNTIME['SMTP']['USER'] = "noreplay@localhost";
$RUNTIME['SMTP']['PASS'] = "...";
$RUNTIME['TOOLS']['IMAGESERVICE'] = "https://image-service.4creative.net/";
$RUNTIME['TOOLS']['TOS'] = "https://4creative.net/nutzung.html";
$RUNTIME['DEFAULTAVATAR']["AVATAR1"]['UUID'] = "0817c915-293a-4041-b5a4-c7c53666bcc6";
$RUNTIME['SIDOMAN']['URL'] = "https://sidoman.4creative.net/";
$RUNTIME['SIDOMAN']['CONTAINER'] = "...";
$RUNTIME['SIDOMAN']['PASSWORD'] = "...";
$RUNTIME['DOMAIN'] = "mcp.4creative.net";
$RUNTIME['IAR']['BASEURL'] = "https://mcp.4creative.net/data/";
?>

View File

@ -1,61 +0,0 @@
<?php
$statement = $RUNTIME['PDO']->prepare("CREATE TABLE IF NOT EXISTS `iarstates` (`userID` VARCHAR(36) NOT NULL COLLATE 'utf8_unicode_ci', `filesize` BIGINT(20) NOT NULL DEFAULT '0', `iarfilename` VARCHAR(64) NOT NULL COLLATE 'utf8_unicode_ci', `running` INT(1) NOT NULL DEFAULT '0', PRIMARY KEY (`userID`) USING BTREE) COLLATE='utf8_unicode_ci' ENGINE=InnoDB;");
$statement->execute();
$statement = $RUNTIME['PDO']->prepare("SELECT userID,iarfilename,filesize FROM iarstates WHERE running = 1 LIMIT 1");
$statement->execute();
if($row = $statement->fetch())
{
$email = $RUNTIME['OPENSIM']->getUserMail($row['userID']);
$fullFilePath = "/var/www/html/data/".$row['iarfilename'];
echo "Aktive IAR für ".$RUNTIME['OPENSIM']->getUserName($row['userID'])." gefunden. File: ".$fullFilePath."\n";
if(file_exists($fullFilePath))
{
$filesize = filesize($fullFilePath);
if($filesize != $row['filesize'])
{
$statementUpdate = $RUNTIME['PDO']->prepare('UPDATE iarstates SET filesize = :filesize WHERE userID = :userID');
$statementUpdate->execute(['filesize' => $filesize, 'userID' => $row['userID']]);
echo "Status der IAR für ".$RUNTIME['OPENSIM']->getUserName($row['userID']).": Speichert...\n";
}else{
$APIURL = $RUNTIME['SIDOMAN']['URL']."api.php?CONTAINER=".$RUNTIME['SIDOMAN']['CONTAINER']."&KEY=".$RUNTIME['SIDOMAN']['PASSWORD']."&METODE=RESTART";
$APIResult = file_get_contents($APIURL);
echo "Status der IAR für ".$RUNTIME['OPENSIM']->getUserName($row['userID']).": Sende Mail...\n";
$statementUpdate = $RUNTIME['PDO']->prepare('DELETE FROM iarstates WHERE userID = :userID');
$statementUpdate->execute(['userID' => $row['userID']]);
sendInworldIM("00000000-0000-0000-0000-000000000000", $row['userID'], "Inventory", $RUNTIME['GRID']['HOMEURL'], "Deine IAR ist fertig zum Download: ".$RUNTIME['IAR']['BASEURL'].$row['iarfilename']);
}
}else{
$name = explode(" ", $RUNTIME['OPENSIM']->getUserName($row['userID']));
$APIURL = $RUNTIME['SIDOMAN']['URL']."api.php?CONTAINER=".$RUNTIME['SIDOMAN']['CONTAINER']."&KEY=".$RUNTIME['SIDOMAN']['PASSWORD']."&METODE=COMMAND&COMMAND=".urlencode("save iar ".$name[0]." ".$name[1]." /* PASSWORD /downloads/".$row['iarfilename']);
$APIResult = file_get_contents($APIURL);
echo "IAR für ".$name[0]." ".$name[1]." wurde gestartet: Status: ".$APIResult."\n";
}
}else{
$statement = $RUNTIME['PDO']->prepare("SELECT userID,iarfilename FROM iarstates WHERE running = 0 LIMIT 1");
$statement->execute();
while($row = $statement->fetch())
{
$statementUpdate = $RUNTIME['PDO']->prepare('UPDATE iarstates SET running = :running WHERE userID = :userID');
$statementUpdate->execute(['running' => 1, 'userID' => $row['userID']]);
$name = explode(" ", $RUNTIME['OPENSIM']->getUserName($row['userID']));
$APIURL = $RUNTIME['SIDOMAN']['URL']."api.php?CONTAINER=".$RUNTIME['SIDOMAN']['CONTAINER']."&KEY=".$RUNTIME['SIDOMAN']['PASSWORD']."&METODE=COMMAND&COMMAND=".urlencode("save iar ".$name[0]." ".$name[1]." /* PASSWORD /downloads/".$row['iarfilename']);
$APIResult = file_get_contents($APIURL);
echo "IAR für ".$name[0]." ".$name[1]." wurde gestartet: Status: ".$APIResult."\n";
}
}
?>

View File

@ -1,29 +0,0 @@
<?php
$statement = $RUNTIME['PDO']->prepare("SELECT id,hash FROM fsassets ORDER BY create_time DESC");
$statement->execute();
$count = 0;
while($row = $statement->fetch())
{
$fileNameParts = array();
$fileNameParts[0] = substr($row['hash'], 0, 2);
$fileNameParts[1] = substr($row['hash'], 2, 2);
$fileNameParts[2] = substr($row['hash'], 4, 2);
$fileNameParts[3] = substr($row['hash'], 6, 4);
$fileNameParts[4] = $row['hash'].".gz";
//$fileNameParts['Time'] = time();
$fileNameParts['UUID'] = $row['id'];
$fileNameParts['FilePath'] = "/data/assets/base/".$fileNameParts[0]."/".$fileNameParts[1]."/".$fileNameParts[2]."/".$fileNameParts[3]."/".$fileNameParts[4];
$fileNameParts['FileSize'] = @filesize($fileNameParts['FilePath']);
$fileNameParts['Count'] = $count++;
if($fileNameParts['FileSize'] == 0 || !file_exists($fileNameParts['FilePath']))
{
$add = $RUNTIME['PDO']->prepare('DELETE FROM fsassets WHERE hash = :fileHash');
$add->execute(['fileHash' => $row['hash']]);
}
}
?>

View File

@ -1,21 +0,0 @@
<?php
$InventarCheckStatement = $RUNTIME['PDO']->prepare("UPDATE inventoryitems i SET
i.inventoryName = concat('[DEFEKT] ', i.inventoryName)
WHERE
i.assetID IN (
SELECT
i.assetID
FROM inventoryitems i
WHERE
NOT EXISTS( SELECT *
FROM fsassets fs
WHERE
fs.id = i.assetID
)
AND NOT i.inventoryName LIKE '[DEFEKT] %'
AND i.assetType <> 24
)");
$InventarCheckStatement->execute();
?>

View File

@ -1,99 +0,0 @@
<?php
$statement = $RUNTIME['PDO']->prepare("CREATE TABLE IF NOT EXISTS im_offline_send (`id` int(6) NOT NULL DEFAULT 0) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci");
$statement->execute();
function isMailAllreadySend($id)
{
GLOBAL $RUNTIME;
$statement = $RUNTIME['PDO']->prepare("SELECT 1 FROM im_offline_send WHERE id = ? LIMIT 1");
$statement->execute(array($id));
if($statement->rowCount() != 0)
return TRUE;
return FALSE;
}
$IMTYP = array(
"0" => "eine Nachricht",
"3" => "eine Gruppeneinladung",
"4" => "ein Inventaritem",
"5" => "eine Bestätigung zur Annahme von Inventar",
"6" => "eine Information zur Ablehnung von Inventar",
"7" => "eine Aufforderung zur Gruppenwahl",
"9" => "ein Inventaritem von einem Script",
"19" => "eine Nachricht von einem Script",
"32" => "eine Gruppennachricht",
"38" => "eine Freundschaftsanfrage",
"39" => "eine Bestätigung über die Annahme der Freundschaft",
"40" => "eine Information über das Ablehnen der Freundschaft"
);
//$statement = $RUNTIME['PDO']->prepare("SELECT * FROM im_offline WHERE PrincipalID = '1148b04d-7a93-49e9-b3c9-ea0cdeec38f7'");
$statement = $RUNTIME['PDO']->prepare("SELECT ID,PrincipalID,Message FROM im_offline");
$statement->execute();
while($row = $statement->fetch())
{
$email = $RUNTIME['OPENSIM']->getUserMail($row['PrincipalID']);
$allowOfflineIM = $RUNTIME['OPENSIM']->allowOfflineIM($row['PrincipalID']);
if($email != "" && $allowOfflineIM == "TRUE")
{
if(isMailAllreadySend($row['ID']) == FALSE)
{
$statementSend = $RUNTIME['PDO']->prepare('INSERT INTO im_offline_send (id) VALUES (:idnummer)');
$statementSend->execute(['idnummer' => $row['ID']]);
$mail = new PHPMailer(true);
$mail->SMTPDebug = SMTP::DEBUG_SERVER;
$mail->isSMTP();
$mail->Host = $RUNTIME['SMTP']['SERVER'];
$mail->Port = $RUNTIME['SMTP']['PORT'];
$mail->SMTPAuth = false;
$mail->setFrom($RUNTIME['SMTP']['ADRESS'], $RUNTIME['GRID']['NAME']);
$mail->addAddress($email, $RUNTIME['OPENSIM']->getUserName($row['PrincipalID']));
$XMLMESSAGE = new SimpleXMLElement($row['Message']);
$HTMLMESSAGE = "Du hast ".$IMTYP["".$XMLMESSAGE->dialog.""]." in ".$RUNTIME['GRID']['NAME']." bekommen. <br><p><ul><li>".htmlspecialchars($XMLMESSAGE->message)."</li></ul></p>Gesendet von: ";
if(isset($XMLMESSAGE->fromAgentName))
$HTMLMESSAGE .= $XMLMESSAGE->fromAgentName;
if(isset($XMLMESSAGE->RegionID) && isset($XMLMESSAGE->Position))
{
if($XMLMESSAGE->Position->X != 0 || $XMLMESSAGE->Position->X != 0 || $XMLMESSAGE->Position->X != 0)
{
$HTMLMESSAGE .= " @ ".$RUNTIME['OPENSIM']->getRegionName($XMLMESSAGE->RegionID)."/".$XMLMESSAGE->Position->X."/".$XMLMESSAGE->Position->Y."/".$XMLMESSAGE->Position->Z;
}else{
$HTMLMESSAGE .= " @ ".$RUNTIME['OPENSIM']->getRegionName($XMLMESSAGE->RegionID);
}
}
//die($HTMLMESSAGE);
$HTML = new HTML();
$HTML->importHTML("style/mail.html");
$HTML->setSeitenInhalt($HTMLMESSAGE);
$HTML->build();
$mail->isHTML(true);
$mail->Subject = "Du hast ".$IMTYP["".$XMLMESSAGE->dialog.""]." in ".$RUNTIME['GRID']['NAME'].".";
$mail->Body = $HTML->ausgabe();
$mail->AltBody = strip_tags($HTMLMESSAGE);
//print_r($mail);
$mail->send();
}else{
//echo $row['ID']." wurde bereits gesendet.";
}
}else{
//echo $row['PrincipalID']." möchte keine offline IM oder hat keine E-MAIL Adresse hinterlegt.";
}
}
?>

View File

@ -1,48 +0,0 @@
<?php
$createStatement = $RUNTIME['PDO']->prepare("CREATE TABLE IF NOT EXISTS `regions_info` (`regionID` VARCHAR(36) NOT NULL COLLATE 'utf8_unicode_ci', `RegionVersion` VARCHAR(128) NOT NULL DEFAULT '' COLLATE 'utf8_unicode_ci', `ProcMem` INT(11) NOT NULL, `Prims` INT(11) NOT NULL, `SimFPS` INT(11) NOT NULL, `PhyFPS` INT(11) NOT NULL, `OfflineTimer` INT(11) NOT NULL DEFAULT '0', PRIMARY KEY (`regionID`) USING BTREE) COLLATE='utf8_unicode_ci' ENGINE=InnoDB;");
$createStatement->execute();
$statement = $RUNTIME['PDO']->prepare("SELECT uuid,regionName,owner_uuid,serverURI FROM regions");
$statement->execute();
ini_set('default_socket_timeout', 3);
$ctx = stream_context_create(array('http'=>
array(
'timeout' => 3,
)
));
while($row = $statement->fetch())
{
$result = @file_get_contents($row['serverURI']."jsonSimStats", false, $ctx);
if($result == FALSE || $result == "")
{
echo "Die Region ".$row['regionName']." von ".$RUNTIME['OPENSIM']->getUserName($row['owner_uuid'])." ist nicht erreichbar.\n";
$infoStatement = $RUNTIME['PDO']->prepare("SELECT OfflineTimer FROM regions_info WHERE regionID = :regionID");
$infoStatement->execute(['regionID' => $row['uuid']]);
if($infoRow = $infoStatement->fetch())
{
if(($infoRow['OfflineTimer'] + 3600) <= time())
{
echo "Die Region ".$row['regionName']." von ".$RUNTIME['OPENSIM']->getUserName($row['owner_uuid'])." ist seit über eine Stunde nicht erreichbar!\n";
//sendInworldIM("00000000-0000-0000-0000-000000000000", $row['owner_uuid'], "Region", $RUNTIME['GRID']['HOMEURL'], "WARNUNG: Deine Region '".$row['regionName']."' ist nicht erreichbar und wurde deshalb aus dem Grid entfernt.");
//$statementUpdate = $RUNTIME['PDO']->prepare('DELETE FROM regions WHERE uuid = :uuid');
//$statementUpdate->execute(['uuid' => $row['uuid']]);
}else{
//sendInworldIM("00000000-0000-0000-0000-000000000000", $row['owner_uuid'], "Region", $RUNTIME['GRID']['HOMEURL'], "WARNUNG: Deine Region '".$row['regionName']."' ist nicht erreichbar!");
}
}
}else{
$regionData = json_decode($result);
$statementAccounts = $RUNTIME['PDO']->prepare('REPLACE INTO `regions_info` (`regionID`, `RegionVersion`, `ProcMem`, `Prims`, `SimFPS`, `PhyFPS`, `OfflineTimer`) VALUES (:regionID, :RegionVersion, :ProcMem, :Prims, :SimFPS, :PhyFPS, :OfflineTimer)');
$statementAccounts->execute(['regionID' => $row['uuid'], 'RegionVersion' => $regionData->Version, 'ProcMem' => $regionData->ProcMem, 'Prims' => $regionData->Prims, 'SimFPS' => $regionData->SimFPS, 'PhyFPS' => $regionData->PhyFPS, 'OfflineTimer' => time()]);
}
}
?>

View File

@ -1,34 +0,0 @@
<?php
date_default_timezone_set("Europe/Berlin");
error_reporting(E_ALL);
session_start();
include_once 'classen/MAIL/PHPMailer.php';
include_once 'classen/MAIL/SMTP.php';
include_once("classen/utils.php");
include_once("classen/HTML.php");
include_once("classen/GoogleAuthenticator.php");
include_once("classen/OpenSim.php");
include_once("classen/discord.php");
$RUNTIME = array();
$RUNTIME['OPENSIM'] = new OpenSim();
include_once("config.php");
if ($handle = opendir('./cron/'))
{
while (false !== ($entry = readdir($handle)))
{
if ($entry != "." && $entry != "..")
{
include_once "./cron/".$entry;
}
}
closedir($handle);
}
?>

View File

View File

@ -1,79 +0,0 @@
<?php
date_default_timezone_set("Europe/Berlin");
error_reporting(E_ALL);
include_once("config.php");
session_set_cookie_params([
'lifetime' => 86400,
'path' => '/',
'domain' => $RUNTIME['DOMAIN'],
'httponly' => true,
'secure' => true,
'samesite' => 'Lax'
]);
session_start();
if(!isset($_SESSION['csrf']) || strlen($_SESSION['csrf']) != 64) {
$_SESSION['csrf'] = bin2hex(random_bytes(32));
}
include_once 'classen/MAIL/PHPMailer.php';
include_once 'classen/MAIL/SMTP.php';
include_once("classen/utils.php");
include_once("classen/HTML.php");
include_once("classen/GoogleAuthenticator.php");
include_once("classen/OpenSim.php");
include_once("classen/discord.php");
$RUNTIME['OPENSIM'] = new OpenSim();
function isValidEndpoint(string $pageName, string $dirPrefix) {
return preg_match("/[a-zA-Z0-9\.]{1,100}/", $pageName) && file_exists("./".$dirPrefix."/".$pageName.".php");
}
//TODO: add API keys and/or rate limiting
if(isset($_REQUEST['api'])) {
if(isValidEndpoint($_REQUEST['api'], 'api')) {
include "./api/".$_REQUEST['api'].".php";
} else {
die("ERROR; ENDPOINT NOT EXIST");
}
die();
}
if ($handle = opendir('./plugins/')) {
while (false !== ($entry = readdir($handle))) {
if ($entry != "." && $entry != "..") {
include_once "./plugins/".$entry;
}
}
closedir($handle);
}
if(isset($_REQUEST['logout']) && $_REQUEST['logout'] == '1') {
$_SESSION = array();
header('Location: index.php');
}
if(isset($_SESSION['LOGIN']) && $_SESSION['LOGIN'] == 'true') {
if(!isset($_REQUEST['page'])) {
include './pages/dashboard.php';
} else if(isValidEndpoint($_REQUEST['page'], 'pages')) {
include "./pages/".$_REQUEST['page'].".php";
} else {
include "./pages/error.php";
}
die();
}
if(isset($_REQUEST['page']) && $_REQUEST['page'] == "register") {
include "./pages/register.php";
} else {
include "./pages/login.php";
}
?>

View File

@ -0,0 +1,247 @@
<?php
/**
* PHPMailer - PHP email creation and transport class.
* PHP Version 5.5.
*
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
*
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
* @author Brent R. Matzelle (original founder)
* @copyright 2012 - 2023 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*/
namespace PHPMailer\PHPMailer;
/**
* Configure PHPMailer with DSN string.
*
* @see https://en.wikipedia.org/wiki/Data_source_name
*
* @author Oleg Voronkovich <oleg-voronkovich@yandex.ru>
*/
class DSNConfigurator
{
/**
* Create new PHPMailer instance configured by DSN.
*
* @param string $dsn DSN
* @param bool $exceptions Should we throw external exceptions?
*
* @return PHPMailer
*/
public static function mailer($dsn, $exceptions = null)
{
static $configurator = null;
if (null === $configurator) {
$configurator = new DSNConfigurator();
}
return $configurator->configure(new PHPMailer($exceptions), $dsn);
}
/**
* Configure PHPMailer instance with DSN string.
*
* @param PHPMailer $mailer PHPMailer instance
* @param string $dsn DSN
*
* @return PHPMailer
*/
public function configure(PHPMailer $mailer, $dsn)
{
$config = $this->parseDSN($dsn);
$this->applyConfig($mailer, $config);
return $mailer;
}
/**
* Parse DSN string.
*
* @param string $dsn DSN
*
* @throws Exception If DSN is malformed
*
* @return array Configuration
*/
private function parseDSN($dsn)
{
$config = $this->parseUrl($dsn);
if (false === $config || !isset($config['scheme']) || !isset($config['host'])) {
throw new Exception(
sprintf('Malformed DSN: "%s".', $dsn)
);
}
if (isset($config['query'])) {
parse_str($config['query'], $config['query']);
}
return $config;
}
/**
* Apply configuration to mailer.
*
* @param PHPMailer $mailer PHPMailer instance
* @param array $config Configuration
*
* @throws Exception If scheme is invalid
*/
private function applyConfig(PHPMailer $mailer, $config)
{
switch ($config['scheme']) {
case 'mail':
$mailer->isMail();
break;
case 'sendmail':
$mailer->isSendmail();
break;
case 'qmail':
$mailer->isQmail();
break;
case 'smtp':
case 'smtps':
$mailer->isSMTP();
$this->configureSMTP($mailer, $config);
break;
default:
throw new Exception(
sprintf(
'Invalid scheme: "%s". Allowed values: "mail", "sendmail", "qmail", "smtp", "smtps".',
$config['scheme']
)
);
}
if (isset($config['query'])) {
$this->configureOptions($mailer, $config['query']);
}
}
/**
* Configure SMTP.
*
* @param PHPMailer $mailer PHPMailer instance
* @param array $config Configuration
*/
private function configureSMTP($mailer, $config)
{
$isSMTPS = 'smtps' === $config['scheme'];
if ($isSMTPS) {
$mailer->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
}
$mailer->Host = $config['host'];
if (isset($config['port'])) {
$mailer->Port = $config['port'];
} elseif ($isSMTPS) {
$mailer->Port = SMTP::DEFAULT_SECURE_PORT;
}
$mailer->SMTPAuth = isset($config['user']) || isset($config['pass']);
if (isset($config['user'])) {
$mailer->Username = $config['user'];
}
if (isset($config['pass'])) {
$mailer->Password = $config['pass'];
}
}
/**
* Configure options.
*
* @param PHPMailer $mailer PHPMailer instance
* @param array $options Options
*
* @throws Exception If option is unknown
*/
private function configureOptions(PHPMailer $mailer, $options)
{
$allowedOptions = get_object_vars($mailer);
unset($allowedOptions['Mailer']);
unset($allowedOptions['SMTPAuth']);
unset($allowedOptions['Username']);
unset($allowedOptions['Password']);
unset($allowedOptions['Hostname']);
unset($allowedOptions['Port']);
unset($allowedOptions['ErrorInfo']);
$allowedOptions = \array_keys($allowedOptions);
foreach ($options as $key => $value) {
if (!in_array($key, $allowedOptions)) {
throw new Exception(
sprintf(
'Unknown option: "%s". Allowed values: "%s"',
$key,
implode('", "', $allowedOptions)
)
);
}
switch ($key) {
case 'AllowEmpty':
case 'SMTPAutoTLS':
case 'SMTPKeepAlive':
case 'SingleTo':
case 'UseSendmailOptions':
case 'do_verp':
case 'DKIM_copyHeaderFields':
$mailer->$key = (bool) $value;
break;
case 'Priority':
case 'SMTPDebug':
case 'WordWrap':
$mailer->$key = (int) $value;
break;
default:
$mailer->$key = $value;
break;
}
}
}
/**
* Parse a URL.
* Wrapper for the built-in parse_url function to work around a bug in PHP 5.5.
*
* @param string $url URL
*
* @return array|false
*/
protected function parseUrl($url)
{
if (\PHP_VERSION_ID >= 50600 || false === strpos($url, '?')) {
return parse_url($url);
}
$chunks = explode('?', $url);
if (is_array($chunks)) {
$result = parse_url($chunks[0]);
if (is_array($result)) {
$result['query'] = $chunks[1];
}
return $result;
}
return false;
}
}

View File

@ -1,4 +1,5 @@
<?php
/**
* PHPMailer Exception class.
* PHP Version 5.5.
@ -9,7 +10,7 @@
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
* @author Brent R. Matzelle (original founder)
* @copyright 2012 - 2017 Marcus Bointon
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
@ -18,6 +19,8 @@
* FITNESS FOR A PARTICULAR PURPOSE.
*/
namespace PHPMailer\PHPMailer;
/**
* PHPMailer exception handler.
*
@ -32,6 +35,6 @@ class Exception extends \Exception
*/
public function errorMessage()
{
return '<strong>' . htmlspecialchars($this->getMessage()) . "</strong><br />\n";
return '<strong>' . htmlspecialchars($this->getMessage(), ENT_COMPAT | ENT_HTML401) . "</strong><br />\n";
}
}

View File

@ -1,4 +1,5 @@
<?php
/**
* PHPMailer - PHP email creation and transport class.
* PHP Version 5.5.
@ -9,7 +10,7 @@
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
* @author Brent R. Matzelle (original founder)
* @copyright 2012 - 2015 Marcus Bointon
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
@ -18,6 +19,8 @@
* FITNESS FOR A PARTICULAR PURPOSE.
*/
namespace PHPMailer\PHPMailer;
use League\OAuth2\Client\Grant\RefreshToken;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Token\AccessToken;
@ -30,7 +33,7 @@ use League\OAuth2\Client\Token\AccessToken;
*
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
*/
class OAuth
class OAuth implements OAuthTokenProvider
{
/**
* An instance of the League OAuth Client Provider.
@ -120,7 +123,7 @@ class OAuth
*/
public function getOauth64()
{
// Get a new token if it's not available or has expired
//Get a new token if it's not available or has expired
if (null === $this->oauthToken || $this->oauthToken->hasExpired()) {
$this->oauthToken = $this->getToken();
}

View File

@ -0,0 +1,44 @@
<?php
/**
* PHPMailer - PHP email creation and transport class.
* PHP Version 5.5.
*
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
*
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
* @author Brent R. Matzelle (original founder)
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*/
namespace PHPMailer\PHPMailer;
/**
* OAuthTokenProvider - OAuth2 token provider interface.
* Provides base64 encoded OAuth2 auth strings for SMTP authentication.
*
* @see OAuth
* @see SMTP::authenticate()
*
* @author Peter Scopes (pdscopes)
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
*/
interface OAuthTokenProvider
{
/**
* Generate a base64-encoded OAuth token ensuring that the access token has not expired.
* The string to be base 64 encoded should be in the form:
* "user=<user_email_address>\001auth=Bearer <access_token>\001\001"
*
* @return string
*/
public function getOauth64();
}

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
<?php
/**
* PHPMailer POP-Before-SMTP Authentication Class.
* PHP Version 5.5.
@ -9,7 +10,7 @@
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
* @author Brent R. Matzelle (original founder)
* @copyright 2012 - 2019 Marcus Bointon
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
@ -18,6 +19,8 @@
* FITNESS FOR A PARTICULAR PURPOSE.
*/
namespace PHPMailer\PHPMailer;
/**
* PHPMailer POP-Before-SMTP Authentication Class.
* Specifically for PHPMailer to use for RFC1939 POP-before-SMTP authentication.
@ -43,7 +46,7 @@ class POP3
*
* @var string
*/
const VERSION = '6.1.5';
const VERSION = '6.8.0';
/**
* Default POP3 port number.
@ -60,12 +63,16 @@ class POP3
const DEFAULT_TIMEOUT = 30;
/**
* Debug display level.
* Options: 0 = no, 1+ = yes.
* POP3 class debug output mode.
* Debug output level.
* Options:
* @see POP3::DEBUG_OFF: No output
* @see POP3::DEBUG_SERVER: Server messages, connection/server errors
* @see POP3::DEBUG_CLIENT: Client and Server messages, connection/server errors
*
* @var int
*/
public $do_debug = 0;
public $do_debug = self::DEBUG_OFF;
/**
* POP3 mail server hostname.
@ -128,6 +135,28 @@ class POP3
*/
const LE = "\r\n";
/**
* Debug level for no output.
*
* @var int
*/
const DEBUG_OFF = 0;
/**
* Debug level to show server -> client messages
* also shows clients connection errors or errors from server
*
* @var int
*/
const DEBUG_SERVER = 1;
/**
* Debug level to show client -> server and server -> client messages.
*
* @var int
*/
const DEBUG_CLIENT = 2;
/**
* Simple static wrapper for all-in-one POP before SMTP.
*
@ -170,13 +199,13 @@ class POP3
public function authorise($host, $port = false, $timeout = false, $username = '', $password = '', $debug_level = 0)
{
$this->host = $host;
// If no port value provided, use default
//If no port value provided, use default
if (false === $port) {
$this->port = static::DEFAULT_PORT;
} else {
$this->port = (int) $port;
}
// If no timeout value provided, use default
//If no timeout value provided, use default
if (false === $timeout) {
$this->tval = static::DEFAULT_TIMEOUT;
} else {
@ -185,9 +214,9 @@ class POP3
$this->do_debug = $debug_level;
$this->username = $username;
$this->password = $password;
// Reset the error log
//Reset the error log
$this->errors = [];
// connect
//Connect
$result = $this->connect($this->host, $this->port, $this->tval);
if ($result) {
$login_result = $this->login($this->username, $this->password);
@ -197,7 +226,7 @@ class POP3
return true;
}
}
// We need to disconnect regardless of whether the login succeeded
//We need to disconnect regardless of whether the login succeeded
$this->disconnect();
return false;
@ -214,7 +243,7 @@ class POP3
*/
public function connect($host, $port = false, $tval = 30)
{
// Are we already connected?
//Are we already connected?
if ($this->connected) {
return true;
}
@ -227,22 +256,22 @@ class POP3
$port = static::DEFAULT_PORT;
}
// connect to the POP3 server
//Connect to the POP3 server
$errno = 0;
$errstr = '';
$this->pop_conn = fsockopen(
$host, // POP3 Host
$port, // Port #
$errno, // Error Number
$errstr, // Error Message
$host, //POP3 Host
$port, //Port #
$errno, //Error Number
$errstr, //Error Message
$tval
); // Timeout (seconds)
// Restore the error handler
); //Timeout (seconds)
//Restore the error handler
restore_error_handler();
// Did we connect?
//Did we connect?
if (false === $this->pop_conn) {
// It would appear not...
//It would appear not...
$this->setError(
"Failed to connect to server $host on port $port. errno: $errno; errstr: $errstr"
);
@ -250,14 +279,14 @@ class POP3
return false;
}
// Increase the stream time-out
//Increase the stream time-out
stream_set_timeout($this->pop_conn, $tval, 0);
// Get the POP3 server response
//Get the POP3 server response
$pop3_response = $this->getResponse();
// Check for the +OK
//Check for the +OK
if ($this->checkResponse($pop3_response)) {
// The connection is established and the POP3 server is talking
//The connection is established and the POP3 server is talking
$this->connected = true;
return true;
@ -279,6 +308,7 @@ class POP3
{
if (!$this->connected) {
$this->setError('Not connected to POP3 server');
return false;
}
if (empty($username)) {
$username = $this->username;
@ -287,11 +317,11 @@ class POP3
$password = $this->password;
}
// Send the Username
//Send the Username
$this->sendString("USER $username" . static::LE);
$pop3_response = $this->getResponse();
if ($this->checkResponse($pop3_response)) {
// Send the Password
//Send the Password
$this->sendString("PASS $password" . static::LE);
$pop3_response = $this->getResponse();
if ($this->checkResponse($pop3_response)) {
@ -307,7 +337,21 @@ class POP3
*/
public function disconnect()
{
$this->sendString('QUIT');
// If could not connect at all, no need to disconnect
if ($this->pop_conn === false) {
return;
}
$this->sendString('QUIT' . static::LE);
// RFC 1939 shows POP3 server sending a +OK response to the QUIT command.
// Try to get it. Ignore any failures here.
try {
$this->getResponse();
} catch (Exception $e) {
//Do nothing
}
//The QUIT command may cause the daemon to exit, which will kill our connection
//So ignore errors here
try {
@ -315,6 +359,10 @@ class POP3
} catch (Exception $e) {
//Do nothing
}
// Clean up attributes.
$this->connected = false;
$this->pop_conn = false;
}
/**
@ -327,7 +375,7 @@ class POP3
protected function getResponse($size = 128)
{
$response = fgets($this->pop_conn, $size);
if ($this->do_debug >= 1) {
if ($this->do_debug >= self::DEBUG_SERVER) {
echo 'Server -> Client: ', $response;
}
@ -344,7 +392,7 @@ class POP3
protected function sendString($string)
{
if ($this->pop_conn) {
if ($this->do_debug >= 2) { //Show client messages when debug >= 2
if ($this->do_debug >= self::DEBUG_CLIENT) { //Show client messages when debug >= 2
echo 'Client -> Server: ', $string;
}
@ -382,7 +430,7 @@ class POP3
protected function setError($error)
{
$this->errors[] = $error;
if ($this->do_debug >= 1) {
if ($this->do_debug >= self::DEBUG_SERVER) {
echo '<pre>';
foreach ($this->errors as $e) {
print_r($e);

View File

@ -1,4 +1,5 @@
<?php
/**
* PHPMailer RFC821 SMTP email transport class.
* PHP Version 5.5.
@ -9,7 +10,7 @@
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
* @author Brent R. Matzelle (original founder)
* @copyright 2012 - 2019 Marcus Bointon
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
@ -18,6 +19,8 @@
* FITNESS FOR A PARTICULAR PURPOSE.
*/
namespace PHPMailer\PHPMailer;
/**
* PHPMailer RFC821 SMTP email transport class.
* Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server.
@ -32,7 +35,7 @@ class SMTP
*
* @var string
*/
const VERSION = '6.1.5';
const VERSION = '6.8.0';
/**
* SMTP line break constant.
@ -48,6 +51,13 @@ class SMTP
*/
const DEFAULT_PORT = 25;
/**
* The SMTPs port to use if one is not specified.
*
* @var int
*/
const DEFAULT_SECURE_PORT = 465;
/**
* The maximum line length allowed by RFC 5321 section 4.5.3.1.6,
* *excluding* a trailing CRLF break.
@ -183,6 +193,9 @@ class SMTP
'Amazon_SES' => '/[\d]{3} Ok (.*)/',
'SendGrid' => '/[\d]{3} Ok: queued as (.*)/',
'CampaignMonitor' => '/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/',
'Haraka' => '/[\d]{3} Message Queued \((.*)\)/',
'ZoneMTA' => '/[\d]{3} Message queued as (.*)/',
'Mailjet' => '/[\d]{3} OK queued as (.*)/',
];
/**
@ -309,17 +322,11 @@ class SMTP
*/
public function connect($host, $port = null, $timeout = 30, $options = [])
{
static $streamok;
//This is enabled by default since 5.0.0 but some providers disable it
//Check this once and cache the result
if (null === $streamok) {
$streamok = function_exists('stream_socket_client');
}
// Clear errors to avoid confusion
//Clear errors to avoid confusion
$this->setError('');
// Make sure we are __not__ connected
//Make sure we are __not__ connected
if ($this->connected()) {
// Already connected, generate error
//Already connected, generate error
$this->setError('Already connected to a server');
return false;
@ -327,18 +334,66 @@ class SMTP
if (empty($port)) {
$port = self::DEFAULT_PORT;
}
// Connect to the SMTP server
//Connect to the SMTP server
$this->edebug(
"Connection: opening to $host:$port, timeout=$timeout, options=" .
(count($options) > 0 ? var_export($options, true) : 'array()'),
self::DEBUG_CONNECTION
);
$this->smtp_conn = $this->getSMTPConnection($host, $port, $timeout, $options);
if ($this->smtp_conn === false) {
//Error info already set inside `getSMTPConnection()`
return false;
}
$this->edebug('Connection: opened', self::DEBUG_CONNECTION);
//Get any announcement
$this->last_reply = $this->get_lines();
$this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
$responseCode = (int)substr($this->last_reply, 0, 3);
if ($responseCode === 220) {
return true;
}
//Anything other than a 220 response means something went wrong
//RFC 5321 says the server will wait for us to send a QUIT in response to a 554 error
//https://tools.ietf.org/html/rfc5321#section-3.1
if ($responseCode === 554) {
$this->quit();
}
//This will handle 421 responses which may not wait for a QUIT (e.g. if the server is being shut down)
$this->edebug('Connection: closing due to error', self::DEBUG_CONNECTION);
$this->close();
return false;
}
/**
* Create connection to the SMTP server.
*
* @param string $host SMTP server IP or host name
* @param int $port The port number to connect to
* @param int $timeout How long to wait for the connection to open
* @param array $options An array of options for stream_context_create()
*
* @return false|resource
*/
protected function getSMTPConnection($host, $port = null, $timeout = 30, $options = [])
{
static $streamok;
//This is enabled by default since 5.0.0 but some providers disable it
//Check this once and cache the result
if (null === $streamok) {
$streamok = function_exists('stream_socket_client');
}
$errno = 0;
$errstr = '';
if ($streamok) {
$socket_context = stream_context_create($options);
set_error_handler([$this, 'errorHandler']);
$this->smtp_conn = stream_socket_client(
$connection = stream_socket_client(
$host . ':' . $port,
$errno,
$errstr,
@ -346,7 +401,6 @@ class SMTP
STREAM_CLIENT_CONNECT,
$socket_context
);
restore_error_handler();
} else {
//Fall back to fsockopen which should work in more places, but is missing some features
$this->edebug(
@ -354,17 +408,18 @@ class SMTP
self::DEBUG_CONNECTION
);
set_error_handler([$this, 'errorHandler']);
$this->smtp_conn = fsockopen(
$connection = fsockopen(
$host,
$port,
$errno,
$errstr,
$timeout
);
restore_error_handler();
}
// Verify we connected properly
if (!is_resource($this->smtp_conn)) {
restore_error_handler();
//Verify we connected properly
if (!is_resource($connection)) {
$this->setError(
'Failed to connect to server',
'',
@ -379,22 +434,19 @@ class SMTP
return false;
}
$this->edebug('Connection: opened', self::DEBUG_CONNECTION);
// SMTP server can take longer to respond, give longer timeout for first read
// Windows does not have support for this timeout function
//SMTP server can take longer to respond, give longer timeout for first read
//Windows does not have support for this timeout function
if (strpos(PHP_OS, 'WIN') !== 0) {
$max = (int) ini_get('max_execution_time');
// Don't bother if unlimited
if (0 !== $max && $timeout > $max) {
$max = (int)ini_get('max_execution_time');
//Don't bother if unlimited, or if set_time_limit is disabled
if (0 !== $max && $timeout > $max && strpos(ini_get('disable_functions'), 'set_time_limit') === false) {
@set_time_limit($timeout);
}
stream_set_timeout($this->smtp_conn, $timeout, 0);
stream_set_timeout($connection, $timeout, 0);
}
// Get any announcement
$announce = $this->get_lines();
$this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
return true;
return $connection;
}
/**
@ -418,7 +470,7 @@ class SMTP
$crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
}
// Begin encrypted connection
//Begin encrypted connection
set_error_handler([$this, 'errorHandler']);
$crypto_ok = stream_socket_enable_crypto(
$this->smtp_conn,
@ -439,7 +491,7 @@ class SMTP
* @param string $username The user name
* @param string $password The password
* @param string $authtype The auth type (CRAM-MD5, PLAIN, LOGIN, XOAUTH2)
* @param OAuth $OAuth An optional OAuth instance for XOAUTH2 authentication
* @param OAuthTokenProvider $OAuth An optional OAuthTokenProvider instance for XOAUTH2 authentication
*
* @return bool True if successfully authenticated
*/
@ -456,11 +508,11 @@ class SMTP
}
if (array_key_exists('EHLO', $this->server_caps)) {
// SMTP extensions are available; try to find a proper authentication method
//SMTP extensions are available; try to find a proper authentication method
if (!array_key_exists('AUTH', $this->server_caps)) {
$this->setError('Authentication is not allowed at this stage');
// 'at this stage' means that auth may be allowed after the stage changes
// e.g. after STARTTLS
//'at this stage' means that auth may be allowed after the stage changes
//e.g. after STARTTLS
return false;
}
@ -504,22 +556,25 @@ class SMTP
}
switch ($authtype) {
case 'PLAIN':
// Start authentication
//Start authentication
if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
return false;
}
// Send encoded username and password
if (!$this->sendCommand(
'User & Password',
base64_encode("\0" . $username . "\0" . $password),
235
)
//Send encoded username and password
if (
//Format from https://tools.ietf.org/html/rfc4616#section-2
//We skip the first field (it's forgery), so the string starts with a null byte
!$this->sendCommand(
'User & Password',
base64_encode("\0" . $username . "\0" . $password),
235
)
) {
return false;
}
break;
case 'LOGIN':
// Start authentication
//Start authentication
if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
return false;
}
@ -531,17 +586,17 @@ class SMTP
}
break;
case 'CRAM-MD5':
// Start authentication
//Start authentication
if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
return false;
}
// Get the challenge
//Get the challenge
$challenge = base64_decode(substr($this->last_reply, 4));
// Build the response
//Build the response
$response = $username . ' ' . $this->hmac($challenge, $password);
// send encoded credentials
//send encoded credentials
return $this->sendCommand('Username', base64_encode($response), 235);
case 'XOAUTH2':
//The OAuth instance must be set up prior to requesting auth.
@ -550,7 +605,7 @@ class SMTP
}
$oauth = $OAuth->getOauth64();
// Start authentication
//Start authentication
if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
return false;
}
@ -580,15 +635,15 @@ class SMTP
return hash_hmac('md5', $data, $key);
}
// The following borrowed from
// http://php.net/manual/en/function.mhash.php#27225
//The following borrowed from
//http://php.net/manual/en/function.mhash.php#27225
// RFC 2104 HMAC implementation for php.
// Creates an md5 HMAC.
// Eliminates the need to install mhash to compute a HMAC
// by Lance Rushing
//RFC 2104 HMAC implementation for php.
//Creates an md5 HMAC.
//Eliminates the need to install mhash to compute a HMAC
//by Lance Rushing
$bytelen = 64; // byte length for md5
$bytelen = 64; //byte length for md5
if (strlen($key) > $bytelen) {
$key = pack('H*', md5($key));
}
@ -611,7 +666,7 @@ class SMTP
if (is_resource($this->smtp_conn)) {
$sock_status = stream_get_meta_data($this->smtp_conn);
if ($sock_status['eof']) {
// The socket is valid but we are not connected
//The socket is valid but we are not connected
$this->edebug(
'SMTP NOTICE: EOF caught while checking if connected',
self::DEBUG_CLIENT
@ -621,7 +676,7 @@ class SMTP
return false;
}
return true; // everything looks good
return true; //everything looks good
}
return false;
@ -635,11 +690,10 @@ class SMTP
*/
public function close()
{
$this->setError('');
$this->server_caps = null;
$this->helo_rply = null;
if (is_resource($this->smtp_conn)) {
// close the connection and cleanup
//Close the connection and cleanup
fclose($this->smtp_conn);
$this->smtp_conn = null; //Makes for cleaner serialization
$this->edebug('Connection: closed', self::DEBUG_CONNECTION);
@ -649,7 +703,7 @@ class SMTP
/**
* Send an SMTP DATA command.
* Issues a data command and sends the msg_data to the server,
* finializing the mail transaction. $msg_data is the message
* finalizing the mail transaction. $msg_data is the message
* that is to be send with the headers. Each header needs to be
* on a single line followed by a <CRLF> with the message headers
* and the message body being separated by an additional <CRLF>.
@ -674,7 +728,7 @@ class SMTP
* NOTE: this does not count towards line-length limit.
*/
// Normalize line breaks before exploding
//Normalize line breaks before exploding
$lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data));
/* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
@ -720,7 +774,8 @@ class SMTP
//Send the lines to the server
foreach ($lines_out as $line_out) {
//RFC2821 section 4.5.2
//Dot-stuffing as per RFC5321 section 4.5.2
//https://tools.ietf.org/html/rfc5321#section-4.5.2
if (!empty($line_out) && $line_out[0] === '.') {
$line_out = '.' . $line_out;
}
@ -754,7 +809,16 @@ class SMTP
public function hello($host = '')
{
//Try extended hello first (RFC 2821)
return $this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host);
if ($this->sendHello('EHLO', $host)) {
return true;
}
//Some servers shut down the SMTP service here (RFC 5321)
if (substr($this->helo_rply, 0, 3) == '421') {
return false;
}
return $this->sendHello('HELO', $host);
}
/**
@ -944,12 +1008,12 @@ class SMTP
$this->client_send($commandstring . static::LE, $command);
$this->last_reply = $this->get_lines();
// Fetch SMTP code and possible error code explanation
//Fetch SMTP code and possible error code explanation
$matches = [];
if (preg_match('/^([\d]{3})[ -](?:([\d]\\.[\d]\\.[\d]{1,2}) )?/', $this->last_reply, $matches)) {
$code = (int) $matches[1];
$code_ex = (count($matches) > 2 ? $matches[2] : null);
// Cut off error code from each response line
//Cut off error code from each response line
$detail = preg_replace(
"/{$code}[ -]" .
($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m',
@ -957,7 +1021,7 @@ class SMTP
$this->last_reply
);
} else {
// Fall back to simple parsing if regex fails
//Fall back to simple parsing if regex fails
$code = (int) substr($this->last_reply, 0, 3);
$code_ex = null;
$detail = substr($this->last_reply, 4);
@ -980,7 +1044,10 @@ class SMTP
return false;
}
$this->setError('');
//Don't clear the error store when using keepalive
if ($command !== 'RSET') {
$this->setError('');
}
return true;
}
@ -1056,8 +1123,10 @@ class SMTP
{
//If SMTP transcripts are left enabled, or debug output is posted online
//it can leak credentials, so hide credentials in all but lowest level
if (self::DEBUG_LOWLEVEL > $this->do_debug &&
in_array($command, ['User & Password', 'Username', 'Password'], true)) {
if (
self::DEBUG_LOWLEVEL > $this->do_debug &&
in_array($command, ['User & Password', 'Username', 'Password'], true)
) {
$this->edebug('CLIENT -> SERVER: [credentials hidden]', self::DEBUG_CLIENT);
} else {
$this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT);
@ -1111,7 +1180,7 @@ class SMTP
if (!$this->server_caps) {
$this->setError('No HELO/EHLO was sent');
return;
return null;
}
if (!array_key_exists($name, $this->server_caps)) {
@ -1123,7 +1192,7 @@ class SMTP
}
$this->setError('HELO handshake was used; No information about server extensions available');
return;
return null;
}
return $this->server_caps[$name];
@ -1150,7 +1219,7 @@ class SMTP
*/
protected function get_lines()
{
// If the connection is bad, give up straight away
//If the connection is bad, give up straight away
if (!is_resource($this->smtp_conn)) {
return '';
}
@ -1164,33 +1233,61 @@ class SMTP
$selW = null;
while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
//Must pass vars in here as params are by reference
if (!stream_select($selR, $selW, $selW, $this->Timelimit)) {
//solution for signals inspired by https://github.com/symfony/symfony/pull/6540
set_error_handler([$this, 'errorHandler']);
$n = stream_select($selR, $selW, $selW, $this->Timelimit);
restore_error_handler();
if ($n === false) {
$message = $this->getError()['detail'];
$this->edebug(
'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
'SMTP -> get_lines(): select failed (' . $message . ')',
self::DEBUG_LOWLEVEL
);
//stream_select returns false when the `select` system call is interrupted
//by an incoming signal, try the select again
if (stripos($message, 'interrupted system call') !== false) {
$this->edebug(
'SMTP -> get_lines(): retrying stream_select',
self::DEBUG_LOWLEVEL
);
$this->setError('');
continue;
}
break;
}
if (!$n) {
$this->edebug(
'SMTP -> get_lines(): select timed-out in (' . $this->Timelimit . ' sec)',
self::DEBUG_LOWLEVEL
);
break;
}
//Deliberate noise suppression - errors are handled afterwards
$str = @fgets($this->smtp_conn, self::MAX_REPLY_LENGTH);
$this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL);
$data .= $str;
// If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
// or 4th character is a space or a line break char, we are done reading, break the loop.
// String array access is a significant micro-optimisation over strlen
//If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
//or 4th character is a space or a line break char, we are done reading, break the loop.
//String array access is a significant micro-optimisation over strlen
if (!isset($str[3]) || $str[3] === ' ' || $str[3] === "\r" || $str[3] === "\n") {
break;
}
// Timed-out? Log and break
//Timed-out? Log and break
$info = stream_get_meta_data($this->smtp_conn);
if ($info['timed_out']) {
$this->edebug(
'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
'SMTP -> get_lines(): stream timed-out (' . $this->Timeout . ' sec)',
self::DEBUG_LOWLEVEL
);
break;
}
// Now check if reads took too long
//Now check if reads took too long
if ($endtime && time() > $endtime) {
$this->edebug(
'SMTP -> get_lines(): timelimit reached (' .

1665
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

33
package.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "mcp-frontend",
"description": "Frontend components for the 4Creative MCP",
"version": "1.0.0",
"private": true,
"scripts": {
"build": "npm run css && npm run js && npm run fonts",
"css-compile-dash": "sass --style compressed --no-error-css --no-source-map --load-path=node_modules scss/dashboard/sb-admin.scss:public/style/sb-admin.min.css",
"css-compile-login": "sass --style compressed --no-error-css --no-source-map --load-path=node_modules scss/login/login.scss:public/style/login.min.css",
"css-lint": "stylelint scss/",
"css-prefix": "postcss --replace public/style/* --use autoprefixer --no-map",
"css": "npm run css-compile-dash && npm run css-compile-login && npm run css-prefix",
"js": "cp node_modules/bootstrap/dist/js/bootstrap.bundle.min.js public/js/vendor/bootstrap.bundle.min.js && cp node_modules/jquery/dist/jquery.min.js public/js/vendor/jquery.min.js && cp node_modules/jquery.easing/jquery.easing.min.js public/js/vendor/jquery.easing.min.js",
"fonts": "cp -r node_modules/@fortawesome/fontawesome-free/webfonts public/fonts/fontawesome-free",
"server": "http-server ./public -c-1 -a 127.0.0.1",
"start": "npm run watch & npm run server",
"watch": "nodemon -e html,scss,js --ignore public/js -x \"npm run build\""
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.4.2",
"bootstrap": "^4.6.2",
"jquery": "^3.7.1",
"jquery.easing": "^1.4.1"
},
"devDependencies": {
"autoprefixer": "^10.4.14",
"http-server": "^14.1.1",
"nodemon": "^3.0.1",
"postcss": "^8.4.24",
"postcss-cli": "^10.1.0",
"sass": "^1.63.6"
}
}

View File

@ -1 +0,0 @@
Deny from all

View File

@ -1 +0,0 @@
%%REGION-LIST%%

View File

@ -1,41 +0,0 @@
<div>
Hier kannst du die UUID von deinem Avatar ändern und später jederzeit wieder zurück weckseln. <br>
Inventar und Gruppen bleiben dabei erhalten. <br>
Jede Identität hat ein eigenes Aussehen, ein eigenes Profile und eine eigene Freundesliste.<br>
Nach dem ändern musst du dich neu anmelden.<br>
</div>
<br>%%MESSAGE%%<br>
<div class="container-fluid">
<div class="row">
<div class="col-md-6">
%%IDENT-LIST%%
</div>
<div class="col-md-6">
<div style="width: 400px; margin: auto; left: 50%;">
Hier kannst du eine neue Identität erstellen.
</div>
<div style="width: 400px; margin: auto; left: 50%;">
<form action="index.php?page=identities" method="post">
<div class="row" style="margin-top: 15px;">
<div class="col">
<label for="newName">Name</label>
<input type="text" class="form-control" id="newName" name="newName" placeholder="Name">
</div>
</div>
<div class="row" style="margin-top: 15px;">
<div class="col">
%%CSRF%%
<button type="submit" name="createIdent" class="btn btn-primary btn-lg">Erstelle Identität</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@ -1,24 +0,0 @@
<div style="width: 400px; margin: auto; left: 50%;">
Hier kannst du einen Invite Link erstellen.<br>
Jeder der solch einen Link bekommt, kann sich im Grid Regestrieren.
Der Link ist einzigartig und funktioniert nur einmalig.<br>
Nach dem aufrufen des Links muss ein Name, Passwort und Standart Avatar ausgewählt werden.
</div>
<div style="width: 400px; margin: auto; left: 50%;">
<form action="index.php?page=invite" method="post">
<div class="row" style="margin-top: 15px;">
<div class="col">
<label for="linkOutput">InviteLink:</label>
<input type="text" class="form-control" id="linkOutput" name="formLink" value="%%link%%">
</div>
</div>
<div class="row" style="margin-top: 15px;">
<div class="col">
%%CSRF%%
<button type="submit" name="generateLink" class="btn btn-primary btn-lg">Link Generieren</button>
</div>
</div>
</form>
</div>

View File

@ -1 +0,0 @@
%%ONLINE-LIST%%

View File

@ -1,44 +0,0 @@
<center>%%INFOMESSAGE%%</center>
<div style="width: 400px; margin: auto; left: 50%;">
<form action="index.php?page=password" method="post">
<div class="row" style="margin-top: 15px;">
<div class="col">
<label for="oldPassword">Altes Passwort</label>
<input type="text" class="form-control" id="oldPassword" name="oldPassword"">
</div>
</div>
<div class="row" style="margin-top: 15px;">
<div class="col">
<label for="newPassword">Neues Passwort</label>
<input type="text" class="form-control" id="PasswordNew" name="newPassword"">
</div>
</div>
<div class="row" style="margin-top: 15px;">
<div class="col">
<label for="newPasswordRepeate">Neues Passwort wiederholen</label>
<input type="text" class="form-control" id="PasswordNewRepeate" name="newPasswordRepeate"">
</div>
</div>
<div class="row" style="margin-top: 15px;">
<div class="col">
<hr>
</div>
</div>
<div class="row" style="margin-top: 15px;">
<div class="col">
%%CSRF%%
<button type="submit" name="savePassword" class="btn btn-primary btn-lg">Speichern</button>
</div>
</div>
</form>
</div>

View File

@ -1,202 +0,0 @@
<html>
<head>
<style>
body {
background-color: rgb(0, 0, 0);
width: 100%;
height: 100%;
overflow: hidden;
font-family: 'Arial';
font-size: 11px;
padding: 0;
margin: 0;
}
.InfoBox {
width: 300px;
height: auto;
background: rgba(0, 0, 0, 0.85);
color: rgb(220, 220, 220);
padding: 10px;
}
.InfoBox a {
color: rgb(220, 220, 220);
text-decoration: underline;
}
.InfoBox a:hover {
color: rgb(255, 255, 255);
text-decoration: underline;
}
.InfoBoxTitle {
width: 100%;
height: auto;
padding: 0;
padding-bottom: 5px;
margin-bottom: 5px;
border: 0px dashed rgb(128, 128, 128);
border-bottom-width: 1px;
color: rgb(220, 220, 220);
font-weight: bold;
font-size: 14px;
}
.GridLogo {
position: absolute;
top: 50px;
left: 50px;
border: 0;
}
.ScrollBar::-webkit-scrollbar {
width: 3px;
}
.ScrollBar::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
}
.ScrollBar::-webkit-scrollbar-thumb {
background: rgba(38, 38, 38, 0.9);
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5);
}
</style>
</head>
<body>
<img src='' style='border: 0; display: block; opacity; 0; position: absolute;' id='Image1' />
<img src='' style='border: 0; display: block; opacity: 0; position: absolute;' id='Image2' />
<script type='text/javascript'>
var Images = %%JSONIMAGEARRAY%%;
var MakeAnimation = true;
var CurrentIndex = 2;
var CurrentImage = -1;
var ImageTimeout = 0;
var CurrentImageTranslation = -1;
var ImagePositions = [{'x': -50, 'y': -50, 'px': 1, 'py': 1},
{'x': -50, 'y': -50, 'px': 1, 'py': 1}];
document.getElementById('Image1').onload = function() {
if (MakeAnimation)
{
this.style.width = (window.innerWidth + 100) + 'px';
if (this.offsetHeight < window.innerHeight + 100)
{
this.style.width = null;
this.style.height = (window.innerHeight + 100) + 'px';
}
ImagePositions[0].x = -50;
ImagePositions[0].y = -50;
}
else
{
this.style.width = window.innerWidth + 'px';
if (this.offsetHeight < window.innerHeight)
{
this.style.width = null;
this.style.height = window.innerHeight + 'px';
}
ImagePositions[0].x = 0;
ImagePositions[0].y = 0;
}
CurrentImageTranslation = 0;
};
document.getElementById('Image2').onload = function() {
if (MakeAnimation)
{
this.style.width = (window.innerWidth + 100) + 'px';
if (this.offsetHeight < window.innerHeight + 100)
{
this.style.width = null;
this.style.height = (window.innerHeight + 100) + 'px';
}
ImagePositions[1].x = -50;
ImagePositions[1].y = -50;
}
else
{
this.style.width = window.innerWidth + 'px';
if (this.offsetHeight < window.innerHeight)
{
this.style.width = null;
this.style.height = window.innerHeight + 'px';
}
ImagePositions[0].x = 0;
ImagePositions[0].y = 0;
}
CurrentImageTranslation = 0;
};
window.setInterval(function() {
ImageTimeout = ImageTimeout - 1;
if (ImageTimeout <= 0)
{
ImageTimeout = 10;
CurrentImage = CurrentImage + 1;
if (CurrentImage >= Images.length)
{CurrentImage = 0;}
if (CurrentIndex == 1)
{CurrentIndex = 2;}
else
{CurrentIndex = 1;}
document.getElementById('Image' + CurrentIndex).style.width = null;
document.getElementById('Image' + CurrentIndex).style.height = null;
document.getElementById('Image' + CurrentIndex).src = Images[CurrentImage];
}
}, 1000);
window.setInterval(function() {
if (MakeAnimation)
{
for (var i = 0; i < 2; ++i)
{
ImagePositions[i].x = ImagePositions[i].x + ImagePositions[i].px;
ImagePositions[i].y = ImagePositions[i].y + ImagePositions[i].py;
var OffWidth = document.getElementById('Image' + (i + 1)).offsetWidth;
var OffHeight = document.getElementById('Image' + (i + 1)).offsetHeight;
if (ImagePositions[i].x >= 0 || ImagePositions[i].x + OffWidth <= window.innerWidth)
{ImagePositions[i].px = -ImagePositions[i].px;}
if (ImagePositions[i].y >= 0 || ImagePositions[i].y + OffHeight <= window.innerHeight)
{ImagePositions[i].py = -ImagePositions[i].py;}
document.getElementById('Image' + (i + 1)).style.left = ImagePositions[i].x + 'px';
document.getElementById('Image' + (i + 1)).style.top = ImagePositions[i].y + 'px';
}
}
if (CurrentImageTranslation > -1)
{
var DoReset = false;
CurrentImageTranslation = CurrentImageTranslation + 0.025;
if (CurrentImageTranslation >= 1.0)
{
CurrentImageTranslation = 1.0;
DoReset = true;
}
if (CurrentIndex == 1)
{
document.getElementById('Image1').style.opacity = CurrentImageTranslation;
document.getElementById('Image2').style.opacity = 1 - CurrentImageTranslation;
}
else
{
document.getElementById('Image2').style.opacity = CurrentImageTranslation;
document.getElementById('Image1').style.opacity = 1 - CurrentImageTranslation;
}
if (DoReset)
{CurrentImageTranslation = -1;}
}
}, 50);
</script>
<div class='InfoBox' style='position: absolute; right: 50px; top: 50px;'>
<div class='InfoBoxTitle'>%%GRIDNAME%%</div>
Willkommen<br />
Bitte melde dich an, um %%GRIDNAME%% zu betreten.<br />
<br />
%%SHOWNEWS%%
</div>
<div class='InfoBox' style='position: absolute; left: 50px; bottom: 50px;'>
<div class='InfoBoxTitle'>
Status: <span style='color: rgb(0, 255, 0);'>Online</span>
</div>
%%SHOWSTATS%%
</div>
</body>
</html>

View File

@ -1,12 +0,0 @@
<?php
$HTML->setHTMLTitle("Dashboard");
$HTML->importSeitenInhalt("pages/HTML/dashboard.html");
$HTML->ReplaceSeitenInhalt("%%GLOBAL-USER-COUNT%%", $RUNTIME['OPENSIM']->getUserCount());
$HTML->ReplaceSeitenInhalt("%%GLOBAL-REGION-COUNT%%", $RUNTIME['OPENSIM']->getRegionCount());
$HTML->ReplaceLayoutInhalt("%%USERNAME%%", htmlspecialchars($_SESSION['DISPLAYNAME']));
$HTML->build();
echo $HTML->ausgabe();
?>

View File

@ -1,7 +0,0 @@
<?php
$HTML->setHTMLTitle("Seite nicht gefunden");
$HTML->build();
http_response_code(404);
echo $HTML->ausgabe();
?>

View File

@ -1,46 +0,0 @@
<?php
if(@$_REQUEST['action'] == 'remove' && @$_REQUEST['uuid'] != '')
{
$statementMembership = $RUNTIME['PDO']->prepare("DELETE FROM Friends WHERE Friend = ? AND PrincipalID = ?");
$statementMembership->execute(array($_REQUEST['uuid'], $_SESSION['UUID']));
$statementMembership = $RUNTIME['PDO']->prepare("DELETE FROM Friends WHERE PrincipalID = ? AND Friend = ?");
$statementMembership->execute(array($_REQUEST['uuid'], $_SESSION['UUID']));
header('Location: index.php?page=friends');
die();
}
$HTML->setHTMLTitle("Online Anzeige");
$HTML->importSeitenInhalt("pages/HTML/online-anzeige.html");
$table = '<table class="table"><thead><tr><th scope="col">Name</th><th scope="col">Optionen</th></thead><tbody>%%ENTRY%%</tbody></table>';
$statement = $RUNTIME['PDO']->prepare("SELECT PrincipalID,Friend FROM Friends WHERE PrincipalID = ? ORDER BY Friend ASC");
$statement->execute([$_SESSION['UUID']]);
while($row = $statement->fetch())
{
$PrincipalID = explode(";", $row['PrincipalID'])[0];
$FriendData = explode(";", $row['Friend']);
$Friend = $FriendData[0];
$entry = '<tr><td>'.trim($RUNTIME['OPENSIM']->getUserName($Friend)).'</td><td><a href="index.php?page=friends&action=remove&uuid='.$row['Friend'].'">LÖSCHEN</a></td></tr>';
if(count($FriendData) > 1)
{
$FriendData[1] = str_replace("http://", "", $FriendData[1]);
$FriendData[1] = str_replace("https://", "", $FriendData[1]);
$FriendData[1] = str_replace("/", "", $FriendData[1]);
$entry = '<tr><td>'.htmlspecialchars(trim($RUNTIME['OPENSIM']->getUserName($Friend)).' @ '.strtolower($FriendData[1])).'</td><td><a href="index.php?page=friends&action=remove&uuid='.urlencode($row['Friend']).'">LÖSCHEN</a></td></tr>';
}
$table = str_replace("%%ENTRY%%", $entry."%%ENTRY%%", $table);
}
$table = str_replace("%%ENTRY%%", "", $table);
$HTML->ReplaceSeitenInhalt("%%ONLINE-LIST%%", $table);
$HTML->build();
echo $HTML->ausgabe();
?>

View File

@ -1,36 +0,0 @@
<?php
if(@$_REQUEST['action'] == 'leave' && @$_REQUEST['group'] != '')
{
$statementMembership = $RUNTIME['PDO']->prepare("DELETE FROM os_groups_membership WHERE GroupID = ? AND PrincipalID = ?");
$statementMembership->execute(array($_REQUEST['group'], $_SESSION['UUID']));
header('Location: index.php?page=groups');
die();
}
$HTML->setHTMLTitle("Gruppen");
$HTML->importSeitenInhalt("pages/HTML/deine-regionen.html");
$table = '<table class="table"><thead><tr><th scope="col">Name</th><th scope="col">Gründer</th><th scope="col">Aktionen</th></thead><tbody>%%ENTRY%%</tbody></table>';
$statementMembership = $RUNTIME['PDO']->prepare("SELECT GroupID FROM os_groups_membership WHERE PrincipalID = ? ORDER BY GroupID ASC");
$statementMembership->execute(array($_SESSION['UUID']));
while($rowMembership = $statementMembership->fetch())
{
$statementGroups = $RUNTIME['PDO']->prepare("SELECT Name,FounderID,GroupID FROM os_groups_groups WHERE GroupID = ? LIMIT 1");
$statementGroups->execute(array($rowMembership['GroupID']));
while($rowGroups = $statementGroups->fetch())
{
$entry = '<tr><td>'.htmlspecialchars($rowGroups['Name']).'</td><td>'.htmlspecialchars($RUNTIME['OPENSIM']->getUserName($rowGroups['FounderID'])).'</td><td><a href="index.php?page=groups&action=leave&group='.htmlspecialchars($rowGroups['GroupID']).'">VERLASSEN</a></td></tr>';
$table = str_replace("%%ENTRY%%", $entry."%%ENTRY%%", $table);
}
}
$table = str_replace("%%ENTRY%%", "", $table);
$HTML->ReplaceSeitenInhalt("%%REGION-LIST%%", $table);
$HTML->build();
echo $HTML->ausgabe();
?>

View File

@ -1,121 +0,0 @@
<?php
$HTML->setHTMLTitle("Identitäten");
$HTML->importSeitenInhalt("pages/HTML/identities.html");
$statementCreateTable = $RUNTIME['PDO']->prepare("CREATE TABLE IF NOT EXISTS `UserIdentitys` (`PrincipalID` VARCHAR(38) NOT NULL, `IdentityID` VARCHAR(38) NOT NULL, PRIMARY KEY (`IdentityID`))");
$statementCreateTable->execute();
$statementCheckForEntry = $RUNTIME['PDO']->prepare("SELECT 1 FROM UserIdentitys WHERE PrincipalID = ? LIMIT 1");
$statementCheckForEntry->execute(array($_SESSION['UUID']));
if($statementCheckForEntry->rowCount() == 0)
{
$statement = $RUNTIME['PDO']->prepare('INSERT INTO `UserIdentitys` (PrincipalID, IdentityID) VALUES (:PrincipalID, :IdentityID)');
$statement->execute(['PrincipalID' => $_SESSION['UUID'], 'IdentityID' => $_SESSION['UUID']]);
}
if(isset($_REQUEST['enableIdent']) && isset($_REQUEST['newuuid']) && $_REQUEST['enableIdent'] == "" && $_REQUEST['newuuid'] != "")
{
$statement = $RUNTIME['PDO']->prepare("SELECT 1 FROM UserIdentitys WHERE PrincipalID = :PrincipalID AND IdentityID = :IdentityID LIMIT 1");
$statement->execute(['PrincipalID' => $_SESSION['UUID'], 'IdentityID' => $_REQUEST['newuuid']]);
$statementPresence = $RUNTIME['PDO']->prepare("SELECT 1 FROM Presence WHERE UserID = :PrincipalID LIMIT 1");
$statementPresence->execute(['PrincipalID' => $_SESSION['UUID']]);
if($statementPresence->rowCount() == 0)
{
if($statement->rowCount() == 1)
{
$statementAuth = $RUNTIME['PDO']->prepare('UPDATE auth SET UUID = :IdentityID WHERE UUID = :PrincipalID');
$statementAuth->execute(['IdentityID' => $_REQUEST['newuuid'], 'PrincipalID' => $_SESSION['UUID']]);
$statementUserIdentitys = $RUNTIME['PDO']->prepare('UPDATE UserIdentitys SET PrincipalID = :IdentityID WHERE PrincipalID = :PrincipalID');
$statementUserIdentitys->execute(['IdentityID' => $_REQUEST['newuuid'], 'PrincipalID' => $_SESSION['UUID']]);
$statementFriends = $RUNTIME['PDO']->prepare('UPDATE Friends SET PrincipalID = :IdentityID WHERE PrincipalID = :PrincipalID');
$statementFriends->execute(['IdentityID' => $_REQUEST['newuuid'], 'PrincipalID' => $_SESSION['UUID']]);
//$statementReFriends = $RUNTIME['PDO']->prepare('UPDATE Friends SET Friend = :IdentityID WHERE Friend = :PrincipalID');
//$statementReFriends->execute(['IdentityID' => $_REQUEST['newuuid'], 'PrincipalID' => $_SESSION['UUID']]);
$statementInventoryFolders = $RUNTIME['PDO']->prepare('UPDATE inventoryfolders SET agentID = :IdentityID WHERE agentID = :PrincipalID AND type != :InventarTyp');
$statementInventoryFolders->execute(['IdentityID' => $_REQUEST['newuuid'], 'PrincipalID' => $_SESSION['UUID'], 'InventarTyp' => 46]);
$statementInventoryItems = $RUNTIME['PDO']->prepare('UPDATE inventoryitems SET avatarID = :IdentityID WHERE avatarID = :PrincipalID');
$statementInventoryItems->execute(['IdentityID' => $_REQUEST['newuuid'], 'PrincipalID' => $_SESSION['UUID']]);
$statementGroupMembership = $RUNTIME['PDO']->prepare('UPDATE os_groups_membership SET PrincipalID = :IdentityID WHERE PrincipalID = :PrincipalID');
$statementGroupMembership->execute(['IdentityID' => $_REQUEST['newuuid'], 'PrincipalID' => $_SESSION['UUID']]);
$statementGroupRoles = $RUNTIME['PDO']->prepare('UPDATE os_groups_rolemembership SET PrincipalID = :IdentityID WHERE PrincipalID = :PrincipalID');
$statementGroupRoles->execute(['IdentityID' => $_REQUEST['newuuid'], 'PrincipalID' => $_SESSION['UUID']]);
$statementGroupRoles = $RUNTIME['PDO']->prepare('DELETE FROM Presence WHERE UserID = :PrincipalID');
$statementGroupRoles->execute(['PrincipalID' => $_SESSION['UUID']]);
$_SESSION['LOGIN'] = 'false';
session_destroy();
header("Location: index.php?page=identities");
die();
}
}else{
$HTML->ReplaceSeitenInhalt("%%MESSAGE%%", '<div class="alert alert-danger" role="alert">Du kannst die Identität nicht ändern, während du angemeldet bist. Bitte schließe den Viewer.</div>');
}
}
if(isset($_REQUEST['createIdent']) && isset($_REQUEST['newName']) && $_REQUEST['createIdent'] == "" && $_REQUEST['newName'] != "")
{
$avatarNameParts = explode(" ", trim($_REQUEST['newName']));
if(count($avatarNameParts) == 2)
{
$statement = $RUNTIME['PDO']->prepare("SELECT 1 FROM UserAccounts WHERE FirstName = :FirstName AND LastName = :LastName LIMIT 1");
$statement->execute(['FirstName' => trim($avatarNameParts[0]), 'LastName' => trim($avatarNameParts[1])]);
if($statement->rowCount() == 0)
{
$avatarUUID = $RUNTIME['OPENSIM']->gen_uuid();
$statementAccounts = $RUNTIME['PDO']->prepare('INSERT INTO UserAccounts (PrincipalID, ScopeID, FirstName, LastName, Email, ServiceURLs, Created, UserLevel, UserFlags, UserTitle, active) VALUES (:PrincipalID, :ScopeID, :FirstName, :LastName, :Email, :ServiceURLs, :Created, :UserLevel, :UserFlags, :UserTitle, :active )');
$statementAccounts->execute(['PrincipalID' => $avatarUUID, 'ScopeID' => "00000000-0000-0000-0000-000000000000", 'FirstName' => $avatarNameParts[0], 'LastName' => $avatarNameParts[1], 'Email' => $_SESSION['EMAIL'], 'ServiceURLs' => "HomeURI= GatekeeperURI= InventoryServerURI= AssetServerURI= ", 'Created' => time(), 'UserLevel' => 0, 'UserFlags' => 0, 'UserTitle' => "", 'active' => 1]);
//print_r($statementAccounts->errorInfo());
$statementUserIdentitys = $RUNTIME['PDO']->prepare('INSERT INTO UserIdentitys (PrincipalID, IdentityID) VALUES (:PrincipalID, :IdentityID)');
$statementUserIdentitys->execute(['PrincipalID' => $_SESSION['UUID'], 'IdentityID' => $avatarUUID]);
//print_r($statementUserIdentitys->errorInfo());
header('Location: index.php?page=identities');
die();
}else{
$HTML->ReplaceSeitenInhalt("%%MESSAGE%%", '<div class="alert alert-danger" role="alert">Dieser Name ist schon in Benutzung.</div>');
}
}else{
$HTML->ReplaceSeitenInhalt("%%MESSAGE%%", '<div class="alert alert-danger" role="alert">Der Name muss aus einem Vor und einem Nachnamen bestehen.</div>');
}
}
$table = '<table class="table"><thead><tr><th scope="col">Name</th><th scope="col">Aktionen</th></thead><tbody>%%ENTRY%%</tbody></table>';
$statement = $RUNTIME['PDO']->prepare("SELECT IdentityID FROM UserIdentitys WHERE PrincipalID = ? ORDER BY IdentityID ASC");
$statement->execute(array($_SESSION['UUID']));
while($row = $statement->fetch())
{
if($row['IdentityID'] == $_SESSION['UUID'])
{
$entry = '<tr><td>'.htmlspecialchars(trim($RUNTIME['OPENSIM']->getUserName($row['IdentityID']))).' <span class="badge badge-info">Aktiv</span></td><td>-</td></tr>';
}else{
$entry = '<tr><td>'.htmlspecialchars(trim($RUNTIME['OPENSIM']->getUserName($row['IdentityID']))).'</td><td><form action="index.php?page=identities" method="post">%%CSRF%%<input type="hidden" name="newuuid" value="'.htmlspecialchars($row['IdentityID']).'"><button type="submit" name="enableIdent" class="btn btn-success btn-sm">Aktivieren</button></form></td></tr>';
}
$table = str_replace("%%ENTRY%%", $entry."%%ENTRY%%", $table);
}
$table = str_replace("%%ENTRY%%", "", $table);
$HTML->ReplaceSeitenInhalt("%%IDENT-LIST%%", $table);
$HTML->ReplaceSeitenInhalt("%%link%%", ' ');
$HTML->ReplaceSeitenInhalt("%%MESSAGE%%", ' ');
$HTML->build();
echo $HTML->ausgabe();
?>

View File

@ -1,19 +0,0 @@
<?php
if(@$_SESSION['LEVEL'] < 100)
{
$HTML->setHTMLTitle("Kein Zugriff");
$HTML->SetSeitenInhalt("Dazu hast du keine Rechte!");
$HTML->build();
echo $HTML->ausgabe();
die();
}
$HTML->setHTMLTitle("Benutzer");
$HTML->importSeitenInhalt("pages/HTML/users.html");
$HTML->ReplaceSeitenInhalt("%%link%%", ' ');
$HTML->build();
echo $HTML->ausgabe();
?>

View File

@ -1,82 +0,0 @@
<?php
$HTML = new HTML();
$HTML->setHTMLTitle("Login");
$HTML->importHTML("style/login/login.html");
if(isset($_POST['login']))
{
include_once 'classen/FormValidator.php';
$validator = new FormValidator(array(
'username' => array('required' => true, 'regex' => '/[^\\\/<>\s]{1,64} [^\\\/<>\s]{1,64}/'),
'password' => array('required' => true, 'regex' => '/.{1,1000}/')
));
if(!$validator->isValid($_POST)) {
$HTML->ReplaceLayoutInhalt("%%LOGINMESSAGE%%", "Bitte gebe Benutzername und Passwort an.");
}
else {
$statementUser = $RUNTIME['PDO']->prepare("SELECT PrincipalID,FirstName,LastName,Email,UserLevel FROM UserAccounts WHERE FirstName = ? AND LastName = ? LIMIT 1");
$statementUser->execute(explode(" ", trim($_POST['username'])));
$RUNTIME['MESSAGE']['LOGINERROR'] = "Benutzername nicht gefunden!";
while($rowUser = $statementUser->fetch())
{
$statementAuth = $RUNTIME['PDO']->prepare("SELECT passwordHash,passwordSalt FROM auth WHERE UUID = ? LIMIT 1");
$statementAuth->execute(array($rowUser['PrincipalID']));
$RUNTIME['DEBUG']['LOGIN']['UUID'] = $rowUser['PrincipalID'];
while($rowAuth = $statementAuth->fetch())
{
$passwordCorrect = false;
if(strlen($rowAuth['passwordHash']) == 32) {
if(md5(md5($_POST['password']).":".$rowAuth['passwordSalt']) == $rowAuth['passwordHash']) {
$passwordCorrect = true;
$newHash = password_hash($_POST['password'], PASSWORD_ARGON2ID);
$updateHash = $RUNTIME['PDO']->prepare("UPDATE auth SET passwordHash = ?, passwordSalt = ? WHERE UUID = ?");
$updateHash->execute(array($newHash, '', $rowUser['PrincipalID']));
}
}
else {
$passwordCorrect = password_verify($_POST['password'], $rowAuth['passwordHash']);
}
if($passwordCorrect)
{
session_unset(); // Unset pre-session variables, next request will generate a new CSRF token
$_SESSION['USERNAME'] = trim($_POST['username']);
$_SESSION['FIRSTNAME'] = trim($rowUser['FirstName']);
$_SESSION['LASTNAME'] = trim($rowUser['LastName']);
$_SESSION['EMAIL'] = trim($rowUser['Email']);
$_SESSION['PASSWORD'] = $rowAuth['passwordHash'];
$_SESSION['SALT'] = $rowAuth['passwordSalt'];
$_SESSION['UUID'] = $rowUser['PrincipalID'];
$_SESSION['LEVEL'] = $rowUser['UserLevel'];
$_SESSION['DISPLAYNAME'] = strtoupper(trim($_POST['username']));
$_SESSION['LOGIN'] = 'true';
header("Location: index.php?page=".urlencode($_REQUEST['page']));
die();
}
}
$RUNTIME['MESSAGE']['LOGINERROR'] = "Passwort falsch!";
}
$HTML->ReplaceLayoutInhalt("%%LOGINMESSAGE%%", $RUNTIME['MESSAGE']['LOGINERROR']);
$HTML->ReplaceLayoutInhalt("%%LASTUSERNAME%%", htmlspecialchars($_POST['username']));
}
}
if(isset($_REQUEST['page']) && preg_match('/[0-9a-zA-Z]{1-100}/', $_REQUEST['page']) && file_exists("./pages/".$_REQUEST['page'].".php"))
$HTML->ReplaceLayoutInhalt("%%PAGENAME%%", urlencode($_REQUEST['page']));
$HTML->ReplaceLayoutInhalt("%%LOGINMESSAGE%%", "");
$HTML->ReplaceLayoutInhalt("%%LASTUSERNAME%%", "");
$HTML->ReplaceLayoutInhalt("%%PAGENAME%%", "dashboard");
$HTML->build();
echo $HTML->ausgabe();
?>

View File

@ -1,79 +0,0 @@
<?php
$HTML->setHTMLTitle("Passwort ändern");
$HTML->importSeitenInhalt("pages/HTML/profile.html");
if(isset($_REQUEST['oldPassword']) || @$_REQUEST['oldPassword'] != "")
{
$OLDPassword = trim($_REQUEST['oldPassword']);
if($OLDPassword != "")
{
if(password_verify($OLDPassword, $_SESSION['PASSWORD']))
{
if(isset($_REQUEST['newPassword']) && $_REQUEST['newPassword'] != "")
{
$NewPassword = trim($_REQUEST['newPassword']);
if($NewPassword != "")
{
if(isset($_REQUEST['newPasswordRepeate']) || @$_REQUEST['newPasswordRepeate'] != "")
{
$NewPasswordRepeate = trim($_REQUEST['newPasswordRepeate']);
if($NewPasswordRepeate != "")
{
if($NewPasswordRepeate == $NewPassword)
{
$hash = password_hash($NewPassword, PASSWORD_ARGON2ID);
$statement = $RUNTIME['PDO']->prepare('UPDATE auth SET passwordHash = :PasswordHash WHERE UUID = :PrincipalID');
$statement->execute(['PasswordHash' => $hash, 'PrincipalID' => $_SESSION['UUID']]);
$_SESSION['PASSWORD'] = $hash;
$_SESSION['pwChanged'] = true;
header('Location: index.php?page=password');
die();
}else{
$HTML->ReplaceSeitenInhalt("%%INFOMESSAGE%%", 'Passwörter stimmen nicht überein!');
}
}else{
$HTML->ReplaceSeitenInhalt("%%INFOMESSAGE%%", 'Bitte gib das Passwort zur bestätigung noch einmal ein!');
}
}else{
$HTML->ReplaceSeitenInhalt("%%INFOMESSAGE%%", 'Bitte gib das Passwort zur bestätigung noch einmal ein!');
}
}else{
$HTML->ReplaceSeitenInhalt("%%INFOMESSAGE%%", 'Bitte gebe ein neues Passwort ein!');
}
}else{
$HTML->ReplaceSeitenInhalt("%%INFOMESSAGE%%", 'Bitte gebe ein neues Passwort ein!');
}
}else{
$HTML->ReplaceSeitenInhalt("%%INFOMESSAGE%%", 'Das alte Passwort ist nicht richtig!');
}
}else{
$HTML->ReplaceSeitenInhalt("%%INFOMESSAGE%%", 'Gebe bitte dein Passwort ein.');
}
}
$PartnerName = "";
$PartnerUUID = $RUNTIME['OPENSIM']->getPartner($_SESSION['UUID']);
if($PartnerUUID != null)$PartnerName = $RUNTIME['OPENSIM']->getUserName($PartnerUUID);
$HTML->ReplaceSeitenInhalt("%%offlineIMSTATE%%", ' ');
$HTML->ReplaceSeitenInhalt("%%firstname%%", htmlspecialchars($_SESSION['FIRSTNAME']));
$HTML->ReplaceSeitenInhalt("%%lastname%%", htmlspecialchars($_SESSION['LASTNAME']));
$HTML->ReplaceSeitenInhalt("%%partner%%", htmlspecialchars($PartnerName));
$HTML->ReplaceSeitenInhalt("%%email%%", htmlspecialchars($RUNTIME['OPENSIM']->getUserMail($_SESSION['UUID'])));
$HTML->ReplaceSeitenInhalt("%%listAllResidentsAsJSArray%%", "");
$pwChanged = false;
if(isset($_SESSION['pwChanged'])) {
$pwChanged = true;
unset($_SESSION['pwChanged']);
}
$HTML->ReplaceSeitenInhalt("%%INFOMESSAGE%%", $pwChanged ? 'Neues Passwort gespeichert.' : ' ');
$HTML->build();
echo $HTML->ausgabe();
?>

View File

@ -1,166 +0,0 @@
<?php
$statement = $RUNTIME['PDO']->prepare("CREATE TABLE IF NOT EXISTS `iarstates` (`userID` VARCHAR(36) NOT NULL COLLATE 'utf8_unicode_ci', `filesize` BIGINT(20) NOT NULL DEFAULT '0', `iarfilename` VARCHAR(64) NOT NULL COLLATE 'utf8_unicode_ci', `running` INT(1) NOT NULL DEFAULT '0', PRIMARY KEY (`userID`) USING BTREE) COLLATE='utf8_unicode_ci' ENGINE=InnoDB;");
$statement->execute();
$HTML->setHTMLTitle("Dein Profile");
$HTML->importSeitenInhalt("pages/HTML/profile.html");
//Prüfe ob IAR grade erstellt wird.
$IARRUNNING = FALSE;
$statementIARCheck = $RUNTIME['PDO']->prepare('SELECT 1 FROM iarstates WHERE userID =:userID');
$statementIARCheck->execute(['userID' => $_SESSION['UUID']]);
if($statementIARCheck->rowCount() != 0)
{
$HTML->ReplaceSeitenInhalt("%%IARINFOMESSAGE%%", '<div class="alert alert-danger" role="alert">Aktuell wird eine IAR erstellt.<br>Warte bitte bis du eine PM bekommst.</div>');
$HTML->ReplaceSeitenInhalt("%%IARBUTTONSTATE%%", 'disabled');
$IARRUNNING = TRUE;
}
if(isset($_REQUEST['createIAR']))
{
if($IARRUNNING == FALSE)
{
$iarname = md5(time().$_SESSION['UUID'] . rand()).".iar";
$HTML->ReplaceSeitenInhalt("%%IARINFOMESSAGE%%", '<div class="alert alert-danger" role="alert">Deine IAR wird jetzt erstellt und der Download Link wird dir per PM zugesendet. '.$APIResult.'</div>');
$HTML->ReplaceSeitenInhalt("%%IARBUTTONSTATE%%", 'disabled');
$statementIARSTART = $RUNTIME['PDO']->prepare('INSERT INTO iarstates (userID, filesize, iarfilename) VALUES (:userID, :filesize, :iarfilename)');
$statementIARSTART->execute(['userID' => $_SESSION['UUID'], 'filesize' => 0, 'iarfilename' => $iarname]);
}
}
if(isset($_REQUEST['formInputFeldVorname']) && $_REQUEST['formInputFeldVorname'] != "")
{
$NewFirstName = trim($_REQUEST['formInputFeldVorname']);
if($NewFirstName != "")
{
if($_SESSION['FIRSTNAME'] != $NewFirstName)
{
$statement = $RUNTIME['PDO']->prepare('UPDATE UserAccounts SET FirstName = :FirstName WHERE PrincipalID = :PrincipalID');
$statement->execute(['FirstName' => $NewFirstName, 'PrincipalID' => $_SESSION['UUID']]);
$_SESSION['FIRSTNAME'] = $NewFirstName;
$_SESSION['USERNAME'] = $_SESSION['FIRSTNAME']." ".$_SESSION['LASTNAME'];
$_SESSION['DISPLAYNAME'] = strtoupper($_SESSION['USERNAME']);
}
}
}
if(isset($_REQUEST['formInputFeldNachname']) && $_REQUEST['formInputFeldNachname'] != "")
{
$NewLastName = trim($_REQUEST['formInputFeldNachname']);
if($NewLastName != "")
{
if($_SESSION['LASTNAME'] != $NewLastName)
{
$statement = $RUNTIME['PDO']->prepare('UPDATE UserAccounts SET LastName = :LastName WHERE PrincipalID = :PrincipalID');
$statement->execute(['LastName' => $NewLastName, 'PrincipalID' => $_SESSION['UUID']]);
$_SESSION['LASTNAME'] = $NewLastName;
$_SESSION['USERNAME'] = $_SESSION['FIRSTNAME']." ".$_SESSION['LASTNAME'];
$_SESSION['DISPLAYNAME'] = strtoupper($_SESSION['USERNAME']);
}
}
}
if(isset($_REQUEST['formInputFeldEMail']) && $_REQUEST['formInputFeldEMail'] != "")
{
$NewEMail = trim($_REQUEST['formInputFeldEMail']);
if($NewEMail != "")
{
if($_SESSION['EMAIL'] != $NewEMail)
{
$statement = $RUNTIME['PDO']->prepare('UPDATE UserAccounts SET Email = :Email WHERE PrincipalID = :PrincipalID');
$statement->execute(['Email' => $NewEMail, 'PrincipalID' => $_SESSION['UUID']]);
$statement = $RUNTIME['PDO']->prepare('UPDATE usersettings SET email = :Email WHERE useruuid = :PrincipalID');
$statement->execute(['Email' => $NewEMail, 'PrincipalID' => $_SESSION['UUID']]);
$_SESSION['EMAIL'] = $NewEMail;
}
}
}
if(isset($_REQUEST['formInputFeldOfflineIM']) && $_REQUEST['formInputFeldOfflineIM'] != "")
{
$NewOfflineIM = trim($_REQUEST['formInputFeldOfflineIM']);
if($NewOfflineIM != "")
{
if($NewOfflineIM == "on" || $NewOfflineIM == "true")
{
$statement = $RUNTIME['PDO']->prepare('UPDATE usersettings SET imviaemail = :IMState WHERE useruuid = :PrincipalID');
$statement->execute(['IMState' => 'true', 'PrincipalID' => $_SESSION['UUID']]);
}
}
}else if(!isset($_REQUEST['formInputFeldOfflineIM']) && isset($_REQUEST['saveProfileData'])){
$statement = $RUNTIME['PDO']->prepare('UPDATE usersettings SET imviaemail = :IMState WHERE useruuid = :PrincipalID');
$statement->execute(['IMState' => 'false', 'PrincipalID' => $_SESSION['UUID']]);
}
if(isset($_REQUEST['formInputFeldPartnerName']) && $_REQUEST['formInputFeldPartnerName'] != "")
{
$NewPartner = trim($_REQUEST['formInputFeldPartnerName']);
$CurrentPartner = $RUNTIME['OPENSIM']->getPartner($_SESSION['UUID']);
if($CurrentPartner != "")$CurrentPartner = $RUNTIME['OPENSIM']->getUserName($CurrentPartner);
if($NewPartner != "")
{
if($CurrentPartner != $NewPartner)
{
$newPartnerUUID = $RUNTIME['OPENSIM']->getUserUUID($NewPartner);
if($newPartnerUUID != null)
{
$statement = $RUNTIME['PDO']->prepare('UPDATE userprofile SET profilePartner = :profilePartner WHERE useruuid = :PrincipalID');
$statement->execute(['profilePartner' => $newPartnerUUID, 'PrincipalID' => $_SESSION['UUID']]);
}
}
}else{
$statement = $RUNTIME['PDO']->prepare('UPDATE userprofile SET profilePartner = :profilePartner WHERE useruuid = :PrincipalID');
$statement->execute(['profilePartner' => '00000000-0000-0000-0000-000000000000', 'PrincipalID' => $_SESSION['UUID']]);
}
}
$statementLocalUsers = $RUNTIME['PDO']->prepare("SELECT FirstName,LastName FROM UserAccounts ORDER BY PrincipalID ASC");
$statementLocalUsers->execute();
$allUsers = "";
while($row = $statementLocalUsers->fetch())
{
$name = '"'.@$row['FirstName']." ".@$row['LastName'].'"';
if($allUsers != "")
{
$allUsers .= ",".$name;
}else{
$allUsers .= $name;
}
}
$allUsers .= '," "';
$PartnerUUID = $RUNTIME['OPENSIM']->getPartner($_SESSION['UUID']);
$PartnerName = "";
if($PartnerUUID != null)$PartnerName = $RUNTIME['OPENSIM']->getUserName($PartnerUUID);
if($RUNTIME['OPENSIM']->allowOfflineIM($_SESSION['UUID']) == "TRUE")$HTML->ReplaceSeitenInhalt("%%offlineIMSTATE%%", ' checked');
$HTML->ReplaceSeitenInhalt("%%offlineIMSTATE%%", ' ');
$HTML->ReplaceSeitenInhalt("%%firstname%%", htmlspecialchars($_SESSION['FIRSTNAME']));
$HTML->ReplaceSeitenInhalt("%%lastname%%", htmlspecialchars($_SESSION['LASTNAME']));
$HTML->ReplaceSeitenInhalt("%%partner%%", htmlspecialchars($PartnerName));
$HTML->ReplaceSeitenInhalt("%%email%%", htmlspecialchars($RUNTIME['OPENSIM']->getUserMail($_SESSION['UUID'])));
$HTML->ReplaceSeitenInhalt("%%listAllResidentsAsJSArray%%", "");
$HTML->ReplaceSeitenInhalt("%%INFOMESSAGE%%", ' ');
$HTML->ReplaceSeitenInhalt("%%IARINFOMESSAGE%%", ' ');
$HTML->ReplaceSeitenInhalt("%%IARBUTTONSTATE%%", '');
$HTML->build();
echo $HTML->ausgabe();
?>

View File

@ -1,77 +0,0 @@
<?php
$HTML->setHTMLTitle("Deine Regionen");
$HTML->importSeitenInhalt("pages/HTML/deine-regionen.html");
function cleanSize($bytes)
{
if ($bytes > 0)
{
$unit = intval(log($bytes, 1024));
$units = array('B', 'KB', 'MB', 'GB');
if (array_key_exists($unit, $units) === true)
{
return sprintf('%d %s', $bytes / pow(1024, $unit), $units[$unit]);
}
}
return $bytes;
}
function getRegionStatsData($regionID)
{
global $RUNTIME;
$statement = $RUNTIME['PDO']->prepare("SELECT Prims,SimFPS,PhyFPS,ProcMem,RegionVersion FROM regions_info WHERE regionID = ?");
$statement->execute([$regionID]);
if($row = $statement->fetch())
{
$return = array();
$return['Prims'] = $row['Prims'];
$return['SimFPS'] = $row['SimFPS'];
$return['PhyFPS'] = $row['PhyFPS'];
$return['ProcMem'] = cleanSize(str_replace(".", "", str_replace(",", ".", $row['ProcMem']))."000");
$return['RegionVersion'] = trim($row['RegionVersion']);
return $return;
}
return array();
}
if(isset($_REQUEST['action']) && isset($_REQUEST['region']) && $_REQUEST['action'] == 'remove' && $_REQUEST['region'] != '')
{
if(isset($_SESSION['LEVEL']) && $_SESSION['LEVEL'] >= 100)
{
$statementMembership = $RUNTIME['PDO']->prepare("DELETE FROM regions WHERE uuid = ?");
$statementMembership->execute(array($_REQUEST['region']));
}else{
$statementMembership = $RUNTIME['PDO']->prepare("DELETE FROM regions WHERE uuid = ? AND owner_uuid = ?");
$statementMembership->execute(array($_REQUEST['region'], $_SESSION['UUID']));
}
header('Location: index.php?page=regions');
die();
}
$table = '<table class="table"><thead><tr><th scope="col">Region Name</th><th scope="col">Eigentümer</th><th scope="col">Position</th><th scope="col">Aktionen</th></thead><tbody>%%ENTRY%%</tbody></table>';
$showAll = isset($_SESSION['LEVEL']) && $_SESSION['LEVEL'] >= 100 && isset($_REQUEST['SHOWALL']) && $_REQUEST['SHOWALL'] == "1";
$statement = $RUNTIME['PDO']->prepare("SELECT uuid,regionName,owner_uuid,locX,locY FROM regions ".($showAll ? "ORDER BY owner_uuid ASC" : "WHERE owner_uuid = ? ORDER BY uuid ASC"));
$statement->execute(array($_SESSION['UUID']));
while($row = $statement->fetch())
{
$stats = getRegionStatsData($row['uuid']);
$entry = '<tr><td>'.htmlspecialchars($row['regionName']).'<div class="blockquote-footer">Prims: '.$stats['Prims'].'; RAM-Nutzung: '.$stats['ProcMem'].'; SIM/PHYS FPS: '.$stats['SimFPS'].'/'.$stats['PhyFPS'].' ('.$stats['RegionVersion'].')</div></td><td>'.htmlspecialchars($RUNTIME['OPENSIM']->getUserName($row['owner_uuid'])).'</td><td>'.fillString(($row['locX'] / 256), 4).' / '.fillString(($row['locY'] / 256), 4).'</td><td><a href="index.php?page=regions&action=remove&region='.$row['uuid'].'">LÖSCHEN</a></td></tr>';
$table = str_replace("%%ENTRY%%", $entry."%%ENTRY%%", $table);
}
$table = str_replace("%%ENTRY%%", "", $table);
$HTML->ReplaceSeitenInhalt("%%REGION-LIST%%", $table);
$HTML->build();
echo $HTML->ausgabe();
?>

View File

@ -1,133 +0,0 @@
<?php
function displayPage(string $message)
{
global $RUNTIME;
$HTML = new HTML();
$HTML->setHTMLTitle("Registrieren");
$HTML->importHTML("style/login/register.html");
$HTML->ReplaceLayoutInhalt("%%MESSAGE%%", $message);
$HTML->ReplaceLayoutInhalt("%%tosURL%%", $RUNTIME['TOOLS']['TOS'] );
$HTML->ReplaceLayoutInhalt("%%INVCODE%%", htmlspecialchars($_REQUEST['code']));
$HTML->build();
echo $HTML->ausgabe();
die();
}
if(!isset($_REQUEST['code']))
die("MISSING INVITE CODE!");
if(strlen($_REQUEST['code']) != 32 || !preg_match('/[a-f0-9]+/', $_REQUEST['code'])) {
die("INVALID INVITE CODE!");
}
$statementInviteCode = $RUNTIME['PDO']->prepare("SELECT 1 FROM InviteCodes WHERE InviteCode = ? LIMIT 1");
$statementInviteCode->execute([$_REQUEST['code']]);
if($statementInviteCode->rowCount() == 0) {
die("INVALID INVITE CODE!");
}
if(!isset($_REQUEST['doRegister']))
{
displayPage("");
}
include_once('classen/FormValidator.php');
$validator = new FormValidator(array(
'tos' => array('required' => true, 'equals' => 'on'),
'username' => array('required' => true, 'regex' => '/[^\\\/<>\s]{1,64}( [^\\\/<>\s]{1,64})?/'),
'password' => array('required' => true, 'regex' => '/.{1,1000}/'),
'email' => array('required' => true, 'regex' => '/\S{1,64}@\S{1,250}.\S{2,64}/'),
'avatar' => array('required' => true)
));
if(!$validator->isValid($_POST)) {
if(!isset($_REQUEST['tos']) || $_REQUEST['tos'] !== true) {
displayPage("Du musst die Nutzungsbedingungen lesen und Akzeptieren.");
}
else {
displayPage("Ups da stimmt was nicht. Versuche es bitte noch mal.");
}
die();
}
$RUNTIME['REGISTER']['Name'] = null;
$RUNTIME['REGISTER']['PASS'] = null;
$RUNTIME['REGISTER']['EMAIL'] = null;
$RUNTIME['REGISTER']['AVATAR'] = null;
$RUNTIME['REGISTER']['TOS'] = true;
$name = trim($_REQUEST['username']);
if($name != "")
{
$nameParts = explode(" ", $name);
if(count($nameParts) == 1)
{
$name .= " Resident";
$nameParts = explode(" ", $name);
}
$statementAvatarName = $RUNTIME['PDO']->prepare("SELECT 1 FROM UserAccounts WHERE FirstName = :FirstName AND LastName = :LastName LIMIT 1");
$statementAvatarName->execute(['FirstName' => $nameParts[0], 'LastName' => $nameParts[1]]);
if($statementAvatarName->rowCount() == 0)
{
$RUNTIME['REGISTER']['Name'] = $name;
}
else
{
displayPage("Der gewählte Name ist bereits vergeben.");
}
}
$RUNTIME['REGISTER']['PASS'] = trim($_REQUEST['password']);
$RUNTIME['REGISTER']['EMAIL'] = trim($_REQUEST['email']);
if(isset($RUNTIME['DEFAULTAVATAR'][$_REQUEST['avatar']]['UUID']))
{
$RUNTIME['REGISTER']['AVATAR'] = trim($_REQUEST['avatar']);
}
else
{
displayPage("Der gewählte Standardavatar existiert nicht.");
}
$avatarUUID = $RUNTIME['OPENSIM']->gen_uuid();
$passwordHash = password_hash($RUNTIME['REGISTER']['PASS'], PASSWORD_ARGON2ID);
$avatarNameParts = explode(" ", $RUNTIME['REGISTER']['Name']);
$statementAuth = $RUNTIME['PDO']->prepare('INSERT INTO `auth` (`UUID`, `passwordHash`, `webLoginKey`, `accountType`) VALUES (:UUID, :HASHVALUE, :WEBKEY, :ACCTYPE)');
$statementAuth->execute(['UUID' => $avatarUUID, 'HASHVALUE' => $passwordHash, 'WEBKEY' => "00000000-0000-0000-0000-000000000000", 'ACCTYPE' => "UserAccount"]);
$statementAccounts = $RUNTIME['PDO']->prepare('INSERT INTO `UserAccounts` (`PrincipalID`, `ScopeID`, `FirstName`, `LastName`, `Email`, `ServiceURLs`, `Created`, `UserLevel`, `UserFlags`, `UserTitle`, `active`) VALUES (:PrincipalID, :ScopeID, :FirstName, :LastName, :Email, :ServiceURLs, :Created, :UserLevel, :UserFlags, :UserTitle, :active )');
$statementAccounts->execute(['PrincipalID' => $avatarUUID, 'ScopeID' => "00000000-0000-0000-0000-000000000000", 'FirstName' => $avatarNameParts[0], 'LastName' => $avatarNameParts[1], 'Email' => $RUNTIME['REGISTER']['EMAIL'], 'ServiceURLs' => "HomeURI= GatekeeperURI= InventoryServerURI= AssetServerURI= ", 'Created' => time(), 'UserLevel' => 0, 'UserFlags' => 0, 'UserTitle' => "", 'active' => 1]);
$statementProfile = $RUNTIME['PDO']->prepare('INSERT INTO `userprofile` (`useruuid`, `profilePartner`, `profileImage`, `profileFirstImage`) VALUES (:useruuid, :profilePartner, :profileImage, :profileFirstImage)');
$statementProfile->execute(['useruuid' => $avatarUUID, 'profilePartner' => "00000000-0000-0000-0000-000000000000", 'profileImage' => "00000000-0000-0000-0000-000000000000", 'profileFirstImage' => "00000000-0000-0000-0000-000000000000"]);
$Inventory = array('Calling Cards' => 2, 'Objects' => 6, 'Landmarks' => 3, 'Clothing' => 5, 'Gestures' => 21, 'Body Parts' => 13, 'Textures' => 0, 'Scripts' => 10, 'Photo Album' => 15, 'Lost And Found' => 16, 'Trash' => 14, 'Notecards' => 7, 'My Inventory' => 8, 'Sounds' => 1, 'Animations' => 20);
$InventoryRootFolder = $RUNTIME['OPENSIM']->gen_uuid();
foreach ($Inventory as $FolderName => $InventoryType)
{
$FolderUUID = $RUNTIME['OPENSIM']->gen_uuid();
if ($InventoryType == 8)
{
$FolderUUID = $InventoryRootFolder;
$FolderParent = "00000000-0000-0000-0000-000000000000";
}else{
$FolderParent = $InventoryRootFolder;
}
$statementInventoryFolder = $RUNTIME['PDO']->prepare('INSERT INTO `inventoryfolders` (`folderName`, `type`, `version`, `folderID`, `agentID`, `parentFolderID`) VALUES (:folderName, :folderTyp, :folderVersion, :folderID, :agentID, :parentFolderID)');
$statementInventoryFolder->execute(['agentID' => $avatarUUID, 'folderName' => $FolderName, 'folderTyp' => $InventoryType, 'folderVersion' => 1, 'folderID' => $FolderUUID, 'parentFolderID' => $FolderParent]);
}
$statementInviteDeleter = $RUNTIME['PDO']->prepare('DELETE FROM InviteCodes WHERE InviteCode = :code');
$statementInviteDeleter->execute(['code' => $_REQUEST['code']]);
session_unset(); // Unset pre-session variables, next request will generate a new CSRF token
$_SESSION['USERNAME'] = trim($RUNTIME['REGISTER']['Name']);
$_SESSION['FIRSTNAME'] = trim($avatarNameParts[0]);
$_SESSION['LASTNAME'] = trim($avatarNameParts[1]);
$_SESSION['EMAIL'] = trim($RUNTIME['REGISTER']['EMAIL']);
$_SESSION['PASSWORD'] = $passwordHash;
$_SESSION['UUID'] = $avatarUUID;
$_SESSION['LEVEL'] = 0;
$_SESSION['DISPLAYNAME'] = strtoupper(trim($RUNTIME['REGISTER']['Name']));
$_SESSION['LOGIN'] = 'true';
header('Location: index.php?page=dashboard');
die();
?>

View File

@ -1,24 +0,0 @@
<?php
$HTML->setHTMLTitle("Online Anzeige");
$HTML->importSeitenInhalt("pages/HTML/online-anzeige.html");
$table = '<table class="table"><thead><tr><th scope="col">Benutzername</th><th scope="col">Region</th></thead><tbody>%%ENTRY%%</tbody></table>';
$statement = $RUNTIME['PDO']->prepare("SELECT RegionID,UserID FROM Presence ORDER BY RegionID ASC");
$statement->execute();
while($row = $statement->fetch())
{
if($row['RegionID'] != "00000000-0000-0000-0000-000000000000")
{
$entry = '<tr><td>'.htmlspecialchars(trim($RUNTIME['OPENSIM']->getUserName($row['UserID']))).'</td><td>'.htmlspecialchars($RUNTIME['OPENSIM']->getRegionName($row['RegionID'])).'</td></tr>';
$table = str_replace("%%ENTRY%%", $entry."%%ENTRY%%", $table);
}
}
$table = str_replace("%%ENTRY%%", "", $table);
$HTML->ReplaceSeitenInhalt("%%ONLINE-LIST%%", $table);
$HTML->build();
echo $HTML->ausgabe();
?>

View File

@ -1,60 +0,0 @@
<?php
function generateRandomString($length = 10) {
return substr(str_shuffle(str_repeat($x='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', ceil($length/strlen($x)) )),1,$length);
}
if(@$_SESSION['LEVEL'] < 100)
{
$HTML->setHTMLTitle("Kein Zugriff");
$HTML->SetSeitenInhalt("Dazu hast du keine Rechte!");
$HTML->build();
echo $HTML->ausgabe();
die();
}
$HTML->setHTMLTitle("Benutzer");
$HTML->importSeitenInhalt("pages/HTML/users.html");
if(@$_REQUEST['action'] == 'genpw' && @$_REQUEST['userid'] != '')
{
$NEWPW = generateRandomString(10);
$statement = $RUNTIME['PDO']->prepare('UPDATE auth SET passwordHash = :PasswordHash WHERE UUID = :PrincipalID');
$statement->execute(['PasswordHash' => password_hash($NEWPW, PASSWORD_ARGON2ID), 'PrincipalID' => $_REQUEST['userid']]);
$HTML->ReplaceSeitenInhalt("%%MESSAGE%%", '<div class="alert alert-danger" role="alert">Das Passwort für '.htmlspecialchars($RUNTIME['OPENSIM']->getUserName($_REQUEST['userid'])).' wurde geändert. Das neue Passwort ist <b>'.htmlspecialchars($NEWPW).'</b></div>');
}
$statement = $RUNTIME['PDO']->prepare("CREATE TABLE IF NOT EXISTS `InviteCodes` (`InviteCode` VARCHAR(64) NOT NULL, PRIMARY KEY (`InviteCode`))");
$statement->execute();
if(isset($_REQUEST['generateLink']) || @$_REQUEST['generateLink'] != "")
{
$inviteID = md5(time().$_SESSION['UUID'].rand(11111, 9999999));
$link = "https://".$_SERVER['SERVER_NAME']."/index.php?page=register&code=".$inviteID;
$statement = $RUNTIME['PDO']->prepare('INSERT INTO `InviteCodes` (`InviteCode`) VALUES (:InviteCode)');
$statement->execute(['InviteCode' => $inviteID]);
$HTML->ReplaceSeitenInhalt("%%link%%", $link);
}
$table = '<table class="table"><thead><tr><th scope="col">Vorname</th><th scope="col">Nachname</th><th scope="col">Status</th><th scope="col">Aktionen</th></thead><tbody>%%ENTRY%%</tbody></table>';
$statement = $RUNTIME['PDO']->prepare("SELECT FirstName,LastName,UserLevel,PrincipalID FROM UserAccounts ORDER BY Created ASC");
$statement->execute();
while($row = $statement->fetch())
{
$entry = '<tr><td>'.htmlspecialchars($row['FirstName']).'</td><td>'.htmlspecialchars($row['LastName']).'</td><td>'.htmlspecialchars($row['UserLevel']).'</td><td><a href="index.php?page=users&action=genpw&userid='.htmlspecialchars($row['PrincipalID']).'">PASSWORT ÄNDERN</a></td></tr>';
$table = str_replace("%%ENTRY%%", $entry."%%ENTRY%%", $table);
}
$table = str_replace("%%ENTRY%%", "", $table);
$HTML->ReplaceSeitenInhalt("%%USER-LIST%%", $table);
$HTML->ReplaceSeitenInhalt("%%link%%", ' ');
$HTML->ReplaceSeitenInhalt("%%MESSAGE%%", ' ');
$HTML->build();
echo $HTML->ausgabe();
?>

Some files were not shown because too many files have changed in this diff Show More