From 70538aa509c867713868f092d3974b98c8a532d1 Mon Sep 17 00:00:00 2001 From: Skylar Ittner Date: Sat, 17 Jun 2017 00:33:28 -0600 Subject: [PATCH] Fix #1 (Add mobile session creation API), add better IP detection --- lib/authlog.php | 20 +------- lib/iputils.php | 127 +++++++++++++++++++++++++++++++++++++++++++++++ mobile/index.php | 76 ++++++++++++++++++++++++++++ 3 files changed, 205 insertions(+), 18 deletions(-) create mode 100644 lib/iputils.php create mode 100644 mobile/index.php diff --git a/lib/authlog.php b/lib/authlog.php index 597100e..2923b92 100644 --- a/lib/authlog.php +++ b/lib/authlog.php @@ -1,29 +1,13 @@ insert("authlog", ['logtime' => date("Y-m-d H:i:s"), 'logtype' => $type, 'uid' => $uid, 'ip' => $ip, 'otherdata' => $data]); } \ No newline at end of file diff --git a/lib/iputils.php b/lib/iputils.php new file mode 100644 index 0000000..f46c89f --- /dev/null +++ b/lib/iputils.php @@ -0,0 +1,127 @@ + + */ +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 +} diff --git a/mobile/index.php b/mobile/index.php new file mode 100644 index 0000000..742ae4b --- /dev/null +++ b/mobile/index.php @@ -0,0 +1,76 @@ + "OK"])); +} + +function mobile_enabled() { + $client = new GuzzleHttp\Client(); + + $response = $client + ->request('POST', PORTAL_API, [ + 'form_params' => [ + 'key' => PORTAL_KEY, + 'action' => "mobileenabled" + ] + ]); + + if ($response->getStatusCode() > 299) { + return false; + } + + $resp = json_decode($response->getBody(), TRUE); + if ($resp['status'] == "OK" && $resp['mobile'] === TRUE) { + return true; + } else { + return false; + } +} + +if (mobile_enabled() !== TRUE) { + exit(json_encode(["status" => "ERROR", "msg" => lang("mobile login disabled", false)])); +} + +// Make sure we have a username and access key +if (is_empty($VARS['username']) || is_empty($VARS['key'])) { + http_response_code(401); + die(json_encode(["status" => "ERROR", "msg" => "Missing username and/or access key."])); +} + +// Make sure the username and key are actually legit +$user_key_valid = $database->has('mobile_codes', ['[>]accounts' => ['uid' => 'uid']], ["AND" => ['mobile_codes.code' => $VARS['key'], 'accounts.username' => $VARS['username']]]); +if ($user_key_valid !== TRUE) { + engageRateLimit(); + http_response_code(401); + insertAuthLog(21, null, "Username: " . $VARS['username'] . ", Key: " . $VARS['key']); + die(json_encode(["status" => "ERROR", "msg" => "Invalid username and/or access key."])); +} + +// Process the action +switch ($VARS['action']) { + case "start_session": + // Do a web login. + if (user_exists($VARS['username'])) { + if (get_account_status($VARS['username']) == "NORMAL") { + if (authenticate_user($VARS['username'], $VARS['password'], $autherror)) { + doLoginUser($VARS['username'], $VARS['password']); + exit(json_encode(["status" => "OK"])); + } + } + } + exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)])); + default: + http_response_code(404); + die(json_encode(["status" => "ERROR", "msg" => "The requested action is not available."])); +} \ No newline at end of file