diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f4773f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config.php diff --git a/MatrixConnection.php b/MatrixConnection.php new file mode 100644 index 0000000..e97a39e --- /dev/null +++ b/MatrixConnection.php @@ -0,0 +1,156 @@ +hs = $homeserver; + $this->at = $access_token; + } + + function send($room_id, $message) { + if (!$this->at) { + error_log("No access token defined"); + return false; + } + + $send_message = NULL; + if (!$message) { + error_log("no message to send"); + } elseif(is_array($message)) { + $send_message = $message; + } elseif ($message instanceof MatrixMessage) { + $send_message = $message->get_object(); + } else { + error_log("message is of not valid type\n"); + return false; + } + + $url="https://".$this->hs."/_matrix/client/r0/rooms/" + . urlencode($room_id) ."/send/m.room.message?access_token=".$this->at; + $handle = curl_init($url); + curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); + curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 5); + curl_setopt($handle, CURLOPT_TIMEOUT, 60); + curl_setopt($handle, CURLOPT_POSTFIELDS, json_encode($send_message)); + curl_setopt($handle, CURLOPT_HTTPHEADER, array("Content-Type: application/json")); + + $response = $this->exec_curl_request($handle); + return isset($response["event_id"]); + } + + function send_msg($room_id, $message) { + return $this->send($room_id, array( + "msgtype" => "m.notice", + "body" => $message + ) + ); + } + + function hasUser($username) { + if (!$username) { + throw new Exception ("no user given to lookup"); + } + + $url = "https://".$this->hs."/_matrix/client/r0/profile/@" . $username . ":" . $this->hs; + $handle = curl_init($url); + curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); + curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 5); + curl_setopt($handle, CURLOPT_TIMEOUT, 60); + curl_setopt($handle, CURLOPT_HTTPHEADER, array("Content-Type: application/json")); + + $res = $this->exec_curl_request($handle); + return !(isset($res["errcode"]) && $res["errcode"] == "M_UNKNOWN"); + } + + function register($username, $password, $shared_secret) { + if (!$username) { + error_log("no username provided"); + } + if (!$password) { + error_log("no message to send"); + } + + $mac = hash_hmac('sha1', $username, $shared_secret); + + $data = array( + "username" => $username, + "password" => $password, + "mac" => $mac, + ); + $url = "https://".$this->hs."/_matrix/client/v2_alpha/register"; + $handle = curl_init($url); + curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); + curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 5); + curl_setopt($handle, CURLOPT_TIMEOUT, 60); + curl_setopt($handle, CURLOPT_HTTPHEADER, array("Content-Type: application/json")); + curl_setopt($handle, CURLOPT_POSTFIELDS, json_encode($data)); + + return $this->exec_curl_request($handle); + } + + function exec_curl_request($handle) { + $response = curl_exec($handle); + + if ($response === false) { + $errno = curl_errno($handle); + $error = curl_error($handle); + error_log("Curl returned error $errno: $error\n"); + curl_close($handle); + return false; + } + + $http_code = intval(curl_getinfo($handle, CURLINFO_HTTP_CODE)); + curl_close($handle); + + if ($http_code >= 500) { + // do not want to DDOS server if something goes wrong + sleep(10); + return false; + } else if ($http_code != 200) { + $response = json_decode($response, true); + error_log("Request has failed with error {$response['error']}\n"); + if ($http_code == 401) { + throw new Exception('Invalid access token provided'); + } + } else { + $response = json_decode($response, true); + } + + return $response; + } +} + +class MatrixMessage +{ + private $message; + + function __construct() { + $this->message = array( + "msgtype" => "m.notice", + ); + } + + function set_type($msgtype) { + $this->message["msgtype"] = $msgtype; + } + + function set_format($format) { + $this->message["format"] = $format; + } + + function set_body($body) { + $this->message["body"] = $body; + } + + function set_formatted_body($fbody, $format="org.matrix.custom.html") { + $this->message["formatted_body"] = $fbody; + $this->message["format"] = $format; + } + + function get_object() { + return $this->message; + } +} +?> diff --git a/config.sample.php b/config.sample.php new file mode 100644 index 0000000..419a682 --- /dev/null +++ b/config.sample.php @@ -0,0 +1,9 @@ + diff --git a/cron.php b/cron.php new file mode 100644 index 0000000..861137e --- /dev/null +++ b/cron.php @@ -0,0 +1,96 @@ +query($sql) as $row) { + $first_name = $row["first_name"]; + $last_name = $row["last_name"]; + $username = $row["username"]; + $email = $row["email"]; + $state = $row["state"]; + + try { + switch ($state) { + case RegisterState::PendingEmailSend: + $verify_url = $webroot . "/verify.php?t=" . $row["verify_token"]; + $success = send_mail_pending_verification( + $homeserver, + $row["first_name"] . " " . $row["last_name"], + $row["email"], + $row["verify_url"]); + + if ($success) { + $db->exec("UPDATE registrations SET state = " . RegisterState::PendingEmailVerify + . " WHERE id = " . $row["id"] . ";"); + } else { + throw new Exception("Could not send mail to ".$row["first_name"]." ".$row["last_name"]."(".$row["id"].")"); + } + break; + case RegisterState::PendingAdminSend: + require_once("MatrixConnection.php"); + $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" + . $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:
" + . $row["note"] . "
" + . "Zum Bearbeiten hier klicken"); + $mxMsg->set_type("m.text"); + $response = $mxConn->send($register_room, $mxMsg); + + if ($response) { + $db->exec("UPDATE registrations SET state = " . RegisterState::PendingAdminVerify + . " WHERE id = " . $row["id"] . ";"); + + send_mail_pending_approval($homeserver, $first_name . " " . $last_name, $email); + } else { + throw new Exception("Could not send notification for ".$row["first_name"]." ".$row["last_name"]."(".$row["id"].") to admins."); + } + break; + 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) { + // send registration_success + send_mail_registration_success($homeserver, $first_name . " " . $last_name, $email, $username, $password, $howToURL); + } else { + send_mail_registration_allowed_but_failed($homeserver, $first_name . " " . $last_name, $email); + $mxMsg = new MatrixMessage(); + $mxMsg->set_type("m.text"); + $mxMsg->set_body("Fehler beim Registrieren von " . $first_name . " " . $last_name . "."); + $mxConn->send($register_room, $mxMsg); + throw new Exception($language["REGISTRATION_FAILED"]); + } + break; + case RegisterState::PendingSendRegistrationMail: + print ("Error: Unhandled state: PendingSendRegistrationMail for " . $first_name . " " . $last_name . " (" . $username . ")\n"); + break; + case RegisterState::RegistrationDeclined: + case RegisterState::AllDone: + // do reqular cleanup + break; + } + } catch (Exception $e) { + print("Error while handling cron for " . $first_name . " " . $last_name . " (" . $username . ")\n"); + print($e->getMessage()); + } +} +?> diff --git a/database.php b/database.php new file mode 100644 index 0000000..abfeae3 --- /dev/null +++ b/database.php @@ -0,0 +1,55 @@ +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); +} + +// set writeable when not set already +if (!is_writable($db_file)) { + chmod($db_file, 0777); +} +?> diff --git a/lang/lang.de-de.php b/lang/lang.de-de.php new file mode 100644 index 0000000..84dafb0 --- /dev/null +++ b/lang/lang.de-de.php @@ -0,0 +1,27 @@ + "Es konnte keine Konfiguration gefunden werden.", + "UNKNOWN_SESSION" => "Sitzungstoken nicht vorhanden oder ungültig.", + "UNKNOWN_USERNAME" => "Nutzername fehlt", + "UNKNOWN_TOKEN" => "Token ist unbekannt", + "USERNAME_LENGTH_INVALID" => "Entweder mehr als 20 oder weniger als 3 Zeichen für den Nutzernamen verwendet", + "USERNAME_NOT_ALNUM" => "Nutzername ist nicht alphanumerisch", + "USERNAME_PENDING_REGISTRATION" => "Dieser Nutzername wurde bereits zur Registrierung vorgemerkt. Versuche es später noch einmal oder wähle einen anderen Nutzernamen", + "USERNAME_REGISTERED" => "Dieser Nutzername wurde bereits registriert. Bitte wähle einen anderen Nutzernamen", + "PASSWORD_NOT_MATCH" => "Passwörter stimmen nicht überein", + "NOTE_LENGTH_EXEEDED" => "Notiz ist länger als die erlaubten 50 Zeichen", + "EMAIL_INVALID_FORMAT" => "Keine valide E-Mail-Adresse angegeben", + "FIRSTNAME_INVALID_FORMAT" => "Vorname hat ungültiges Format", + "SIRNAME_INVALID_FORMAT" => "Nachname hat ungültiges Format", + "SEND_MAIL_FAIL" => "Senden der E-Mail fehlgeschlagen", + "SEND_MATRIX_FAIL" => "Senden einer Nachricht an die Administratoren fehlgeschlagen", + "REGISTRATION_REQUEST_FAILED" => "Registrierungsanfrage ist fehlgeschlagen", + "REGISTRATION_FAILED" => "Registrierung ist fehlgeschlagen", + "VERIFICATION_SUCEEDED" => "Verifizierung erfolgreich", + "VERIFICATION_FAILED" => "Verifizierung fehlgeschlagen", + "VERIFICATION_SUCCESS_BODY" => "Vielen Dank. Die Administratoren wurden informiert", + "ADMIN_VERIFY_SITE_TITLE" => "Registrierungsanfrage bearbeiten", + "ADMIN_REGISTER_ACCEPTED_BODY" => "Die Registrierungsanfrage wurde akzeptiert. Der Nutzer wurde per Mail informiert.", + "ADMIN_REGISTER_DECLINED_BODY" => "Die Registrierungsanfrage wurde angelehnt. Der Nutzer wurde per Mail informiert.", +); +?> diff --git a/language.php b/language.php new file mode 100644 index 0000000..bd50c04 --- /dev/null +++ b/language.php @@ -0,0 +1,12 @@ + diff --git a/mail_templates.php b/mail_templates.php new file mode 100644 index 0000000..5db7b81 --- /dev/null +++ b/mail_templates.php @@ -0,0 +1,103 @@ + diff --git a/public/register.php b/public/register.php new file mode 100644 index 0000000..d52a2c7 --- /dev/null +++ b/public/register.php @@ -0,0 +1,225 @@ + + + 20 || strlen($_POST["username"]) < 3)) { + throw new Exception($language["USERNAME_LENGTH_INVALID"]); + } + if (ctype_alnum($_POST['username']) != true) { + throw new Exception($language["USERNAME_NOT_ALNUM"]); + } + if (isset($_POST["note"]) && strlen($_POST["note"]) > 50) { + throw new Exception($language["NOTE_LENGTH_EXEEDED"]); + } + if (!isset($_POST["email"]) || !filter_var($_POST["email"], FILTER_VALIDATE_EMAIL)) { + throw new Exception($language["EMAIL_INVALID_FORMAT"]); + } + if (isset($_POST["first_name"]) && ! preg_match("/[A-Z][a-z]+/", $_POST["first_name"])) { + throw new Exception($language["FIRSTNAME_INVALID_FORMAT"]); + } + if (isset($_POST["last_name"]) && ! preg_match("/[A-Z][a-z]+/", $_POST["last_name"])) { + throw new Exception($language["SIRNAME_INVALID_FORMAT"]); + } + + // check valid password + $first_name = filter_var($_POST["first_name"], FILTER_SANITIZE_STRING); + $last_name = filter_var($_POST["last_name"], FILTER_SANITIZE_STRING); + $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"; + + $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"]); + } + 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_url = $webroot . "/verify.php?t=" . $verify_token; + $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. "\";"); + + print("Erfolgreich"); + print(""); + print("

Erfolgreich

"); + print("

Bitte überprüfe deine E-Mails um deine E-Mail-Adresse zu bestätigen.

"); + print("Zur Registrierungsseite"); + } catch (Exception $e) { + print("" . $language["REGISTRATION_REQUEST_FAILED"] . ""); + print(""); + print("

" . $language["REGISTRATION_REQUEST_FAILED"] . "

"); + print("

" . $e->getMessage() . "

"); + print("Zur Registrierungsseite"); + } +} else { + $_SESSION["token"] = bin2hex(random_bytes(16)); +?> + Registriere dich für <?php echo $homeserver; ?> + + + + + + +
+
+
+
+
+

Bitte für registrieren2-Schritt-Registrierung

+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+
+
+
+ +
+
+
+ */ ?> + "> + + + +

Hinweis:
+ cg-s.tk is ein geschlossenes Chat-Netzwerk in dem jeder Nutzer bestätigt werden muss.
+ Du bekommst eine E-Mail wenn jemand deine Mitgliedschaft bestätigt hat. An diese wird auch dein initiales Passwort gesendet. + Hinterlasse also bitte einen Hinweis zu dir (der nur den entsprechenden Personen gezeigt wird).
+ Liebe Grüße vom Team von cg-s.tk +

+
+
+
+
+ + + + + diff --git a/public/verify.php b/public/verify.php new file mode 100644 index 0000000..ed358af --- /dev/null +++ b/public/verify.php @@ -0,0 +1,86 @@ + + +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 { + throw new Exception($language["UNKNOWN_TOKEN"]); + } + + require_once("../MatrixConnection.php"); + $adminUrl = $webroot . "/verify_admin.php?t=" . $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" + . $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:
" + . $note . "
" + . "Zum Bearbeiten hier klicken"); + $mxMsg->set_type("m.text"); + $response = $mxConn->send($register_room, $mxMsg); + + if ($response) { + $message = $language["SEND_MATRIX_FAIL"]; + } + $db->exec("UPDATE registrations SET state = " . + ($response ? RegisterState::PendingAdminVerify : RegisterState::PendingAdminSend) + . " WHERE verify_token = \"" . $token. "\";"); + + send_mail_pending_approval($homeserver, $first_name . " " . $last_name, $email); + + print("" . $language["VERIFICATION_SUCEEDED"] . ""); + print(""); + print("

" . $language["VERIFICATION_SUCEEDED"] . "

"); + print("

" . $language["VERIFICATION_SUCCESS_BODY"] . "

"); + print("Zur Registrierungsseite"); +} catch (Exception $e) { + print("" . $language["VERIFICATION_FAILED"] . ""); + print(""); + print("

" . $language["VERIFICATION_FAILED"] . "

"); + print("

" . $e->getMessage() . "

"); + print("Zur Registrierungsseite"); +} +?> + + diff --git a/public/verify_admin.php b/public/verify_admin.php new file mode 100644 index 0000000..7ba51fd --- /dev/null +++ b/public/verify_admin.php @@ -0,0 +1,183 @@ + + +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 { + throw new Exception($language["UNKNOWN_TOKEN"]); + } + + if ($action == RegisterState::RegistrationAccepted) { + $db->exec("UPDATE registrations SET state = " . RegisterState::PendingRegistration + . " WHERE admin_token = '" . $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) { + // 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. "';"); + } else { + $db->exec("UPDATE registrations SET state = " . RegisterState::PendingSendRegistrationMail + . " WHERE admin_token = '" . $token. "';"); + } + } else { + send_mail_registration_allowed_but_failed($homeserver, $first_name . " " . $last_name, $email); + $mxMsg = new MatrixMessage(); + $mxMsg->set_type("m.text"); + $mxMsg->set_body("Fehler beim Registrieren von " . $first_name . " " . $last_name . "."); + $mxConn->send($register_room, $mxMsg); + throw new Exception($language["REGISTRATION_FAILED"]); + } + + print("" . $language["ADMIN_VERIFY_SITE_TITLE"] . ""); + print(""); + 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. "';"); + send_mail_registration_decline($homeserver, $first_name . " " . $last_name, $email, $decline_reason); + print("" . $language["ADMIN_VERIFY_SITE_TITLE"] . ""); + print(""); + print("

" . $language["ADMIN_VERIFY_SITE_TITLE"] . "

"); + print("

" . $language["ADMIN_REGISTER_DECLINED_BODY"] . "

"); + } else { + + print("" . $language["ADMIN_VERIFY_SITE_TITLE"] . ""); + ?> + + + + + + +
+
+
+
+
+

+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+ +
+ +
+ +
+ +
+ + + + +
+
+
+
+
+
+