From 7cb7aa8f866da29ac972455ba98aa8dbadf67402 Mon Sep 17 00:00:00 2001 From: Skylar Ittner Date: Sat, 24 Mar 2018 01:41:23 -0600 Subject: [PATCH] Finish cloud sync --- cloud/composer.json | 14 ++ cloud/required.php | 310 ++++++++++++++++++++++++++++++++++++ cloud/settings.template.php | 16 ++ cloud/sync.php | 11 ++ www/pages/home.php | 2 +- www/settings.template.php | 4 + www/sync.php | 44 +++++ 7 files changed, 400 insertions(+), 1 deletion(-) create mode 100644 cloud/composer.json create mode 100644 cloud/required.php create mode 100644 cloud/settings.template.php create mode 100644 cloud/sync.php create mode 100644 www/sync.php diff --git a/cloud/composer.json b/cloud/composer.json new file mode 100644 index 0000000..652a064 --- /dev/null +++ b/cloud/composer.json @@ -0,0 +1,14 @@ +{ + "name": "netsyms/business-accelerator-sync-server", + "description": "Cloud component for the Business Accelerator", + "require": { + "catfan/medoo": "^1.5" + }, + "license": "MPL-2.0", + "authors": [ + { + "name": "Skylar Ittner", + "email": "admin@netsyms.com" + } + ] +} diff --git a/cloud/required.php b/cloud/required.php new file mode 100644 index 0000000..45d7884 --- /dev/null +++ b/cloud/required.php @@ -0,0 +1,310 @@ + DB_TYPE, + 'database_name' => DB_NAME, + 'server' => DB_SERVER, + 'username' => DB_USER, + 'password' => DB_PASS, + 'charset' => DB_CHARSET + ]); +} catch (Exception $ex) { + header('HTTP/1.1 500 Internal Server Error'); + sendError("Database error. $ex"); +} + +/** + * Checks if a string or whatever is empty. + * @param $str The thingy to check + * @return boolean True if it's empty or whatever. + */ +function is_empty($str) { + return (is_null($str) || !isset($str) || $str == ''); +} + +/** + * Securely verify a password and its hash + * @param String $password + * @param String $hash the hash to compare to + * @return boolean True if password OK, else false + */ +function comparePassword($password, $hash) { + return password_verify($password, $hash); +} + +function dieifnotloggedin() { + if ($_SESSION['loggedin'] != true) { + sendError("Session expired. Please log out and log in again."); + } +} + +/** + * Check if the previous database action had a problem. + * @param array $specials int=>string array with special response messages for SQL errors + */ +function checkDBError($specials = []) { + global $database; + $errors = $database->error(); + if (!is_null($errors[1])) { + foreach ($specials as $code => $text) { + if ($errors[1] == $code) { + sendError($text); + } + } + sendError("A database error occurred:
" . $errors[2] . ""); + } +} + +/* + * http://stackoverflow.com/a/20075147 + */ +if (!function_exists('base_url')) { + + function base_url($atRoot = FALSE, $atCore = FALSE, $parse = FALSE) { + if (isset($_SERVER['HTTP_HOST'])) { + $http = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off' ? 'https' : 'http'; + $hostname = $_SERVER['HTTP_HOST']; + $dir = str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']); + + $core = preg_split('@/@', str_replace($_SERVER['DOCUMENT_ROOT'], '', realpath(dirname(__FILE__))), NULL, PREG_SPLIT_NO_EMPTY); + $core = $core[0]; + + $tmplt = $atRoot ? ($atCore ? "%s://%s/%s/" : "%s://%s/") : ($atCore ? "%s://%s/%s/" : "%s://%s%s"); + $end = $atRoot ? ($atCore ? $core : $hostname) : ($atCore ? $core : $dir); + $base_url = sprintf($tmplt, $http, $hostname, $end); + } else + $base_url = 'http://localhost/'; + + if ($parse) { + $base_url = parse_url($base_url); + if (isset($base_url['path'])) + if ($base_url['path'] == '/') + $base_url['path'] = ''; + } + + return $base_url; + } + +} + +/** + * Check if a given ipv4 address is in a given cidr + * @param string $ip IP to check in IPV4 format eg. 127.0.0.1 + * @param string $range IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed + * @return boolean true if the ip is in this range / false if not. + * @author Thorsten Ott + */ +function ip4_in_cidr($ip, $cidr) { + if (strpos($cidr, '/') == false) { + $cidr .= '/32'; + } + // $range is in IP/CIDR format eg 127.0.0.1/24 + list( $cidr, $netmask ) = explode('/', $cidr, 2); + $range_decimal = ip2long($cidr); + $ip_decimal = ip2long($ip); + $wildcard_decimal = pow(2, ( 32 - $netmask)) - 1; + $netmask_decimal = ~ $wildcard_decimal; + return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) ); +} + +/** + * Check if a given ipv6 address is in a given cidr + * @param string $ip IP to check in IPV6 format + * @param string $cidr CIDR netmask + * @return boolean true if the IP is in this range, false otherwise. + * @author MW. + */ +function ip6_in_cidr($ip, $cidr) { + $address = inet_pton($ip); + $subnetAddress = inet_pton(explode("/", $cidr)[0]); + $subnetMask = explode("/", $cidr)[1]; + + $addr = str_repeat("f", $subnetMask / 4); + switch ($subnetMask % 4) { + case 0: + break; + case 1: + $addr .= "8"; + break; + case 2: + $addr .= "c"; + break; + case 3: + $addr .= "e"; + break; + } + $addr = str_pad($addr, 32, '0'); + $addr = pack("H*", $addr); + + $binMask = $addr; + return ($address & $binMask) == $subnetAddress; +} + +/** + * Check if the REMOTE_ADDR is on Cloudflare's network. + * @return boolean true if it is, otherwise false + */ +function validateCloudflare() { + if (filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + // Using IPv6 + $cloudflare_ips_v6 = [ + "2400:cb00::/32", + "2405:8100::/32", + "2405:b500::/32", + "2606:4700::/32", + "2803:f800::/32", + "2c0f:f248::/32", + "2a06:98c0::/29" + ]; + $valid = false; + foreach ($cloudflare_ips_v6 as $cidr) { + if (ip6_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) { + $valid = true; + break; + } + } + } else { + // Using IPv4 + $cloudflare_ips_v4 = [ + "103.21.244.0/22", + "103.22.200.0/22", + "103.31.4.0/22", + "104.16.0.0/12", + "108.162.192.0/18", + "131.0.72.0/22", + "141.101.64.0/18", + "162.158.0.0/15", + "172.64.0.0/13", + "173.245.48.0/20", + "188.114.96.0/20", + "190.93.240.0/20", + "197.234.240.0/22", + "198.41.128.0/17" + ]; + $valid = false; + foreach ($cloudflare_ips_v4 as $cidr) { + if (ip4_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) { + $valid = true; + break; + } + } + } + return $valid; +} + +/** + * Makes a good guess at the client's real IP address. + * + * @return string Client IP or `0.0.0.0` if we can't find anything + */ +function getClientIP() { + // If CloudFlare is in the mix, we should use it. + // Check if the request is actually from CloudFlare before trusting it. + if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) { + if (validateCloudflare()) { + return $_SERVER["HTTP_CF_CONNECTING_IP"]; + } + } + + if (isset($_SERVER["REMOTE_ADDR"])) { + return $_SERVER["REMOTE_ADDR"]; + } + + return "0.0.0.0"; // This will not happen unless we aren't a web server +} + +/** + * Check if the client's IP has been doing too many brute-force-friendly + * requests lately. + * Kills the script with a "friendly" error and response code 429 + * (Too Many Requests) if the last access time in the DB is too near. + * + * Also updates the rate_limit table with the latest data and purges old rows. + * @global type $database + */ +function engageRateLimit() { + global $database; + $delay = date("Y-m-d H:i:s", strtotime("-2 seconds")); + $database->delete('rate_limit', ["lastaction[<]" => $delay]); + if ($database->has('rate_limit', ["AND" => ["ipaddr" => getClientIP()]])) { + http_response_code(429); + // JSONify it so API clients don't scream too loud + die(json_encode(["status" => "ERROR", "msg" => "You're going too fast. Slow down, mkay?"])); + } else { + // Add a record for the IP address + $database->insert('rate_limit', ["ipaddr" => getClientIP(), "lastaction" => date("Y-m-d H:i:s")]); + } +} + diff --git a/cloud/settings.template.php b/cloud/settings.template.php new file mode 100644 index 0000000..27b47fe --- /dev/null +++ b/cloud/settings.template.php @@ -0,0 +1,16 @@ + ["123"]]); diff --git a/cloud/sync.php b/cloud/sync.php new file mode 100644 index 0000000..db86465 --- /dev/null +++ b/cloud/sync.php @@ -0,0 +1,11 @@ +select("accounts", ["uid", "username", "password", "authsecret"], ["password[!]" => null]); + exit(json_encode($accounts)); + default: + die("\"action invalid or not found\""); +} diff --git a/www/pages/home.php b/www/pages/home.php index 67fda13..1b33cd9 100644 --- a/www/pages/home.php +++ b/www/pages/home.php @@ -65,7 +65,7 @@ require_once __DIR__ . '/../required.php';

diff --git a/www/settings.template.php b/www/settings.template.php index f2e111c..110ea7c 100644 --- a/www/settings.template.php +++ b/www/settings.template.php @@ -14,5 +14,9 @@ define("DEBUG", false); define("DB_FILE", "/var/sqlitedb/accounts.db"); define("DB_CHARSET", "utf8"); +define("CLOUD_URL", "https://sync.netsyms.biz/sync.php"); +define("CLOUD_ID", "test"); +define("CLOUD_KEY", "123"); + define("FOOTER_TEXT", ""); define("COPYRIGHT_NAME", "Netsyms Technologies"); diff --git a/www/sync.php b/www/sync.php new file mode 100644 index 0000000..db8a6b0 --- /dev/null +++ b/www/sync.php @@ -0,0 +1,44 @@ +request('POST', CLOUD_URL, [ + 'form_params' => [ + 'id' => CLOUD_ID, + 'key' => CLOUD_KEY, + 'action' => "getaccounts" + ] + ]); + if ($response->getStatusCode() != 200) { + if ($VARS['web'] == "1") { + header("Location: ./index.php?page=home&err=remotefail"); + } + echo "Error: Remote server error. Try again later."; + die(); + } + $resp = json_decode($response->getBody(), TRUE); + $database->delete('accounts', []); + foreach ($resp as $p) { + $database->insert('accounts', ["uid" => $p['uid'], "username" => $p['username'], "password" => $p['password'], "authsecret" => $p['authsecret']]); + } + if ($database->has('config', ['key' => 'lastsync'])) { + $database->update('config', ['value' => time()], ['key' => 'lastsync']); + } else { + $database->insert('config', ['key' => 'lastsync', 'value' => time()]); + } + if ($VARS['web'] == "1") { + header("Location: ./index.php?page=home&msg=syncok"); + } + echo "Sync complete."; + die(); +} catch (Exception $e) { + if ($VARS['web'] == "1") { + header("Location: ./index.php?page=home&err=fail"); + } + echo "Error: " . $e->getMessage(); + die(); +}