From 88003cb77e9fbb42247382830328d0bdd24d642d Mon Sep 17 00:00:00 2001 From: Krombel Date: Fri, 2 Mar 2018 14:26:36 +0100 Subject: [PATCH] added class mxDatabase; store credentials; implement login.php --- .gitignore | 1 + config.sample.php | 1 - cron.php | 28 ++--- database.php | 260 ++++++++++++++++++++++++++++++++++++---- public/login.php | 105 ++++++++++++++++ public/register.php | 44 ++----- public/verify.php | 29 ++--- public/verify_admin.php | 43 +++---- 8 files changed, 387 insertions(+), 124 deletions(-) create mode 100644 public/login.php diff --git a/.gitignore b/.gitignore index 4f4773f..ec6a661 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ config.php +db_file.sqlite \ No newline at end of file diff --git a/config.sample.php b/config.sample.php index 419a682..69252d1 100644 --- a/config.sample.php +++ b/config.sample.php @@ -2,7 +2,6 @@ $homeserver = "example.com"; $access_token = "To be used for sending the registration notification"; $register_room = '$registerRoomID:example.com'; -$registration_shared_secret = "To be used for actually register the user"; $webroot="https://myregisterdomain.net/"; $howToURL = "https://my-url-for-storing-howTos.net"; diff --git a/cron.php b/cron.php index 861137e..384ce0f 100644 --- a/cron.php +++ b/cron.php @@ -10,7 +10,7 @@ $sql = "SELECT id, first_name, last_name, username, email, state, note, verify_t . " OR state = " . RegisterState::PendingSendRegistrationMail . " OR state = " . RegisterState::RegistrationDeclined . " OR state = " . RegisterState::AllDone . ";"; -foreach ($db->query($sql) as $row) { +foreach ($mx_db->query($sql) as $row) { $first_name = $row["first_name"]; $last_name = $row["last_name"]; $username = $row["username"]; @@ -28,8 +28,7 @@ foreach ($db->query($sql) as $row) { $row["verify_url"]); if ($success) { - $db->exec("UPDATE registrations SET state = " . RegisterState::PendingEmailVerify - . " WHERE id = " . $row["id"] . ";"); + $mx_db->setRegistrationStateById(RegisterState::PendingEmailVerify, $row["id"]); } else { throw new Exception("Could not send mail to ".$row["first_name"]." ".$row["last_name"]."(".$row["id"].")"); } @@ -39,7 +38,7 @@ foreach ($db->query($sql) as $row) { $adminUrl = $webroot . "/verify_admin.php?t=" . $row["admin_token"]; $mxConn = new MatrixConnection($homeserver, $access_token); $mxMsg = new MatrixMessage(); - $mxMsg->set_body($first_name . ' ' . $last_name . "möchte sich registrieren und hat folgende Notiz hinterlassen:\r\n" + $mxMsg->set_body($first_name . ' ' . $last_name . " möchte sich registrieren und hat folgende Notiz hinterlassen:\r\n" . $row["note"] . "\r\n" . "Zum Bearbeiten hier klicken:\r\n" . $adminUrl); $mxMsg->set_formatted_body($first_name . ' ' . $last_name . " möchte sich registrieren und hat folgende Notiz hinterlassen:
" @@ -49,8 +48,7 @@ foreach ($db->query($sql) as $row) { $response = $mxConn->send($register_room, $mxMsg); if ($response) { - $db->exec("UPDATE registrations SET state = " . RegisterState::PendingAdminVerify - . " WHERE id = " . $row["id"] . ";"); + $mx_db->setRegistrationStateById(RegisterState::PendingAdminVerify, $row["id"]); send_mail_pending_approval($homeserver, $first_name . " " . $last_name, $email); } else { @@ -60,17 +58,15 @@ foreach ($db->query($sql) as $row) { case RegisterState::PendingRegistration: // Registration got accepted but registration failed - // register user - require_once("MatrixConnection.php"); - $mxConn = new MatrixConnection($homeserver, $access_token); - - // generate a password with 8 characters - $password = bin2hex(openssl_random_pseudo_bytes(4)); - - $res = $mxConn->register($username, $password, $shared_secret); - if ($res) { + $password = addUser($row["first_name"], $row["last_name"], $row["username"], $row["email"]); + if ($password != NULL) { // send registration_success - send_mail_registration_success($homeserver, $first_name . " " . $last_name, $email, $username, $password, $howToURL); + $res = send_mail_registration_success($homeserver, $first_name . " " . $last_name, $email, $username, $password, $howToURL); + if ($res) { + $mx_db->setRegistrationStateById(RegisterState::AllDone, $row["id"]); + } else { + $mx_db->setRegistrationStateById(RegisterState::PendingSendRegistrationMail, $row["id"]); + } } else { send_mail_registration_allowed_but_failed($homeserver, $first_name . " " . $last_name, $email); $mxMsg = new MatrixMessage(); diff --git a/database.php b/database.php index abfeae3..2078aa8 100644 --- a/database.php +++ b/database.php @@ -1,5 +1,5 @@ setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - $db->exec("CREATE TABLE registrations( - id INTEGER PRIMARY KEY AUTOINCREMENT, - state INT DEFAULT 0, - first_name TEXT, - last_name TEXT, - username TEXT, - note TEXT, - email TEXT, - verify_token TEXT, - admin_token TEXT, - request_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP)"); -} -else { - // establish connection - $db = new PDO('sqlite:' . $db_file); - $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); -} +class mxDatabase +{ + private $db = NULL; -// set writeable when not set already -if (!is_writable($db_file)) { - chmod($db_file, 0777); + /** + * Creates mxDatabase object + * @param db_file path to the sqlite file where the credentials should be stored + */ + function __construct($db_file) { + // create database file when not existent yet + if (!file_exists($db_file)) { + $this->db = new PDO('sqlite:' . $db_file); + $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->db->exec("CREATE TABLE registrations( + id INTEGER PRIMARY KEY AUTOINCREMENT, + state INT DEFAULT 0, + first_name TEXT, + last_name TEXT, + username TEXT, + note TEXT, + email TEXT, + verify_token TEXT, + admin_token TEXT, + request_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP)"); + $this->db->exec("CREATE TABLE logins ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + active INT DEFAULT 1, + firstname TEXT, + lastname TEXT, + localpart TEXT, + password_hash TEXT, + email TEXT, + create_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP + )"); + } + else { + // establish connection + $this->db = new PDO('sqlite:' . $db_file); + $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } + + // set writeable when not set already + if (!is_writable($db_file)) { + chmod($db_file, 0777); + } + } + + /** + * WARNING: This allows accessing the database directly. + * This was only be added for convenience. You are advised to not use this function extensively + * + * @param sql String wich will be passed directly to the database + * @return Response of PDO::query() + */ + function query($sql) { + return $this->db->query($sql); + } + + function setRegistrationStateVerify($state, $token) { + $sql = "UPDATE registrations SET state = " . $state + . ' WHERE verify_token = "' . $token . '";'; + + return $this->db->exec($sql); + } + + function setRegistrationStateById($state, $id) { + $sql = "UPDATE registrations SET state = " . $state + . ' WHERE id = "' . $id . '";'; + + return $this->db->exec($sql); + } + + function setRegistrationStateAdmin($state, $token) { + $sql = "UPDATE registrations SET state = " . $state + . ' WHERE admin_token = "' . $token . '";'; + + return $this->db->exec($sql); + } + + function setRegistrationState($state, $token) { + $sql = "UPDATE registrations SET state = " . $state + . " WHERE verify_token = \"" . $token . '" OR admin_token = "' . $token . '";'; + + return $this->db->exec($sql); + } + + function userPendingRegistrations($username) { + $sql = "SELECT COUNT(*) FROM registrations WHERE username = '" . $username . "' AND NOT state = " + . RegisterState::RegistrationDeclined . " LIMIT 1;"; + $res = $db->query($sql); + if ($res->fetchColumn() > 0) { + return true; + } + return false; + } + function userRegistered($username) { + $sql = "SELECT COUNT(*) FROM logins WHERE localpart = '" . $username . "' LIMIT 1;"; + $res = $this->db->query($sql); + if ($res->fetchColumn() > 0) { + return true; + } + return false; + } + + /** + * Adds user to the database. Next steps should be sending a verify-mail to the user + * @param first_name First name of the user + * @param last_name Sirname of the user + * @param username the future localpart of that user + * @param note Note the user typed in to give a hint + * @param email E-Mail-Adress which will be stored into the database. + * This will be send to the server on first login + * + * @return ["verify_token"] + */ + function addRegistration($first_name, $last_name, $username, $note, $email) { + if ($this->userPendingRegistrations()) { + require_once "language.php"; + throw new Exception($language["USERNAME_PENDING_REGISTRATION"]); + } + if ($this->userRegistered($username)) { + require_once "language.php"; + throw new Exception($language["USERNAME_REGISTERED"]); + } + + $verify_token = bin2hex(random_bytes(16)); + $admin_token = bin2hex(random_bytes(16)); + + $db->exec('INSERT INTO registrations + (first_name, last_name, username, note, email, verify_token, admin_token) + VALUES ("' . $first_name.'","' . $last_name . '","' . $username . '","' . $note . '","' + . $email.'","' .$verify_token.'","' .$admin_token.'")'); + + return [ + "verify_token"=> $verify_token, + ]; + } + + /** + * Gets the user for the verify_admin page. + * + * @return ArrayOfUser|NULL Array with "first_name, last_name, username, note and email" + * as members + */ + function getUserForApproval($admin_token) { + $sql = "SELECT COUNT(*) FROM registrations WHERE admin_token = '" . $admin_token + . "' AND state = " . RegisterState::PendingAdminVerify . " LIMIT 1;"; + $res = $db->query($sql); + $first_name = NULL; $last_name = NULL; $username = NULL; $note = NULL; $email = NULL; + + if ($res->fetchColumn() > 0) { + $sql = "SELECT first_name, last_name, username, note, email FROM registrations" + . " WHERE admin_token = '" . $admin_token . "'" + . " AND state = " . RegisterState::PendingAdminVerify + . " LIMIT 1;"; + foreach ($this->db->query($sql) as $row) { + // will only be executed once + return $row; + } + } + return NULL; + } + + /** + * Gets the user when it opens the page to verify its mail + * + * @return ArrayOfUser|NULL Array with "first_name, last_name, note, email and admin_token" + * as members + */ + function getUserForVerify($verify_token) { + $sql = "SELECT COUNT(*) FROM registrations WHERE verify_token = '" . $verify_token + . "' AND state = " . RegisterState::PendingAdminVerify . " LIMIT 1;"; + $res = $db->query($sql); + $first_name = NULL; $last_name = NULL; $username = NULL; $note = NULL; $email = NULL; + + if ($res->fetchColumn() > 0) { + $sql = "SELECT first_name, last_name, note, email, admin_token FROM registrations " + . " WHERE verify_token = '" . $token . "' LIMIT 1;"; + foreach ($db->query($sql) as $row) { + // will only be executed once + return $row; + } + } + return NULL; + } + + function getUserForLogin($localpart, $password) { + $sql = "SELECT COUNT(*) FROM logins WHERE localpart = '" . $localpart + . "' AND active = 1 LIMIT 1;"; + $res = $this->db->query($sql); + $first_name = NULL; $last_name = NULL; $username = NULL; $note = NULL; $email = NULL; + + if ($res->fetchColumn() > 0) { + $sql = "SELECT first_name, last_name, email, password_hash FROM logins " + . " WHERE verify_token = '" . $token . "' LIMIT 1;"; + foreach ($this->db->query($sql) as $row) { + // will only be executed once + if (password_verify($password, $row["password_hash"])) { + return $row; + } + } + } + return NULL; + } + + /** + * adds User to be able to login afterwards. + * @param first_name First name of the user + * @param last_name Sirname of the user + * @param username the future localpart of that user + * @param email E-Mail-Adress which will be stored into the database. + * This will be send to the server on first login + * + * @return password|NULL with member password as this method generates a + * password and saves that into the database + * NULL when failed + * + */ + function addUser($first_name, $last_name, $username, $email) { + // generate a password with 10 characters + $password = bin2hex(openssl_random_pseudo_bytes(5)); + $password_hash = password_hash($passwort, PASSWORD_BCRYPT, ["cost"=>12]); + + $sql = "INSERT INTO logins (firstname, lastname, localpart, password_hash, email) VALUES " + . '("' . $first_name.'","' . $last_name . '","' . $username . '","' + . $password_hash . '","' . $email . '")'; + + if ($this->db->exec($sql)) { + return $password; + } + return NULL; + } } + +$mx_db = new mxDatabase($db_file); ?> diff --git a/public/login.php b/public/login.php new file mode 100644 index 0000000..ce42668 --- /dev/null +++ b/public/login.php @@ -0,0 +1,105 @@ + [ + "success" => false, + ] +]; + +require_once("config.php"); +require_once("database.php"); +abstract class LoginRequester { + const UNDEFINED = 0; + const MXISD = 1; + const RestAuth = 2; +} +$loginRequester = LoginRequester::UNDEFINED; + +try { + $input = json_decode(' + { + "user": { + "id": "@matrix.id.of.the.user:example.com", + "password": "passwordOfTheUser" + } + } + ',true); + $mxid = NULL; + $localpart = NULL; + if (isset($input["user"])) { + if (isset($input["user"]["localpart"])) { + $localpart = $input["user"]["localpart"]; + $loginRequester = LoginRequester::MXISD; + } elseif (isset($input["user"]["id"])) { + // compatibility for matrix-synapse-rest-auth + $mxid = $input["user"]["id"]; + $loginRequester = LoginRequester::RestAuth; + } elseif (isset($input["user"]["mxid"])) { + // compatibility for mxisd + $mxid = $input["user"]["mxid"]; + $loginRequester = LoginRequester::MXISD; + } + } + + // prefer the localpart attribute of mxisd. But in case of matrix-synapse-rest-auth + // we have to parse it on our own + if (empty($localpart) && !empty($mxid)) { + // A mxid would start with an @ so we start at the 2. position + $sepPos = strpos($mxid,':', 1); + if ($sepPos === false) { + // : not found. Assume mxid is localpart + // TODO: further checks + $localpart = $mxid; + } else { + $localpart = substr($mxid, 1, strpos($mxid,':') - 1 ); + } + } + + if (empty($localpart)) { + throw new Exception ("localpart cannot be identified"); + } + + $password = NULL; + if (isset($input["user"]) && isset($input["user"]["password"])) { + $password = $input["user"]["password"]; + } + if (empty($password)) { + throw new Exception ("password is not present"); + } + + $user = $mx_db->getUserForLogin($localpart, $password); + if (!$user) { + throw new Exception("user not found or password did not match"); + } + $response["auth"]["success"] = true; + $response["auth"]["profile"] = [ + "display_name" => $user["first_name"] . " " . $user["first_name"], + "three_pids" => [ + [ + "medium" => "email", + "address" => $user["email"], + ], + ], + ]; + + switch ($loginRequester) { + case LoginRequester::RestAuth: + $response["auth"]["mxid"] = $mxid; + break; + case LoginRequester::MXISD; + $response["auth"]["id"] = [ + "type" => "localpart", + "value" => $localpart, + ]; + break; + default: + // only return that it was successful. + // we do not know how the data shall be transmitted so we do nothing with it + $response["auth"]["success"] = false; + break; + } +} catch (Exception $e) { + error_log("Auth failed with error: " . $e->getMessage()); + //$response["auth"]["error"] = $e->getMessage(); +} +print (json_encode($response, JSON_PRETTY_PRINT) . "\n"); +?> \ No newline at end of file diff --git a/public/register.php b/public/register.php index d52a2c7..eda0435 100644 --- a/public/register.php +++ b/public/register.php @@ -7,7 +7,6 @@ if (!file_exists("../config.php")) { exit(); } require_once "../config.php"; -require_once "../mail_templates.php"; // enforce admin via https if (!isset($_SERVER['HTTPS'])) { @@ -17,8 +16,6 @@ if (!isset($_SERVER['HTTPS'])) { session_start(); -require_once("../database.php"); - if ($_SERVER["REQUEST_METHOD"] == "POST") { try { if (!isset($_SESSION["token"]) || !isset($_POST["token"]) || $_SESSION["token"] != $_POST["token"]) { @@ -53,48 +50,27 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") { $username = filter_var($_POST["username"], FILTER_SANITIZE_STRING); $note = filter_var($_POST["note"], FILTER_SANITIZE_STRING); $email = filter_var($_POST["email"], FILTER_VALIDATE_EMAIL); - $verify_token = bin2hex(random_bytes(16)); - $admin_token = bin2hex(random_bytes(16)); - # $first="test"; $last="test2"; $user="test3"; $note="empty"; $email="mail+test1@matthias-kesler.de"; + require_once("../database.php"); + $res = $mx_db->addRegistration($first_name, $last_name, $username, $note, $email); - $sql = "SELECT COUNT(*) FROM registrations WHERE username = '" . $username . "' AND NOT state = " - . RegisterState::RegistrationDeclined . " LIMIT 1;"; - $res = $db->query($sql); - if ($res->fetchColumn() > 0) { - throw new Exception($language["USERNAME_PENDING_REGISTRATION"]); + if (!isset($res["verify_token"])) { + error_log("sth. went wrong. registration did not throw but admin_token not set"); + throw Exception ("Unknown Error"); } - require_once("../MatrixConnection.php"); - $mxConn = new MatrixConnection($homeserver, $access_token); - if ($mxConn->hasUser($username)) { - throw new Exception($language["USERNAME_REGISTERED"]); - } - - $db->exec('INSERT INTO registrations - (first_name, last_name, username, note, email, verify_token, admin_token) - VALUES ("' . $first_name.'","' . $last_name . '","' . $username . '","' . $note . '","' - . $email.'","' .$verify_token.'","' .$admin_token.'")'); - # $ins_stmt->bindValue(':first_name', $first); - # $ins_stmt->bindValue(':last_lame', $last); - # $ins_stmt->bindValue(':username', $user); - # $ins_stmt->bindValue(':note', $note); - # $ins_stmt->bindValue(':email', $email); - # $ins_stmt->bindValue(':verify_token', $vToken); - # $ins_stmt->bindValue(':admin_token', $adminToken); - # $ins_stmt->bindValue(':now', date('Y-m-d H:i:s')); - # - # $ins_stmt->execute(); + $verify_token = $res["verify_token"]; $verify_url = $webroot . "/verify.php?t=" . $verify_token; + require_once "../mail_templates.php"; $success = send_mail_pending_verification( $homeserver, $first_name . " " . $last_name, $email, $verify_url); - $db->exec("UPDATE registrations SET state = " . - ($success ? RegisterState::PendingEmailVerify : RegisterState::PendingEmailSend) - . " WHERE verify_token = \"" . $verify_token. "\";"); + $mx_db->setRegistrationStateVerify( + ($success ? RegisterState::PendingEmailVerify : RegisterState::PendingEmailSend), + $verify_token); print("Erfolgreich"); print(""); diff --git a/public/verify.php b/public/verify.php index ed358af..b00a7cc 100644 --- a/public/verify.php +++ b/public/verify.php @@ -28,24 +28,15 @@ try { require_once("../database.php"); - $sql = "SELECT COUNT(*) FROM registrations WHERE verify_token = '" . $token . "' LIMIT 1;"; - $res = $db->query($sql); - - $first_name = NULL; $last_name = NULL; $note = NULL; $email = NULL; $admin_token = NULL; - - if ($res->fetchColumn() > 0) { - $sql = "SELECT first_name, last_name, note, email, admin_token FROM registrations WHERE verify_token = '" . $token . "' LIMIT 1;"; - foreach ($db->query($sql) as $row) { - // will only be executed once - $first_name = $row["first_name"]; - $last_name = $row["last_name"]; - $note = $row["note"]; - $email = $row["email"]; - $admin_token = $row["admin_token"]; - } - } else { + $user = $mx_db->getUserForVerify($token); + if ($user == NULL) { throw new Exception($language["UNKNOWN_TOKEN"]); } + $first_name = $user["first_name"]; + $last_name = $user["last_name"]; + $note = $user["note"]; + $email = $user["email"]; + $admin_token = $user["admin_token"]; require_once("../MatrixConnection.php"); $adminUrl = $webroot . "/verify_admin.php?t=" . $admin_token; @@ -63,9 +54,9 @@ try { if ($response) { $message = $language["SEND_MATRIX_FAIL"]; } - $db->exec("UPDATE registrations SET state = " . - ($response ? RegisterState::PendingAdminVerify : RegisterState::PendingAdminSend) - . " WHERE verify_token = \"" . $token. "\";"); + $mx_db->setRegistrationStateVerify( + ($response ? RegisterState::PendingAdminVerify : RegisterState::PendingAdminSend), + $token); send_mail_pending_approval($homeserver, $first_name . " " . $last_name, $email); diff --git a/public/verify_admin.php b/public/verify_admin.php index 7ba51fd..eadec3a 100644 --- a/public/verify_admin.php +++ b/public/verify_admin.php @@ -40,47 +40,33 @@ try { } } - $sql = "SELECT COUNT(*) FROM registrations WHERE admin_token = '" . $token - . "' AND state = " . RegisterState::PendingAdminVerify . " LIMIT 1;"; - $res = $db->query($sql); - $first_name = NULL; $last_name = NULL; $username = NULL; $note = NULL; $email = NULL; - - if ($res->fetchColumn() > 0) { - $sql = "SELECT first_name, last_name, username, note, email FROM registrations WHERE admin_token = '" . $token - . "' AND state = " . RegisterState::PendingAdminVerify . " LIMIT 1;"; - foreach ($db->query($sql) as $row) { - // will only be executed once - $first_name = $row["first_name"]; - $last_name = $row["last_name"]; - $username = $row["username"]; - $note = $row["note"]; - $email = $row["email"]; - } - } else { + $user = $mx_db->getUserForApproval($token); + if ($user == NULL) { throw new Exception($language["UNKNOWN_TOKEN"]); } + $first_name = $user["first_name"]; + $last_name = $user["last_name"]; + $username = $user["username"]; + $note = $user["note"]; + $email = $user["email"]; + if ($action == RegisterState::RegistrationAccepted) { - $db->exec("UPDATE registrations SET state = " . RegisterState::PendingRegistration - . " WHERE admin_token = '" . $token. "';"); + $mx_db->setRegistrationStateAdmin(RegisterState::PendingRegistration, $token); // register user require_once("../MatrixConnection.php"); $mxConn = new MatrixConnection($homeserver, $access_token); // generate a password with 8 characters - $password = bin2hex(openssl_random_pseudo_bytes(4)); - - $res = $mxConn->register($username, $password, $registration_shared_secret); - if ($res) { + $password = addUser($first_name, $last_name, $username, $email); + if ($password != NULL) { // send registration_success $res = send_mail_registration_success($homeserver, $first_name . " " . $last_name, $email, $username, $password, $howToURL); if ($res) { - $db->exec("UPDATE registrations SET state = " . RegisterState::AllDone - . " WHERE admin_token = '" . $token. "';"); + $mx_db->setRegistrationStateAdmin(RegisterState::AllDone, $token); } else { - $db->exec("UPDATE registrations SET state = " . RegisterState::PendingSendRegistrationMail - . " WHERE admin_token = '" . $token. "';"); + $mx_db->setRegistrationStateAdmin(RegisterState::PendingSendRegistrationMail, $token); } } else { send_mail_registration_allowed_but_failed($homeserver, $first_name . " " . $last_name, $email); @@ -96,8 +82,7 @@ try { print("

" . $language["ADMIN_VERIFY_SITE_TITLE"] . "

"); print("

" . $language["ADMIN_REGISTER_ACCEPTED_BODY"] . "

"); } elseif ($action == RegisterState::RegistrationDeclined) { - $db->exec("UPDATE registrations SET state = " . RegisterState::RegistrationDeclined - . " WHERE admin_token = '" . $token. "';"); + $mx_db->setRegistrationStateAdmin(RegisterState::RegistrationDeclined, $token); send_mail_registration_decline($homeserver, $first_name . " " . $last_name, $email, $decline_reason); print("" . $language["ADMIN_VERIFY_SITE_TITLE"] . ""); print("");