Skip to content

Instantly share code, notes, and snippets.

@Zoddo
Created May 6, 2020 18:29
Show Gist options
  • Save Zoddo/7da096bf8c9a9c9a888a75be69ca547c to your computer and use it in GitHub Desktop.
Save Zoddo/7da096bf8c9a9c9a888a75be69ca547c to your computer and use it in GitHub Desktop.
SMS api
<?php
$config = [
'dbhost' => 'localhost',
'dbuser' => 'xxxxxxxxx',
'dbpass' => 'xxxxxxxxx',
'dbname' => 'xxxxxxxxx',
];
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);
<?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]);
<?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