· 7 years ago · Apr 03, 2018, 06:48 PM
1<?php
2error_reporting(0);
3/*
4Hostname - External hostname
5External - External path to play
6SecretKey - reCaptcha secret key, leave blank to disable
7Cloudflare - Is host behind cloudflare?
8Activate - Activate user by default (true) or send verification email (false)
9Clean - Delete old inactive accounts?
10CleanDays - Number of days before inactive accounts expire
11ForceCase - Force CamelCase on usernames
12AllowedChars - Allowed characters in usernames
13EmailWhitelist - List of allowed email domains, can be array or path to list file, leave blank to disable
14MaxPerEmail - Max no of accounts per email
15Database
16 Host - MySQL host
17 User - MySQL user
18 Pass - MySQL password
19 Name - Database name
20*/
21$config = [
22 "Hostname" => "cpremastered.xyzy",
23 "External" => "http://cpremastered.xyz",
24 "SecretKey" => "6LeRqVAUAAAAAKGHMompvLregYTgYUEwSjUh-eVZ",
25 "Cloudflare" => false,
26 "Activate" => false,
27 "Clean" => true,
28 "CleanDays" => 10,
29 "ForceCase" => true,
30 "AllowedChars" => "A-Za-z0-9)(*&^$!`\_+={};:@~#>.<",
31 // "EmailWhitelist" => ["gmail.com", "hotmail.com"]
32 // "EmailWhitelist" => "/path/to/whitelist"
33 "EmailWhitelist" => [],
34 "MaxPerEmail" => 5,
35 "Database" => [
36 "Host" => "http://45.32.199.143/XsCKyP6daTM4Dsvadq7wbt5PGGktHs/X6eQQEp4ySdTJTm2Eds7b8Dz9jSW3J.php",
37 "User" => "root",
38 "Pass" => "S7uGF]g}>C)gRX9e/:R&]?4R2;e$tP",
39 "Name" => "houdini"
40 ]
41];
42final class Database extends PDO {
43 private $connection = null;
44 public function __construct($host, $user, $password, $database) {
45 $connectionString = sprintf("mysql:dbname=%s;host=%s", $database, $host);
46 parent::__construct($connectionString, $user, $password);
47 }
48
49 public function encryptPassword($password, $md5 = true) {
50 if($md5 !== false) {
51 $password = md5($password);
52 }
53 $hash = substr($password, 16, 16) . substr($password, 0, 16);
54 return $hash;
55 }
56 public function getLoginHash($password, $staticKey) {
57 $hash = $this->encryptPassword($password, false);
58 $hash .= $staticKey;
59 $hash .= 'Y(02.>\'H}t":E1';
60 $hash = $this->encryptPassword($hash);
61 return $hash;
62 }
63 public function addUser($username, $password, $color, $email, $isActive = 0) {
64 $hashedPassword = strtoupper(md5($password));
65 $staticKey = "houdini";
66 $flashClientHash = $this->getLoginHash($hashedPassword, $staticKey);
67 $bcryptPassword = password_hash($flashClientHash, PASSWORD_DEFAULT, [ "cost" => 12 ]);
68 $insertPenguin = "INSERT INTO `penguin` (`ID`, `Username`, `Nickname`, `Password`, `Email`, `Active`, `Color`) VALUES ";
69 $insertPenguin .= "(NULL, :Username, :Username, :Password, :Email, :Active, :Color);";
70
71 $insertStatement = $this->prepare($insertPenguin);
72 $insertStatement->bindValue(":Username", $username);
73 $insertStatement->bindValue(":Password", $bcryptPassword);
74 $insertStatement->bindValue(":Email", $email);
75 $insertStatement->bindValue(":Active", $isActive);
76 $insertStatement->bindValue(":Color", $color);
77
78 $insertStatement->execute();
79 $insertStatement->closeCursor();
80
81 $penguinId = $this->lastInsertId();
82
83 $this->insertInventory($penguinId, $color);
84 $this->addActiveIgloo($penguinId);
85 $this->sendMail($penguinId, null, 125);
86 return $penguinId;
87 }
88 public function insertInventory($penguinId, $itemId) {
89 $insertInventory = $this->prepare("INSERT INTO `inventory` (`PenguinID`, `ItemID`) VALUES (:PenguinID, :ItemID);");
90 $insertInventory->bindValue(":PenguinID", $penguinId);
91 $insertInventory->bindValue(":ItemID", $itemId);
92 $insertInventory->execute();
93 $insertInventory->closeCursor();
94 }
95
96 public function sendMail($recipientId, $senderId, $postcardType) {
97 $sendMail = $this->prepare("INSERT INTO `postcard` (`ID`, `SenderID`, `RecipientID`, `Type`) VALUES (NULL, :SenderID, :RecipientID, :Type);");
98 $sendMail->bindValue(":RecipientID", $recipientId);
99 $sendMail->bindValue(":SenderID", $senderId);
100 $sendMail->bindValue(":Type", $postcardType);
101 $sendMail->execute();
102 $sendMail->closeCursor();
103 $postcardId = $this->lastInsertId();
104 return $postcardId;
105 }
106 private function addActiveIgloo($penguinId) {
107 $insertStatement = $this->prepare("INSERT INTO `igloo` (`ID`, `PenguinID`) VALUES (NULL, :PenguinID);");
108 $insertStatement->bindValue(":PenguinID", $penguinId);
109 $insertStatement->execute();
110 $insertStatement->closeCursor();
111
112 $iglooId = $this->lastInsertId();
113 return $iglooId;
114 }
115
116 public function usernameTaken($username) {
117 $usernameTaken = "SELECT Username FROM `penguin` WHERE Username = :Username;";
118
119 $takenQuery = $this->prepare($usernameTaken);
120 $takenQuery->bindValue(":Username", $username);
121 $takenQuery->execute();
122
123 $rowCount = $takenQuery->rowCount();
124 $takenQuery->closeCursor();
125
126 return $rowCount > 0;
127 }
128 public function getEmailCount($email) {
129 $emailCount = "SELECT ID FROM `penguin` WHERE Email = :Email;";
130
131 $emailQuery = $this->prepare($emailCount);
132 $emailQuery->bindValue(":Email", $email);
133 $emailQuery->execute();
134
135 $rowCount = $emailQuery->rowCount();
136 $emailQuery->closeCursor();
137
138 return $rowCount;
139 }
140 public function createActivationKey($penguinId, $key) {
141 $insertStatement = $this->prepare("INSERT INTO `activation_key` (`PenguinID`, `ActivationKey`) VALUES (:PenguinID, :Key);");
142 $insertStatement->bindValue(":PenguinID", $penguinId);
143 $insertStatement->bindValue(":Key", $key);
144 $insertStatement->execute();
145 $insertStatement->closeCursor();
146 }
147 public function activateUser($penguinId, $key) {
148 $setActive = $this->prepare("UPDATE `penguin` INNER JOIN activation_key on penguin.ID = activation_key.PenguinID " .
149 "SET penguin.Active = 1 WHERE activation_key.ActivationKey = :Key;");
150 $setActive->bindValue(":Key", $key);
151 $setActive->execute();
152 if($setActive->rowCount() > 0) {
153 $deleteActivation = $this->prepare("DELETE FROM `activation_key` WHERE `PenguinID` = :PenguinID");
154 $deleteActivation->bindValue(":PenguinID", $penguinId);
155 $deleteActivation->execute();
156 }
157 $setActive->closeCursor();
158 $deleteActivation->closeCursor();
159 }
160
161 public function takenUsernames($username) {
162 $usernamesTaken = "SELECT Username FROM `penguin` WHERE Username LIKE :Username;";
163
164 $usernamesQuery = $this->prepare($usernamesTaken);
165 $usernamesQuery->bindValue(":Username", $username . "%");
166 $usernamesQuery->execute();
167
168 $usernames = $usernamesQuery->fetchAll(self::FETCH_COLUMN);
169 return $usernames;
170 }
171 public function cleanInactive($expiry = 10) {
172 $deleteInactive = "DELETE FROM `penguin` WHERE Active = 0 AND RegistrationDate < :Expiry;";
173 $deleteQuery = $this->prepare($deleteInactive);
174 $deleteQuery->bindValue(":Expiry", date("Y-m-d", strtotime("-$expiry days", time())));
175 $deleteQuery->execute();
176 }
177}
178$localization = [
179 "en" => [
180 "terms" => "You must agree to the Rules and Terms of Use.",
181 "name_missing" => "You need to name your penguin.",
182 "name_short" => "Penguin name is too short.",
183 "name_number" => "Penguin names can only contain 5 numbers.",
184 "penguin_letter" => "Penguin names must contain at least 1 letter.",
185 "name_not_allowed" => "That penguin name is not allowed.",
186 "name_taken" => "That penguin name is already taken.",
187 "name_suggest" => "That penguin name is already taken. Try {suggestion}.",
188 "passwords_match" => "Passwords do not match.",
189 "password_short" => "Password is too short.",
190 "email_invalid" => "Invalid email address."
191 ],
192 "fr" => [
193 "terms" => "Tu dois accepter les conditions d'utilisation.",
194 "name_missing" => "Tu dois donner un nom à ton pingouin.",
195 "name_short" => "Le nom de pingouin est trop court.",
196 "name_number" => "Un nom de pingouin ne peut contenir plus de 5 nombres.",
197 "penguin_letter" => "Un nom de pingouin doit contenir au moins une lettre.",
198 "name_not_allowed" => "Ce nom de pingouing n'est pas autorisé.",
199 "name_taken" => "Ce nom de pingouin est pris.",
200 "name_suggest" => "Ce nom de pingouin est pris. Essaye {suggestion}.",
201 "passwords_match" => "Les mots de passes ne correspondent pas.",
202 "password_short" => "Le mot de passe est trop court.",
203 "email_invalid" => "Adresse email invalide."
204 ],
205 "es" => [
206 "terms" => "Debes seguir las reglas y los términos de uso.",
207 "name_missing" => "Debes escoger un nombre para tu pingüino.",
208 "name_short" => "El nombre de tu pingüino es muy corto.",
209 "name_number" => "Los nombres de usuario sólo pueden tener 5 números.",
210 "penguin_letter" => "Los nombres de usuario deben tener por lo menos 1 letra.",
211 "name_not_allowed" => "Ese nombre de usuario no está permitido.",
212 "name_taken" => "Ese nombre de usuario ya ha sido escogido.",
213 "name_suggest" => "Ese nombre de usuario ya ha sido escogido. Intenta éste {suggestion}.",
214 "passwords_match" => "Las contraseñas no coinciden.",
215 "password_short" => "La contraseña es muy corta.",
216 "email_invalid" => "El correo eléctronico es incorrecto."
217 ],
218 "pt" => [
219 "terms" => "Você precisa concordar com as Regras e com os Termos de Uso.",
220 "name_missing" => "Voce precisa nomear seu pinguim.",
221 "name_short" => "O nome do pinguim é muito curto.",
222 "name_number" => "O nome do pinguim só pode conter 5 números",
223 "penguin_letter" => "O nome do seu pinguim tem de conter pelomenos uma letra.",
224 "name_not_allowed" => "Esse nome de pinguim não é permitido.",
225 "name_taken" => "Esse nome de pinguim já foi escolhido.",
226 "name_suggest" => "Esse nome de pinguim já foi escolhido. Tente {suggestion}.",
227 "passwords_match" => "As senhas não estão iguais.",
228 "password_short" => "A senha é muito curta.",
229 "email_invalid" => "Esse endereço de E-Mail é invalido."
230 ]
231];
232if(!is_array($config["EmailWhitelist"]) && !empty($config["EmailWhitelist"])) {
233 $emailWhitelistFile = file_get_contents($config["EmailWhitelist"]);
234 $config["EmailWhitelist"] = explode("\n", $emailWhitelistFile);
235}
236if(isset($_GET["key"])) {
237 $db = new Database($config["Database"]["Host"], $config["Database"]["User"],
238 $config["Database"]["Pass"], $config["Database"]["Name"]);
239 $key = $_GET["key"];
240 $rawKey = base64_decode($key);
241 $rawKey = explode(":", $rawKey);
242 list($penguinId, $activationKey) = $rawKey;
243
244 $db->activateUser($penguinId, $activationKey);
245 header("Location: " . $config["External"]);
246 die($penguinId . $activationKey);
247}
248session_start();
249function response($data) {
250 die(http_build_query($data));
251}
252function attemptDataRetrieval($key, $session = false) {
253 if(!$session && array_key_exists($key, $_POST)) {
254 return $_POST[$key];
255 }
256 if($session && array_key_exists($key, $_SESSION)) {
257 return $_SESSION[$key];
258 }
259 response([
260 "error" => ""
261 ]);
262}
263function generateActivationKey($length, $keyspace = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") {
264 $str = "";
265 $max = mb_strlen($keyspace, "8bit") - 1;
266 for ($i = 0; $i < $length; ++$i) {
267 $str .= $keyspace[random_int(0, $max)];
268 }
269 return $str;
270}
271function createActivateUrl($baseUrl, $penguinId, $activationKey) {
272 $rawKey = implode(":", [$penguinId, $activationKey]);
273 $key = base64_encode($rawKey);
274 return $baseUrl . "/create_account/create_account.php?key=" . $key;
275}
276$action = attemptDataRetrieval("action");
277$lang = attemptDataRetrieval("lang");
278if(!in_array($lang, array_keys($localization))) {
279 response([
280 "error" => ""
281 ]);
282}
283if($action == "validate_agreement") {
284 $agreeTerms = attemptDataRetrieval("agree_to_terms");
285 $agreeRules = attemptDataRetrieval("agree_to_rules");
286 if(!$agreeTerms || !$agreeRules) {
287 response([
288 "error" => $localization[$lang]["terms"]
289 ]);
290 }
291
292 response([
293 "success" => 1
294 ]);
295} elseif($action == "validate_username") {
296 $username = attemptDataRetrieval("username");
297 $color = attemptDataRetrieval("colour");
298 $colors = range(1, 15);
299
300 if(strlen($username) == 0) {
301 response([
302 "error" => $localization[$lang]["name_missing"]
303 ]);
304 } elseif(strlen($username) < 4 || strlen($username) > 12) {
305 response([
306 "error" => $localization[$lang]["name_short"]
307 ]);
308 } elseif(preg_match_all("/[0-9]/", $username) > 5) {
309 response([
310 "error" => $localization[$lang]["name_number"]
311 ]);
312 } elseif(!preg_match("/[A-z]/i", $username)) {
313 response([
314 "error" => $localization[$lang]["penguin_letter"]
315 ]);
316 } elseif(preg_match("/[^" . $config["AllowedChars"] . "]/", $username)) {
317 response([
318 "error" => $localization[$lang]["name_not_allowed"]
319 ]);
320 } elseif(!is_numeric($color) || !in_array($color, $colors)) {
321 response([
322 "error" => ""
323 ]);
324 }
325
326 $db = new Database($config["Database"]["Host"], $config["Database"]["User"],
327 $config["Database"]["Pass"], $config["Database"]["Name"]);
328 if($db->usernameTaken($username)) {
329 $username = preg_replace("/\d+$/", "", $username);
330 $takenUsernames = $db->takenUsernames($username);
331 $i = 1;
332 while(true) {
333 $suggestion = $username . $i++;
334 if(preg_match_all("/[0-9]/", $username) > 1) {
335 response([
336 "error" => $localization[$lang]["name_taken"]
337 ]);
338 }
339 if(!in_array(strtolower($suggestion), $takenUsernames)) {
340 break;
341 }
342 }
343 response([
344 "error" => str_replace("{suggestion}", $suggestion, $localization[$lang]["name_suggest"])
345 ]);
346 }
347
348 $_SESSION["sid"] = session_id();
349 $_SESSION["username"] = ($config["ForceCase"] ? ucfirst(strtolower($username)) : $username);
350 $_SESSION["colour"] = $color;
351
352 response([
353 "success" => 1,
354 "sid" => session_id()
355 ]);
356} elseif($action == "validate_password_email") {
357 $sessionId = attemptDataRetrieval("sid", true);
358 $username = attemptDataRetrieval("username", true);
359 $color = attemptDataRetrieval("colour", true);
360 $password = attemptDataRetrieval("password");
361 $passwordConfirm = attemptDataRetrieval("password_confirm");
362 $email = attemptDataRetrieval("email");
363 $gtoken = attemptDataRetrieval("gtoken");
364 if(!empty($config["SecretKey"])) {
365 $data = [
366 "secret" => $config["SecretKey"],
367 "response" => $gtoken,
368 "remoteip" => ($config["Cloudflare"] ? $_SERVER["HTTP_CF_CONNECTING_IP"] : $_SERVER['REMOTE_ADDR'])
369 ];
370 $verify = curl_init();
371 curl_setopt($verify, CURLOPT_URL, "https://www.google.com/recaptcha/api/siteverify");
372 curl_setopt($verify, CURLOPT_POST, true);
373 curl_setopt($verify, CURLOPT_POSTFIELDS, http_build_query($data));
374 curl_setopt($verify, CURLOPT_SSL_VERIFYPEER, false);
375 curl_setopt($verify, CURLOPT_RETURNTRANSFER, true);
376 $response = curl_exec($verify);
377 $result = json_decode($response);
378 }
379 $emailDomain = substr(strrchr($email, "@"), 1);
380 if($sessionId !== session_id()) {
381 response([
382 "error" => ""
383 ]);
384 } elseif(empty($result->success) && !empty($config["SecretKey"])) {
385 response([
386 "error" => ""
387 ]);
388 } elseif($password !== $passwordConfirm) {
389 response([
390 "error" => $localization[$lang]["passwords_match"]
391 ]);
392 } elseif(strlen($password) < 4) {
393 response([
394 "error" => $localization[$lang]["password_short"]
395 ]);
396 } elseif(!filter_var($email, FILTER_VALIDATE_EMAIL)) {
397 response([
398 "error" => $localization[$lang]["email_invalid"]
399 ]);
400 } elseif(!in_array($emailDomain, $config["EmailWhitelist"]) && !empty($config["EmailWhitelist"])) {
401 response([
402 "error" => $localization[$lang]["email_invalid"]
403 ]);
404 }
405
406 $db = new Database($config["Database"]["Host"], $config["Database"]["User"],
407 $config["Database"]["Pass"], $config["Database"]["Name"]);
408 if($db->getEmailCount($email) >= $config["MaxPerEmail"]) {
409 response([
410 "error" => $localization[$lang]["email_invalid"]
411 ]);
412 }
413 $penguinId = $db->addUser($username, $password, $color, $email, ($config["Activate"] ? 1 : 0));
414 if(!$config["Activate"]) {
415 $activationKey = generateActivationKey(60);
416 $db->createActivationKey($penguinId, $activationKey);
417 $activationLink = createActivateUrl($config["External"], $penguinId, $activationKey);
418 $headers = "From: noreply@{$config['Hostname']}\r\n";
419 $headers .= "Reply-To: noreply@{$config['Hostname']}\r\n";
420 $headers .= "Return-Path: noreply@{$config['Hostname']}\r\n";
421 $headers .= "MIME-Version: 1.0\r\n";
422 $headers .= "Content-type: text/html; charset=iso-8859-1\r\n";
423 $headers .= "X-Mailer: PHP/" . phpversion();
424 ob_start();
425?>
426
427<!doctype html>
428<html>
429 <head>
430 <title>Activate your penguin!</title>
431 </head>
432 <body>
433 <p>Hello,</p>
434 <p>Thank you for creating a penguin on <?php print($config["Hostname"]); ?>. Please click below to activate your penguin account.</p>
435 <a href="<?php print($activationLink); ?>">Activate</a>
436 </body>
437</html>
438
439<?php
440 $emailContent = ob_get_clean();
441 mail($email, "Activate your penguin!", $emailContent, $headers);
442 }
443 if($config["Clean"] == true) {
444 $db->cleanInactive($config["CleanDays"]);
445 }
446
447 session_destroy();
448
449 response([
450 "success" => 1
451 ]);
452}
453?>