-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchecker_map.php
More file actions
116 lines (105 loc) · 4.24 KB
/
Copy pathchecker_map.php
File metadata and controls
116 lines (105 loc) · 4.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#!/usr/bin/env php
<?php
/**
* checker_map.php — Apache mod_rewrite RewriteMap external ("prg:") program
* for ip-block.com. LuaSec-free alternative to ip_block.lua.
*
* Protocol (Apache RewriteMap prg): Apache starts this program ONCE and keeps it
* running. It writes one lookup key per line to our stdin; we must write exactly
* one line back per key to stdout, unbuffered. The value we return is used by a
* RewriteCond in httpd (see ip-block.conf).
*
* Lookup key format we expect (built in httpd.conf):
* IP|USER_AGENT|REFERER
*
* We return:
* BLOCK -> the RewriteCond matches and the request is blocked
* OK -> allowed
*
* We call POST https://api.ip-block.com/v1/check (api_key in JSON body), cache
* per-IP decisions in memory (this process is long-lived), honour a whitelist,
* and FAIL OPEN (return OK) on any error/timeout unless IPB_FAIL_OPEN=0.
*
* Config via environment variables (export to Apache): IPB_SITE_ID, IPB_API_KEY,
* IPB_API_URL, IPB_FAIL_OPEN, IPB_CACHE_TTL, IPB_TIMEOUT_MS, IPB_WHITELIST.
*/
declare(strict_types=1);
$siteId = (string)(getenv('IPB_SITE_ID') ?: '');
$apiKey = (string)(getenv('IPB_API_KEY') ?: '');
$apiUrl = (string)(getenv('IPB_API_URL') ?: 'https://api.ip-block.com/v1/check');
$failOpen = (getenv('IPB_FAIL_OPEN') ?: '1') === '1';
$cacheTtl = (int)(getenv('IPB_CACHE_TTL') ?: '300');
$timeoutMs = (int)(getenv('IPB_TIMEOUT_MS') ?: '1000');
$whitelist = array_filter(array_map('trim', explode(',', (string)(getenv('IPB_WHITELIST') ?: ''))));
// In-memory per-IP decision cache: ip => [expiry_epoch, "OK"|"BLOCK"].
$cache = [];
// Unbuffered I/O is mandatory for RewriteMap prg programs.
$in = fopen('php://stdin', 'r');
$out = fopen('php://stdout', 'w');
stream_set_blocking($in, true);
/** Query the API. Returns "BLOCK" | "OK" | null (null => error, apply fail-open). */
function query_api(string $apiUrl, string $apiKey, string $siteId, string $ip, string $ua, string $ref, int $timeoutMs): ?string {
$payload = json_encode([
'api_key' => $apiKey,
'site_id' => $siteId,
'ip' => $ip,
'user_agent' => $ua,
'referrer' => $ref,
], JSON_UNESCAPED_SLASHES);
$ch = curl_init($apiUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT_MS => $timeoutMs,
CURLOPT_CONNECTTIMEOUT_MS => $timeoutMs,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
$body = curl_exec($ch);
$status = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$errno = curl_errno($ch);
curl_close($ch);
if ($errno !== 0 || $status < 200 || $status >= 300 || $body === false) {
return null;
}
$data = json_decode((string)$body, true);
if (!is_array($data) || !array_key_exists('action', $data)) {
return null;
}
return $data['action'] === 'block' ? 'BLOCK' : 'OK';
}
// Main loop: one line in, one line out.
while (($line = fgets($in)) !== false) {
$line = rtrim($line, "\r\n");
// Split composite key: IP|UA|Referer (UA/Referer may themselves contain
// nothing dangerous here since we only use them as data).
$parts = explode('|', $line, 3);
$ip = $parts[0] ?? '';
$ua = $parts[1] ?? '';
$ref = $parts[2] ?? '';
$decision = 'OK';
if ($ip === '') {
$decision = $failOpen ? 'OK' : 'BLOCK';
} elseif (in_array($ip, $whitelist, true)) {
$decision = 'OK'; // whitelisted, never checked
} else {
$now = time();
if ($cacheTtl > 0 && isset($cache[$ip]) && $cache[$ip][0] > $now) {
$decision = $cache[$ip][1];
} else {
$result = query_api($apiUrl, $apiKey, $siteId, $ip, $ua, $ref, $timeoutMs);
if ($result === null) {
$decision = $failOpen ? 'OK' : 'BLOCK'; // do not cache errors
} else {
$decision = $result;
if ($cacheTtl > 0) {
$cache[$ip] = [$now + $cacheTtl, $decision];
}
}
}
}
fwrite($out, $decision . "\n");
fflush($out);
}