· 7 years ago · Apr 08, 2018, 04:16 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.xyz",
23 "External" => "http://cpremastered.xyz",
24 "SecretKey" => "",
25 "Cloudflare" => true,
26 "Activate" => true,
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" => "localhost",
37 "User" => "register",
38 "Pass" => '',
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><?php
430error_reporting(0);
431/*
432Hostname - External hostname
433External - External path to play
434SecretKey - reCaptcha secret key, leave blank to disable
435Cloudflare - Is host behind cloudflare?
436Activate - Activate user by default (true) or send verification email (false)
437Clean - Delete old inactive accounts?
438CleanDays - Number of days before inactive accounts expire
439ForceCase - Force CamelCase on usernames
440AllowedChars - Allowed characters in usernames
441EmailWhitelist - List of allowed email domains, can be array or path to list file, leave blank to disable
442MaxPerEmail - Max no of accounts per email
443Database
444 Host - MySQL host
445 User - MySQL user
446 Pass - MySQL password
447 Name - Database name
448*/
449$config = [
450 "Hostname" => "cpremastered.xyz",
451 "External" => "http://cpremastered.xyz",
452 "SecretKey" => "6LeRqVAUAAAAAKGHMompvLregYTgYUEwSjUh-eVZ",
453 "Cloudflare" => takenUsernames,
454 "Activate" => true,
455 "Clean" => true,
456 "CleanDays" => 10,
457 "ForceCase" => true,
458 "AllowedChars" => "A-Za-z0-9)(*&^$!`\_+={};:@~#>.<",
459 // "EmailWhitelist" => ["gmail.com", "hotmail.com"]
460 // "EmailWhitelist" => "/path/to/whitelist"
461 "EmailWhitelist" => [],
462 "MaxPerEmail" => 5,
463 "Database" => [
464 //"Host" => "http://45.32.199.143/XsCKyP6daTM4Dsvadq7wbt5PGGktHs/X6eQQEp4ySdTJTm2Eds7b8Dz9jSW3J.php",
465 "Host" => "localhost",
466 "User" => "root",
467 "Pass" => 'S7uGF]g}>C)gRX9e/:R&]?4R2;e$tP',
468 "Name" => "houdini"
469 ]
470];
471final class Database extends PDO {
472 private $connection = null;
473 public function __construct($host, $user, $password, $database) {
474 $connectionString = sprintf("mysql:dbname=%s;host=%s", $database, $host);
475 parent::__construct($connectionString, $user, $password);
476 }
477
478 public function encryptPassword($password, $md5 = true) {
479 if($md5 !== false) {
480 $password = md5($password);
481 }
482 $hash = substr($password, 16, 16) . substr($password, 0, 16);
483 return $hash;
484 }
485 public function getLoginHash($password, $staticKey) {
486 $hash = $this->encryptPassword($password, false);
487 $hash .= $staticKey;
488 $hash .= 'Y(02.>\'H}t":E1';
489 $hash = $this->encryptPassword($hash);
490 return $hash;
491 }
492 public function addUser($username, $password, $color, $email, $isActive = 0) {
493 $hashedPassword = strtoupper(md5($password));
494 $staticKey = "houdini";
495 $flashClientHash = $this->getLoginHash($hashedPassword, $staticKey);
496 $bcryptPassword = password_hash($flashClientHash, PASSWORD_DEFAULT, [ "cost" => 12 ]);
497 $insertPenguin = "INSERT INTO `penguin` (`ID`, `Username`, `Nickname`, `Password`, `Email`, `Active`, `Color`) VALUES ";
498 $insertPenguin .= "(NULL, :Username, :Username, :Password, :Email, :Active, :Color);";
499
500 $insertStatement = $this->prepare($insertPenguin);
501 $insertStatement->bindValue(":Username", $username);
502 $insertStatement->bindValue(":Password", $bcryptPassword);
503 $insertStatement->bindValue(":Email", $email);
504 $insertStatement->bindValue(":Active", $isActive);
505 $insertStatement->bindValue(":Color", $color);
506
507 $insertStatement->execute();
508 $insertStatement->closeCursor();
509
510 $penguinId = $this->lastInsertId();
511
512 $this->insertInventory($penguinId, $color);
513 $this->addActiveIgloo($penguinId);
514 $this->sendMail($penguinId, null, 125);
515 return $penguinId;
516 }
517 public function insertInventory($penguinId, $itemId) {
518 $insertInventory = $this->prepare("INSERT INTO `inventory` (`PenguinID`, `ItemID`) VALUES (:PenguinID, :ItemID);");
519 $insertInventory->bindValue(":PenguinID", $penguinId);
520 $insertInventory->bindValue(":ItemID", $itemId);
521 $insertInventory->execute();
522 $insertInventory->closeCursor();
523 }
524
525 public function sendMail($recipientId, $senderId, $postcardType) {
526 $sendMail = $this->prepare("INSERT INTO `postcard` (`ID`, `SenderID`, `RecipientID`, `Type`) VALUES (NULL, :SenderID, :RecipientID, :Type);");
527 $sendMail->bindValue(":RecipientID", $recipientId);
528 $sendMail->bindValue(":SenderID", $senderId);
529 $sendMail->bindValue(":Type", $postcardType);
530 $sendMail->execute();
531 $sendMail->closeCursor();
532 $postcardId = $this->lastInsertId();
533 return $postcardId;
534 }
535 private function addActiveIgloo($penguinId) {
536 $insertStatement = $this->prepare("INSERT INTO `igloo` (`ID`, `PenguinID`) VALUES (NULL, :PenguinID);");
537 $insertStatement->bindValue(":PenguinID", $penguinId);
538 $insertStatement->execute();
539 $insertStatement->closeCursor();
540
541 $iglooId = $this->lastInsertId();
542 return $iglooId;
543 }
544
545 public function usernameTaken($username) {
546 $usernameTaken = "SELECT Username FROM `penguin` WHERE Username = :Username;";
547
548 $takenQuery = $this->prepare($usernameTaken);
549 $takenQuery->bindValue(":Username", $username);
550 $takenQuery->execute();
551
552 $rowCount = $takenQuery->rowCount();
553 $takenQuery->closeCursor();
554
555 return $rowCount > 0;
556 }
557 public function getEmailCount($email) {
558 $emailCount = "SELECT ID FROM `penguin` WHERE Email = :Email;";
559
560 $emailQuery = $this->prepare($emailCount);
561 $emailQuery->bindValue(":Email", $email);
562 $emailQuery->execute();
563
564 $rowCount = $emailQuery->rowCount();
565 $emailQuery->closeCursor();
566
567 return $rowCount;
568 }
569 public function createActivationKey($penguinId, $key) {
570 $insertStatement = $this->prepare("INSERT INTO `activation_key` (`PenguinID`, `ActivationKey`) VALUES (:PenguinID, :Key);");
571 $insertStatement->bindValue(":PenguinID", $penguinId);
572 $insertStatement->bindValue(":Key", $key);
573 $insertStatement->execute();
574 $insertStatement->closeCursor();
575 }
576 public function activateUser($penguinId, $key) {
577 $setActive = $this->prepare("UPDATE `penguin` INNER JOIN activation_key on penguin.ID = activation_key.PenguinID " .
578 "SET penguin.Active = 1 WHERE activation_key.ActivationKey = :Key;");
579 $setActive->bindValue(":Key", $key);
580 $setActive->execute();
581 if($setActive->rowCount() > 0) {
582 $deleteActivation = $this->prepare("DELETE FROM `activation_key` WHERE `PenguinID` = :PenguinID");
583 $deleteActivation->bindValue(":PenguinID", $penguinId);
584 $deleteActivation->execute();
585 }
586 $setActive->closeCursor();
587 $deleteActivation->closeCursor();
588 }
589
590 public function takenUsernames($username) {
591 $usernamesTaken = "SELECT Username FROM `penguin` WHERE Username LIKE :Username;";
592
593 $usernamesQuery = $this->prepare($usernamesTaken);
594 $usernamesQuery->bindValue(":Username", $username . "%");
595 $usernamesQuery->execute();
596
597 $usernames = $usernamesQuery->fetchAll(self::FETCH_COLUMN);
598 return $usernames;
599 }
600 public function cleanInactive($expiry = 10) {
601 $deleteInactive = "DELETE FROM `penguin` WHERE Active = 0 AND RegistrationDate < :Expiry;";
602 $deleteQuery = $this->prepare($deleteInactive);
603 $deleteQuery->bindValue(":Expiry", date("Y-m-d", strtotime("-$expiry days", time())));
604 $deleteQuery->execute();
605 }
606}
607$localization = [
608 "en" => [
609 "terms" => "You must agree to the Rules and Terms of Use.",
610 "name_missing" => "You need to name your penguin.",
611 "name_short" => "Penguin name is too short.",
612 "name_number" => "Penguin names can only contain 5 numbers.",
613 "penguin_letter" => "Penguin names must contain at least 1 letter.",
614 "name_not_allowed" => "That penguin name is not allowed.",
615 "name_taken" => "That penguin name is already taken.",
616 "name_suggest" => "That penguin name is already taken. Try {suggestion}.",
617 "passwords_match" => "Passwords do not match.",
618 "password_short" => "Password is too short.",
619 "email_invalid" => "Invalid email address."
620 ],
621 "fr" => [
622 "terms" => "Tu dois accepter les conditions d'utilisation.",
623 "name_missing" => "Tu dois donner un nom à ton pingouin.",
624 "name_short" => "Le nom de pingouin est trop court.",
625 "name_number" => "Un nom de pingouin ne peut contenir plus de 5 nombres.",
626 "penguin_letter" => "Un nom de pingouin doit contenir au moins une lettre.",
627 "name_not_allowed" => "Ce nom de pingouing n'est pas autorisé.",
628 "name_taken" => "Ce nom de pingouin est pris.",
629 "name_suggest" => "Ce nom de pingouin est pris. Essaye {suggestion}.",
630 "passwords_match" => "Les mots de passes ne correspondent pas.",
631 "password_short" => "Le mot de passe est trop court.",
632 "email_invalid" => "Adresse email invalide."
633 ],
634 "es" => [
635 "terms" => "Debes seguir las reglas y los términos de uso.",
636 "name_missing" => "Debes escoger un nombre para tu pingüino.",
637 "name_short" => "El nombre de tu pingüino es muy corto.",
638 "name_number" => "Los nombres de usuario sólo pueden tener 5 números.",
639 "penguin_letter" => "Los nombres de usuario deben tener por lo menos 1 letra.",
640 "name_not_allowed" => "Ese nombre de usuario no está permitido.",
641 "name_taken" => "Ese nombre de usuario ya ha sido escogido.",
642 "name_suggest" => "Ese nombre de usuario ya ha sido escogido. Intenta éste {suggestion}.",
643 "passwords_match" => "Las contraseñas no coinciden.",
644 "password_short" => "La contraseña es muy corta.",
645 "email_invalid" => "El correo eléctronico es incorrecto."
646 ],
647 "pt" => [
648 "terms" => "Você precisa concordar com as Regras e com os Termos de Uso.",
649 "name_missing" => "Voce precisa nomear seu pinguim.",
650 "name_short" => "O nome do pinguim é muito curto.",
651 "name_number" => "O nome do pinguim só pode conter 5 números",
652 "penguin_letter" => "O nome do seu pinguim tem de conter pelomenos uma letra.",
653 "name_not_allowed" => "Esse nome de pinguim não é permitido.",
654 "name_taken" => "Esse nome de pinguim já foi escolhido.",
655 "name_suggest" => "Esse nome de pinguim já foi escolhido. Tente {suggestion}.",
656 "passwords_match" => "As senhas não estão iguais.",
657 "password_short" => "A senha é muito curta.",
658 "email_invalid" => "Esse endereço de E-Mail é invalido."
659 ]
660];
661if(!is_array($config["EmailWhitelist"]) && !empty($config["EmailWhitelist"])) {
662 $emailWhitelistFile = file_get_contents($config["EmailWhitelist"]);
663 $config["EmailWhitelist"] = explode("\n", $emailWhitelistFile);
664}
665if(isset($_GET["key"])) {
666 $db = new Database($config["Database"]["Host"], $config["Database"]["User"],
667 $config["Database"]["Pass"], $config["Database"]["Name"]);
668 $key = $_GET["key"];
669 $rawKey = base64_decode($key);
670 $rawKey = explode(":", $rawKey);
671 list($penguinId, $activationKey) = $rawKey;
672
673 $db->activateUser($penguinId, $activationKey);
674 header("Location: " . $config["External"]);
675 die($penguinId . $activationKey);
676}
677session_start();
678function response($data) {
679 die(http_build_query($data));
680}
681function attemptDataRetrieval($key, $session = false) {
682 if(!$session && array_key_exists($key, $_POST)) {
683 return $_POST[$key];
684 }
685 if($session && array_key_exists($key, $_SESSION)) {
686 return $_SESSION[$key];
687 }
688 response([
689 "error" => ""
690 ]);
691}
692function generateActivationKey($length, $keyspace = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") {
693 $str = "";
694 $max = mb_strlen($keyspace, "8bit") - 1;
695 for ($i = 0; $i < $length; ++$i) {
696 $str .= $keyspace[random_int(0, $max)];
697 }
698 return $str;
699}
700function createActivateUrl($baseUrl, $penguinId, $activationKey) {
701 $rawKey = implode(":", [$penguinId, $activationKey]);
702 $key = base64_encode($rawKey);
703 return $baseUrl . "/create_account/create_account.php?key=" . $key;
704}
705$action = attemptDataRetrieval("action");
706$lang = attemptDataRetrieval("lang");
707if(!in_array($lang, array_keys($localization))) {
708 response([
709 "error" => ""
710 ]);
711}
712if($action == "validate_agreement") {
713 $agreeTerms = attemptDataRetrieval("agree_to_terms");
714 $agreeRules = attemptDataRetrieval("agree_to_rules");
715 if(!$agreeTerms || !$agreeRules) {
716 response([
717 "error" => $localization[$lang]["terms"]
718 ]);
719 }
720
721 response([
722 "success" => 1
723 ]);
724} elseif($action == "validate_username") {
725 $username = attemptDataRetrieval("username");
726 $color = attemptDataRetrieval("colour");
727 $colors = range(1, 15);
728
729 if(strlen($username) == 0) {
730 response([
731 "error" => $localization[$lang]["name_missing"]
732 ]);
733 } elseif(strlen($username) < 4 || strlen($username) > 12) {
734 response([
735 "error" => $localization[$lang]["name_short"]
736 ]);
737 } elseif(preg_match_all("/[0-9]/", $username) > 5) {
738 response([
739 "error" => $localization[$lang]["name_number"]
740 ]);
741 } elseif(!preg_match("/[A-z]/i", $username)) {
742 response([
743 "error" => $localization[$lang]["penguin_letter"]
744 ]);
745 } elseif(preg_match("/[^" . $config["AllowedChars"] . "]/", $username)) {
746 response([
747 "error" => $localization[$lang]["name_not_allowed"]
748 ]);
749 } elseif(!is_numeric($color) || !in_array($color, $colors)) {
750 response([
751 "error" => ""
752 ]);
753 }
754
755 $db = new Database($config["Database"]["Host"], $config["Database"]["User"],
756 $config["Database"]["Pass"], $config["Database"]["Name"]);
757 if($db->usernameTaken($username)) {
758 $username = preg_replace("/\d+$/", "", $username);
759 $takenUsernames = $db->takenUsernames($username);
760 $i = 1;
761 while(true) {
762 $suggestion = $username . $i++;
763 if(preg_match_all("/[0-9]/", $username) > 1) {
764 response([
765 "error" => $localization[$lang]["name_taken"]
766 ]);
767 }
768 if(!in_array(strtolower($suggestion), $takenUsernames)) {
769 break;
770 }
771 }
772 response([
773 "error" => str_replace("{suggestion}", $suggestion, $localization[$lang]["name_suggest"])
774 ]);
775 }
776
777 $_SESSION["sid"] = session_id();
778 $_SESSION["username"] = ($config["ForceCase"] ? ucfirst(strtolower($username)) : $username);
779 $_SESSION["colour"] = $color;
780
781 response([
782 "success" => 1,
783 "sid" => session_id()
784 ]);
785} elseif($action == "validate_password_email") {
786 $sessionId = attemptDataRetrieval("sid", true);
787 $username = attemptDataRetrieval("username", true);
788 $color = attemptDataRetrieval("colour", true);
789 $password = attemptDataRetrieval("password");
790 $passwordConfirm = attemptDataRetrieval("password_confirm");
791 $email = attemptDataRetrieval("email");
792 $gtoken = attemptDataRetrieval("gtoken");
793 if(!empty($config["SecretKey"])) {
794 $data = [
795 "secret" => $config["SecretKey"],
796 "response" => $gtoken,
797 "remoteip" => ($config["Cloudflare"] ? $_SERVER["HTTP_CF_CONNECTING_IP"] : $_SERVER['REMOTE_ADDR'])
798 ];
799 $verify = curl_init();
800 curl_setopt($verify, CURLOPT_URL, "https://www.google.com/recaptcha/api/siteverify");
801 curl_setopt($verify, CURLOPT_POST, true);
802 curl_setopt($verify, CURLOPT_POSTFIELDS, http_build_query($data));
803 curl_setopt($verify, CURLOPT_SSL_VERIFYPEER, false);
804 curl_setopt($verify, CURLOPT_RETURNTRANSFER, true);
805 $response = curl_exec($verify);
806 $result = json_decode($response);
807 }
808 $emailDomain = substr(strrchr($email, "@"), 1);
809 if($sessionId !== session_id()) {
810 response([
811 "error" => ""
812 ]);
813 } elseif(empty($result->success) && !empty($config["SecretKey"])) {
814 response([
815 "error" => ""
816 ]);
817 } elseif($password !== $passwordConfirm) {
818 response([
819 "error" => $localization[$lang]["passwords_match"]
820 ]);
821 } elseif(strlen($password) < 4) {
822 response([
823 "error" => $localization[$lang]["password_short"]
824 ]);
825 } elseif(!filter_var($email, FILTER_VALIDATE_EMAIL)) {
826 response([
827 "error" => $localization[$lang]["email_invalid"]
828 ]);
829 } elseif(!in_array($emailDomain, $config["EmailWhitelist"]) && !empty($config["EmailWhitelist"])) {
830 response([
831 "error" => $localization[$lang]["email_invalid"]
832 ]);
833 }
834
835 $db = new Database($config["Database"]["Host"], $config["Database"]["User"],
836 $config["Database"]["Pass"], $config["Database"]["Name"]);
837 if($db->getEmailCount($email) >= $config["MaxPerEmail"]) {
838 response([
839 "error" => $localization[$lang]["email_invalid"]
840 ]);
841 }
842 $penguinId = $db->addUser($username, $password, $color, $email, ($config["Activate"] ? 1 : 0));
843 if(!$config["Activate"]) {
844 $activationKey = generateActivationKey(60);
845 $db->createActivationKey($penguinId, $activationKey);
846 $activationLink = createActivateUrl($config["External"], $penguinId, $activationKey);
847 $headers = "From: noreply@{$config['Hostname']}\r\n";
848 $headers .= "Reply-To: noreply@{$config['Hostname']}\r\n";
849 $headers .= "Return-Path: noreply@{$config['Hostname']}\r\n";
850 $headers .= "MIME-Version: 1.0\r\n";
851 $headers .= "Content-type: text/html; charset=iso-8859-1\r\n";
852 $headers .= "X-Mailer: PHP/" . phpversion();
853 ob_start();
854?>
855
856<!doctype html>
857<html>
858 <head>
859 <title>Activate your penguin!</title>
860 </head>
861 <body>
862 <p>Hello,</p>
863 <p>Thank you for creating a penguin on <?php print($config["Hostname"]); ?>. Please click below to activate your penguin account.</p>
864 <a href="<?php print($activationLink); ?>">Activate</a>
865 </body>
866</html>
867
868<?php
869 $emailContent = ob_get_clean();
870 mail($email, "Activate your penguin!", $emailContent, $headers);
871 }
872 if($config["Clean"] == true) {
873 $db->cleanInactive($config["CleanDays"]);
874 }
875
876 session_destroy();
877
878 response([
879 "success" => 1
880 ]);
881}
882?>
883
884 <title>Activate your penguin!</title>
885 </head>
886 <body>
887 <p>Hello,</p>
888 <p>Thank you for creating a penguin on <?php print($config["Hostname"]); ?>. Please click below to activate your penguin account.</p>
889 <a href="<?php print($activationLink); ?>">Activate</a>
890 </body>
891</html>
892
893<?php
894 $emailContent = ob_get_clean();
895 mail($email, "Activate your penguin!", $emailContent, $headers);
896 }
897 if($config["Clean"] == true) {
898 $db->cleanInactive($config["CleanDays"]);
899 }
900
901 session_destroy();
902
903 response([
904 "success" => 1
905 ]);
906}
907?>