Created
May 6, 2020 18:29
-
-
Save Zoddo/7da096bf8c9a9c9a888a75be69ca547c to your computer and use it in GitHub Desktop.
SMS api
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
$config = [ | |
'dbhost' => 'localhost', | |
'dbuser' => 'xxxxxxxxx', | |
'dbpass' => 'xxxxxxxxx', | |
'dbname' => 'xxxxxxxxx', | |
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CREATE TABLE sms_logs ( | |
token char(89) NOT NULL, | |
ip varbinary(16) NOT NULL, | |
date timestamp NOT NULL DEFAULT current_timestamp(), | |
destination char(12) NOT NULL, | |
content text NOT NULL | |
) ENGINE=InnoDB DEFAULT CHARSET=utf8; | |
CREATE TABLE sms_tokens ( | |
token char(89) NOT NULL COMMENT 'Use: openssl rand -base64 64 | tr -d ''\\n''', | |
description varchar(100) NOT NULL, | |
default_number varchar(12) DEFAULT '+337xxxxxxxx', | |
allowed_numbers varchar(100) NOT NULL DEFAULT '/^\\+33[67]\\d{8}$/', | |
ratelimit_max tinyint(3) UNSIGNED NOT NULL DEFAULT 3, | |
ratelimit_per smallint(5) UNSIGNED NOT NULL DEFAULT 120 COMMENT 'seconds', | |
quota_hour tinyint(3) UNSIGNED NOT NULL DEFAULT 30, | |
quota_day tinyint(3) UNSIGNED NOT NULL DEFAULT 100 | |
) ENGINE=InnoDB DEFAULT CHARSET=utf8; | |
ALTER TABLE sms_logs | |
ADD KEY date (date), | |
ADD KEY token (token); | |
ALTER TABLE sms_tokens | |
ADD PRIMARY KEY (token); | |
ALTER TABLE sms_logs | |
ADD CONSTRAINT sms_logs_ibfk_1 FOREIGN KEY (token) REFERENCES sms_tokens (token); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
define('DEBUG', !empty($_SERVER['HTTP_X_DEBUG'])); | |
function send_response($json, $code = 200) { | |
header('Content-Type: application/json', true, $code); | |
if (DEBUG) { | |
global $token; | |
if (!empty($token)) { | |
$json['token'] = $token; | |
unset($json['token']['token']); | |
} | |
} | |
echo json_encode($json); | |
} | |
function die_error($code = 500, $error = 'Internal server error', $extra = null) { | |
$res = [ | |
'code' => $code, | |
'error' => $error, | |
]; | |
if (DEBUG && $extra === null) $extra = error_get_last(); | |
if (!empty($extra)) $res['extra'] = $extra; | |
send_response($res, $code); | |
if (!defined('IN_SHUTDOWN')) die; | |
} | |
register_shutdown_function(function() { | |
define('IN_SHUTDOWN', true); | |
$error = error_get_last(); | |
if ($error['type'] === E_ERROR) { | |
die_error(); | |
} | |
}); | |
// BEGIN | |
require('./config.php'); | |
// fuck you PHP for hiding a perfectly valid header | |
$headers = getallheaders(); | |
if (empty($headers['Authorization'])) die_error(401, 'Missing authorization header'); | |
if (strlen($headers['Authorization']) !== 95) die_error(401, 'Invalid authorization header'); | |
if (substr($headers['Authorization'], 0, 7) !== 'Bearer ') die_error(401, 'Invalid authorization header'); | |
// Connect DB only now to prevent useless connections... | |
$db = new PDO("mysql:dbname={$config['dbname']};host={$config['dbhost']};charset=UTF8", $config['dbuser'], $config['dbpass'], [ | |
PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING, | |
]); | |
$req = $db->prepare('SELECT * FROM sms_tokens WHERE token = ?'); | |
$req->execute([substr($headers['Authorization'], 7)]); | |
$token = $req->fetch(PDO::FETCH_ASSOC); | |
if ($token === false) die_error(401, 'Invalid token'); | |
$number = empty($_REQUEST['number']) ? $token['default_number'] : str_replace(' ', '+', (string) $_REQUEST['number']); | |
$text = empty($_REQUEST['text']) ? '' : (string) $_REQUEST['text']; | |
if (empty($number)) die_error(400, 'Missing phone number'); | |
if (empty($text)) die_error(400, 'Missing text'); | |
if (strlen($text) > 1000) die_error(413, 'Text is too long'); | |
if (substr($token['allowed_numbers'], 0, 1) === '/') { | |
if (!preg_match($token['allowed_numbers'], $number)) die_error(403, 'Provided phone number is not allowed'); | |
} else { | |
if ($token['allowed_numbers'] !== $number) die_error(403, 'Provided phone number is not allowed'); | |
} | |
// Check ratelimits | |
$req = $db->prepare('SELECT COUNT(*) FROM sms_logs WHERE token = ? AND date > DATE_SUB(NOW(), INTERVAL ? SECOND)'); | |
$req->execute([$token['token'], $token['ratelimit_per']]); | |
if ($req->fetchColumn() >= $token['ratelimit_max']) die_error(429, 'Ratelimit exceeded'); | |
$req->execute([$token['token'], 3600]); | |
if ($req->fetchColumn() >= $token['quota_hour']) die_error(429, 'Hourly quota exceeded'); | |
$req->execute([$token['token'], 86400]); | |
if ($req->fetchColumn() >= $token['quota_day']) die_error(429, 'Daily quota exceeded'); | |
if (!DEBUG) { | |
$ch = curl_init('http://10.203.1.10/sendsms.php'); | |
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2); | |
curl_setopt($ch, CURLOPT_POST, true); | |
curl_setopt($ch, CURLOPT_POSTFIELDS, [ | |
'number' => $number, | |
'text' => $text, | |
]); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
$ret = curl_exec($ch); | |
$code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); | |
if ($code === 0) die_error(502, 'Could not contact upstream', curl_error($ch)); | |
if (!empty($ret)) $ret = json_decode($ret, true, 5, JSON_THROW_ON_ERROR); | |
if ($code >= 400) die_error($code, 'Upstream returned error', $ret); | |
} else { | |
$code = 200; | |
$ret = ['DEBUG' => true]; | |
} | |
send_response([ | |
'code' => $code, | |
'upstream_result' => $ret, | |
]); | |
$req = $db->prepare('INSERT INTO sms_logs SET token = ?, ip = INET6_ATON(?), destination = ?, content = ?')->execute([$token['token'], $_SERVER['REMOTE_ADDR'], $number, $text]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
$number = empty($_REQUEST['number']) ? '+337xxxxxxxx' : (string) str_replace(' ', '+', $_REQUEST['number']); | |
$text = empty($_REQUEST['text']) ? '' : (string) $_REQUEST['text']; | |
header('Content-Type: application/json'); | |
function die_error($code, $error) { | |
http_response_code($code); | |
die(json_encode([ | |
'code' => $code, | |
'error' => $error, | |
])); | |
} | |
// Sanity checks | |
if (!preg_match('/^\+33[67]\d{8}$/', $number)) die_error(403, 'Provided phone number is not allowed'); | |
if (empty($text)) die_error(400, 'Missing text'); | |
if (strlen($text) > 1000) die_error(400, 'Text is too long'); | |
$number = escapeshellarg($number); | |
$text = escapeshellarg($text); | |
$result = `/usr/bin/gammu-smsd-inject text {$number} -text {$text} -autolen 1000`; | |
echo json_encode([ | |
'code' => 200, | |
'result' => $result, | |
]); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment