OSDN Git Service

27094f43b85afc79a7073c63adf265e11f0e1397
[pukiwiki/pukiwiki_sandbox.git] / spam / spam.php
1 <?php
2 // $Id: spam.php,v 1.196 2007/07/02 14:51:40 henoheno Exp $
3 // Copyright (C) 2006-2007 PukiWiki Developers Team
4 // License: GPL v2 or (at your option) any later version
5 //
6 // Functions for Concept-work of spam-uri metrics
7 //
8 // (PHP 4 >= 4.3.0): preg_match_all(PREG_OFFSET_CAPTURE): $method['uri_XXX'] related feature
9
10 require_once('spam_pickup.php');
11
12 if (! defined('SPAM_INI_FILE'))   define('SPAM_INI_FILE',   'spam.ini.php');
13 if (! defined('DOMAIN_INI_FILE')) define('DOMAIN_INI_FILE', 'domain.ini.php');
14
15 // ---------------------
16 // Compat etc
17
18 // (PHP 4 >= 4.2.0): var_export(): mail-reporting and dump related
19 if (! function_exists('var_export')) {
20         function var_export() {
21                 return 'var_export() is not found on this server' . "\n";
22         }
23 }
24
25 // (PHP 4 >= 4.2.0): preg_grep() enables invert option
26 function preg_grep_invert($pattern = '//', $input = array())
27 {
28         static $invert;
29         if (! isset($invert)) $invert = defined('PREG_GREP_INVERT');
30
31         if ($invert) {
32                 return preg_grep($pattern, $input, PREG_GREP_INVERT);
33         } else {
34                 $result = preg_grep($pattern, $input);
35                 if ($result) {
36                         return array_diff($input, preg_grep($pattern, $input));
37                 } else {
38                         return $input;
39                 }
40         }
41 }
42
43
44 // ---------------------
45 // Utilities
46
47 // Very roughly, shrink the lines of var_export()
48 // NOTE: If the same data exists, it must be corrupted.
49 function var_export_shrink($expression, $return = FALSE, $ignore_numeric_keys = FALSE)
50 {
51         $result = var_export($expression, TRUE);
52
53         $result = preg_replace(
54                 // Remove a newline and spaces
55                 '# => \n *array \(#', ' => array (',
56                 $result
57         );
58
59         if ($ignore_numeric_keys) {
60                 $result =preg_replace(
61                         // Remove numeric keys
62                         '#^( *)[0-9]+ => #m', '$1',
63                         $result
64                 );
65         }
66
67         if ($return) {
68                 return $result;
69         } else {
70                 echo   $result;
71                 return NULL;
72         }
73 }
74
75 // Reverse $string with specified delimiter
76 function delimiter_reverse($string = 'foo.bar.example.com', $from_delim = '.', $to_delim = '.')
77 {
78         if (! is_string($string) || ! is_string($from_delim) || ! is_string($to_delim))
79                 return $string;
80
81         // com.example.bar.foo
82         return implode($to_delim, array_reverse(explode($from_delim, $string)));
83 }
84
85 // ksort() by domain
86 function ksort_by_domain(& $array)
87 {
88         $sort = array();
89         foreach(array_keys($array) as $key) {
90                 $sort[delimiter_reverse($key)] = $key;
91         }
92         ksort($sort, SORT_STRING);
93         $result = array();
94         foreach($sort as $key) {
95                 $result[$key] = & $array[$key];
96         }
97         $array = $result;
98 }
99
100 // Roughly strings(1) using PCRE
101 // This function is useful to:
102 //   * Reduce the size of data, from removing unprintable binary data
103 //   * Detect _bare_strings_ from binary data
104 // References:
105 //   http://www.freebsd.org/cgi/man.cgi?query=strings (Man-page of GNU strings)
106 //   http://www.pcre.org/pcre.txt
107 // Note: mb_ereg_replace() is one of mbstring extension's functions
108 //   and need to init its encoding.
109 function strings($binary = '', $min_len = 4, $ignore_space = FALSE, $multibyte = FALSE)
110 {
111         // String only
112         $binary = (is_array($binary) || $binary === TRUE) ? '' : strval($binary);
113
114         $regex = $ignore_space ?
115                 '[^[:graph:] \t\n]+' :          // Remove "\0" etc, and readable spaces
116                 '[^[:graph:][:space:]]+';       // Preserve readable spaces if possible
117
118         $binary = $multibyte ?
119                 mb_ereg_replace($regex,           "\n",  $binary) :
120                 preg_replace('/' . $regex . '/s', "\n",  $binary);
121
122         if ($ignore_space) {
123                 $binary = preg_replace(
124                         array(
125                                 '/[ \t]{2,}/',
126                                 '/^[ \t]/m',
127                                 '/[ \t]$/m',
128                         ),
129                         array(
130                                 ' ',
131                                 '',
132                                 ''
133                         ),
134                          $binary);
135         }
136
137         if ($min_len > 1) {
138                 // The last character seems "\n" or not
139                 $br = (! empty($binary) && $binary[strlen($binary) - 1] == "\n") ? "\n" : '';
140
141                 $min_len = min(1024, intval($min_len));
142                 $regex = '/^.{' . $min_len . ',}/S';
143                 $binary = implode("\n", preg_grep($regex, explode("\n", $binary))) . $br;
144         }
145
146         return $binary;
147 }
148
149
150 // ---------------------
151 // Utilities: Arrays
152
153 // Count leaves (A leaf = value that is not an array, or an empty array)
154 function array_count_leaves($array = array(), $count_empty = FALSE)
155 {
156         if (! is_array($array) || (empty($array) && $count_empty)) return 1;
157
158         // Recurse
159         $count = 0;
160         foreach ($array as $part) {
161                 $count += array_count_leaves($part, $count_empty);
162         }
163         return $count;
164 }
165
166 // An array-leaves to a flat array
167 function array_flat_leaves($array, $unique = TRUE)
168 {
169         if (! is_array($array)) return $array;
170
171         $tmp = array();
172         foreach(array_keys($array) as $key) {
173                 if (is_array($array[$key])) {
174                         // Recurse
175                         foreach(array_flat_leaves($array[$key]) as $_value) {
176                                 $tmp[] = $_value;
177                         }
178                 } else {
179                         $tmp[] = & $array[$key];
180                 }
181         }
182
183         return $unique ? array_values(array_unique($tmp)) : $tmp;
184 }
185
186 // $array['something'] => $array['wanted']
187 function array_rename_keys(& $array, $keys = array('from' => 'to'), $force = FALSE, $default = '')
188 {
189         if (! is_array($array) || ! is_array($keys)) return FALSE;
190
191         // Nondestructive test
192         if (! $force)
193                 foreach(array_keys($keys) as $from)
194                         if (! isset($array[$from]))
195                                 return FALSE;
196
197         foreach($keys as $from => $to) {
198                 if ($from === $to) continue;
199                 if (! $force || isset($array[$from])) {
200                         $array[$to] = & $array[$from];
201                         unset($array[$from]);
202                 } else  {
203                         $array[$to] = $default;
204                 }
205         }
206
207         return TRUE;
208 }
209
210 // Remove redundant values from array()
211 function array_unique_recursive($array = array())
212 {
213         if (! is_array($array)) return $array;
214
215         $tmp = array();
216         foreach($array as $key => $value){
217                 if (is_array($value)) {
218                         $array[$key] = array_unique_recursive($value);
219                 } else {
220                         if (isset($tmp[$value])) {
221                                 unset($array[$key]);
222                         } else {
223                                 $tmp[$value] = TRUE;
224                         }
225                 }
226         }
227
228         return $array;
229 }
230
231
232 // ---------------------
233 // Part One : Checker
234
235 // Rough implementation of globbing
236 //
237 // USAGE: $regex = '/^' . generate_glob_regex('*.txt', '/') . '$/i';
238 //
239 function generate_glob_regex($string = '', $divider = '/')
240 {
241         static $from = array(
242                          1 => '*',
243                         11 => '?',
244         //              22 => '[',      // Maybe cause regex compilation error (e.g. '[]')
245         //              23 => ']',      //
246                 );
247         static $mid = array(
248                          1 => '_AST_',
249                         11 => '_QUE_',
250         //              22 => '_RBR_',
251         //              23 => '_LBR_',
252                 );
253         static $to = array(
254                          1 => '.*',
255                         11 => '.',
256         //              22 => '[',
257         //              23 => ']',
258                 );
259
260         if (! is_string($string)) return '';
261
262         $string = str_replace($from, $mid, $string); // Hide
263         $string = preg_quote($string, $divider);
264         $string = str_replace($mid, $to, $string);   // Unhide
265
266         return $string;
267 }
268
269 // Generate host (FQDN, IPv4, ...) regex
270 // 'localhost'     : Matches with 'localhost' only
271 // 'example.org'   : Matches with 'example.org' only (See host_normalize() about 'www')
272 // '.example.org'  : Matches with ALL FQDN ended with '.example.org'
273 // '*.example.org' : Almost the same of '.example.org' except 'www.example.org'
274 // '10.20.30.40'   : Matches with IPv4 address '10.20.30.40' only
275 // [TODO] '192.'   : Matches with all IPv4 hosts started with '192.'
276 // TODO: IPv4, CIDR?, IPv6
277 function generate_host_regex($string = '', $divider = '/')
278 {
279         if (! is_string($string)) return '';
280
281         if (mb_strpos($string, '.') === FALSE)
282                 return generate_glob_regex($string, $divider);
283
284         $result = '';
285         if (is_ip($string)) {
286                 // IPv4
287                 return generate_glob_regex($string, $divider);
288         } else {
289                 // FQDN or something
290                 $part = explode('.', $string, 2);
291                 if ($part[0] == '') {
292                         $part[0] = '(?:.*\.)?'; // And all related FQDN
293                 } else if ($part[0] == '*') {
294                         $part[0] = '.*\.';      // All subdomains/hosts only
295                 } else {
296                         return generate_glob_regex($string, $divider);
297                 }
298                 $part[1] = generate_glob_regex($part[1], $divider);
299                 return implode('', $part);
300         }
301 }
302
303 // Rough hostname checker
304 // [OK] 192.168.
305 // TODO: Strict digit, 0x, CIDR, IPv6
306 function is_ip($string = '')
307 {
308         if (preg_match('/^' .
309                 '(?:[0-9]{1,3}\.){3}[0-9]{1,3}' . '|' .
310                 '(?:[0-9]{1,3}\.){1,3}' . '$/',
311                 $string)) {
312                 return 4;       // Seems IPv4(dot-decimal)
313         } else {
314                 return 0;       // Seems not IP
315         }
316 }
317
318 function get_blocklist($list = '')
319 {
320         static $regexes;
321
322         if ($list === NULL) {
323                 $regexes = NULL;        // Unset
324                 return array();
325         }
326
327         if (! isset($regexes)) {
328                 $regexes = array();
329                 if (file_exists(SPAM_INI_FILE)) {
330                         $blocklist = array();
331                         include(SPAM_INI_FILE);
332                         //      $blocklist['badhost'] = array(
333                         //              '*.blogspot.com',       // Blog services's subdomains (only)
334                         //              'IANA-examples' => '#^(?:.*\.)?example\.(?:com|net|org)$#',
335                         //      );
336                         if (isset($blocklist['list'])) {
337                                 $regexes['list'] = & $blocklist['list'];
338                         } else {
339                                 // Default
340                                 $blocklist['list'] = array(
341                                         'goodhost' => FALSE,
342                                         'badhost'  => TRUE,
343                                 );
344                         }
345                         foreach(array_keys($blocklist['list']) as $_list) {
346                                 if (! isset($blocklist[$_list])) continue;
347                                 foreach ($blocklist[$_list] as $key => $value) {
348                                         if (is_array($value)) {
349                                                 $regexes[$_list][$key] = array();
350                                                 foreach($value as $_key => $_value) {
351                                                         get_blocklist_add($regexes[$_list][$key], $_key, $_value);
352                                                 }
353                                         } else {
354                                                 get_blocklist_add($regexes[$_list], $key, $value);
355                                         }
356                                 }
357                                 unset($blocklist[$_list]);
358                         }
359                 }
360         }
361
362         if ($list === '') {
363                 return $regexes;        // ALL
364         } else if (isset($regexes[$list])) {
365                 return $regexes[$list];
366         } else {
367                 return array();
368         }
369 }
370
371 // Subroutine of get_blocklist()
372 function get_blocklist_add(& $array, $key = 0, $value = '*.example.org')
373 {
374         if (is_string($key)) {
375                 $array[$key] = & $value; // Treat $value as a regex
376         } else {
377                 $array[$value] = '/^' . generate_host_regex($value, '/') . '$/i';
378         }
379 }
380
381 // Blocklist metrics: Separate $host, to $blocked and not blocked
382 function blocklist_distiller(& $hosts, $keys = array('goodhost', 'badhost'), $asap = FALSE)
383 {
384         if (! is_array($hosts)) $hosts = array($hosts);
385         if (! is_array($keys))  $keys  = array($keys);
386
387         $list = get_blocklist('list');
388         $blocked = array();
389
390         foreach($keys as $key){
391                 foreach (get_blocklist($key) as $label => $regex) {
392                         if (is_array($regex)) {
393                                 foreach($regex as $_label => $_regex) {
394                                         $group = preg_grep($_regex, $hosts);
395                                         if ($group) {
396                                                 $hosts = array_diff($hosts, $group);
397                                                 $blocked[$key][$label][$_label] = $group;
398                                                 if ($asap && $list[$key]) break;
399                                         }
400                                 }
401                         } else {
402                                 $group = preg_grep($regex, $hosts);
403                                 if ($group) {
404                                         $hosts = array_diff($hosts, $group);
405                                         $blocked[$key][$label] = $group;
406                                         if ($asap && $list[$key]) break;
407                                 }
408                         }
409                 }
410         }
411
412         return $blocked;
413 }
414
415
416 // ---------------------
417
418
419 // Default (enabled) methods and thresholds (for content insertion)
420 function check_uri_spam_method($times = 1, $t_area = 0, $rule = TRUE)
421 {
422         $times  = intval($times);
423         $t_area = intval($t_area);
424
425         $positive = array(
426                 // Thresholds
427                 'quantity'     =>  8 * $times,  // Allow N URIs
428                 'non_uniqhost' =>  3 * $times,  // Allow N duped (and normalized) Hosts
429                 //'non_uniquri'=>  3 * $times,  // Allow N duped (and normalized) URIs
430
431                 // Areas
432                 'area_anchor'  => $t_area,      // Using <a href> HTML tag
433                 'area_bbcode'  => $t_area,      // Using [url] or [link] BBCode
434                 //'uri_anchor' => $t_area,      // URI inside <a href> HTML tag
435                 //'uri_bbcode' => $t_area,      // URI inside [url] or [link] BBCode
436         );
437         if ($rule) {
438                 $bool = array(
439                         // Rules
440                         //'asap'   => TRUE,     // Quit or return As Soon As Possible
441                         'uniqhost' => TRUE,     // Show uniq host (at block notification mail)
442                         'badhost'  => TRUE,     // Check badhost
443                 );
444         } else {
445                 $bool = array();
446         }
447
448         // Remove non-$positive values
449         foreach (array_keys($positive) as $key) {
450                 if ($positive[$key] < 0) unset($positive[$key]);
451         }
452
453         return $positive + $bool;
454 }
455
456 // Simple/fast spam check
457 function check_uri_spam($target = '', $method = array())
458 {
459         // Return value
460         $progress = array(
461                 'method'  => array(
462                         // Theme to do  => Dummy, optional value, or optional array()
463                         //'quantity'    => 8,
464                         //'uniqhost'    => TRUE,
465                         //'non_uniqhost'=> 3,
466                         //'non_uniquri' => 3,
467                         //'badhost'     => TRUE,
468                         //'area_anchor' => 0,
469                         //'area_bbcode' => 0,
470                         //'uri_anchor'  => 0,
471                         //'uri_bbcode'  => 0,
472                 ),
473                 'sum' => array(
474                         // Theme        => Volume found (int)
475                 ),
476                 'is_spam' => array(
477                         // Flag. If someting defined here,
478                         // one or more spam will be included
479                         // in this report
480                 ),
481                 'blocked' => array(
482                         // Hosts blocked
483                         //'category' => array(
484                         //      'host',
485                         //)
486                 ),
487                 'hosts' => array(
488                         // Hosts not blocked
489                 ),
490         );
491
492         // Aliases
493         $sum     = & $progress['sum'];
494         $is_spam = & $progress['is_spam'];
495         $progress['method'] = & $method;        // Argument
496         $blocked = & $progress['blocked'];
497         $hosts   = & $progress['hosts'];
498         $asap    = isset($method['asap']);
499
500         // Init
501         if (! is_array($method) || empty($method)) {
502                 $method = check_uri_spam_method();
503         }
504         foreach(array_keys($method) as $key) {
505                 if (! isset($sum[$key])) $sum[$key] = 0;
506         }
507         if (! isset($sum['quantity'])) $sum['quantity'] = 0;
508
509         if (is_array($target)) {
510                 foreach($target as $str) {
511                         if (! is_string($str)) continue;
512
513                         $_progress = check_uri_spam($str, $method);     // Recurse
514
515                         // Merge $sum
516                         $_sum = & $_progress['sum'];
517                         foreach (array_keys($_sum) as $key) {
518                                 if (! isset($sum[$key])) {
519                                         $sum[$key] = & $_sum[$key];
520                                 } else {
521                                         $sum[$key] += $_sum[$key];
522                                 }
523                         }
524
525                         // Merge $is_spam
526                         $_is_spam = & $_progress['is_spam'];
527                         foreach (array_keys($_is_spam) as $key) {
528                                 $is_spam[$key] = TRUE;
529                                 if ($asap) break;
530                         }
531                         if ($asap && $is_spam) break;
532
533                         // Merge only
534                         $blocked = array_merge_recursive($blocked, $_progress['blocked']);
535                         $hosts   = array_merge_recursive($hosts,   $_progress['hosts']);
536                 }
537
538                 // Unique values
539                 $blocked = array_unique_recursive($blocked);
540                 $hosts   = array_unique_recursive($hosts);
541
542                 // Recount $sum['badhost']
543                 $sum['badhost'] = array_count_leaves($blocked);
544
545                 return $progress;
546         }
547
548         // Area: There's HTML anchor tag
549         if ((! $asap || ! $is_spam) && isset($method['area_anchor'])) {
550                 $key = 'area_anchor';
551                 $_asap = isset($method['asap']) ? array('asap' => TRUE) : array();
552                 $result = area_pickup($target, array($key => TRUE) + $_asap);
553                 if ($result) {
554                         $sum[$key] = $result[$key];
555                         if (isset($method[$key]) && $sum[$key] > $method[$key]) {
556                                 $is_spam[$key] = TRUE;
557                         }
558                 }
559         }
560
561         // Area: There's 'BBCode' linking tag
562         if ((! $asap || ! $is_spam) && isset($method['area_bbcode'])) {
563                 $key = 'area_bbcode';
564                 $_asap = isset($method['asap']) ? array('asap' => TRUE) : array();
565                 $result = area_pickup($target, array($key => TRUE) + $_asap);
566                 if ($result) {
567                         $sum[$key] = $result[$key];
568                         if (isset($method[$key]) && $sum[$key] > $method[$key]) {
569                                 $is_spam[$key] = TRUE;
570                         }
571                 }
572         }
573
574         // Return if ...
575         if ($asap && $is_spam) return $progress;
576
577         // URI: Pickup
578         $pickups = uri_pickup_normalize(spam_uri_pickup($target, $method));
579
580         // Return if ...
581         if (empty($pickups)) return $progress;
582
583         // URI: Check quantity
584         $sum['quantity'] += count($pickups);
585                 // URI quantity
586         if ((! $asap || ! $is_spam) && isset($method['quantity']) &&
587                 $sum['quantity'] > $method['quantity']) {
588                 $is_spam['quantity'] = TRUE;
589         }
590
591         // URI: used inside HTML anchor tag pair
592         if ((! $asap || ! $is_spam) && isset($method['uri_anchor'])) {
593                 $key = 'uri_anchor';
594                 foreach($pickups as $pickup) {
595                         if (isset($pickup['area'][$key])) {
596                                 $sum[$key] += $pickup['area'][$key];
597                                 if(isset($method[$key]) &&
598                                         $sum[$key] > $method[$key]) {
599                                         $is_spam[$key] = TRUE;
600                                         if ($asap && $is_spam) break;
601                                 }
602                                 if ($asap && $is_spam) break;
603                         }
604                 }
605         }
606
607         // URI: used inside 'BBCode' pair
608         if ((! $asap || ! $is_spam) && isset($method['uri_bbcode'])) {
609                 $key = 'uri_bbcode';
610                 foreach($pickups as $pickup) {
611                         if (isset($pickup['area'][$key])) {
612                                 $sum[$key] += $pickup['area'][$key];
613                                 if(isset($method[$key]) &&
614                                         $sum[$key] > $method[$key]) {
615                                         $is_spam[$key] = TRUE;
616                                         if ($asap && $is_spam) break;
617                                 }
618                                 if ($asap && $is_spam) break;
619                         }
620                 }
621         }
622
623         // URI: Uniqueness (and removing non-uniques)
624         if ((! $asap || ! $is_spam) && isset($method['non_uniquri'])) {
625
626                 $uris = array();
627                 foreach (array_keys($pickups) as $key) {
628                         $uris[$key] = uri_pickup_implode($pickups[$key]);
629                 }
630                 $count = count($uris);
631                 $uris  = array_unique($uris);
632                 $sum['non_uniquri'] += $count - count($uris);
633                 if ($sum['non_uniquri'] > $method['non_uniquri']) {
634                         $is_spam['non_uniquri'] = TRUE;
635                 }
636                 if (! $asap || ! $is_spam) {
637                         foreach (array_diff(array_keys($pickups),
638                                 array_keys($uris)) as $remove) {
639                                 unset($pickups[$remove]);
640                         }
641                 }
642                 unset($uris);
643         }
644
645         // Return if ...
646         if ($asap && $is_spam) return $progress;
647
648         // Host: Uniqueness (uniq / non-uniq)
649         foreach ($pickups as $pickup) $hosts[] = & $pickup['host'];
650         $hosts = array_unique($hosts);
651         $sum['uniqhost'] += count($hosts);
652         if ((! $asap || ! $is_spam) && isset($method['non_uniqhost'])) {
653                 $sum['non_uniqhost'] = $sum['quantity'] - $sum['uniqhost'];
654                 if ($sum['non_uniqhost'] > $method['non_uniqhost']) {
655                         $is_spam['non_uniqhost'] = TRUE;
656                 }
657         }
658
659         // Return if ...
660         if ($asap && $is_spam) return $progress;
661
662         // URI: Bad host (Separate good/bad hosts from $hosts)
663         if ((! $asap || ! $is_spam) && isset($method['badhost'])) {
664
665                 // is_badhost()
666                 $list = get_blocklist('list');
667                 $blocked = blocklist_distiller($hosts, array_keys($list), $asap);
668                 foreach($list as $key=>$type){
669                         if (! $type) unset($blocked[$key]); // Ignore goodhost etc
670                 }
671                 unset($list);
672
673                 if (! empty($blocked)) $is_spam['badhost'] = TRUE;
674         }
675
676         return $progress;
677 }
678
679 // ---------------------
680 // Reporting
681
682 // Summarize $progress (blocked only)
683 function summarize_spam_progress($progress = array(), $blockedonly = FALSE)
684 {
685         if ($blockedonly) {
686                 $tmp = array_keys($progress['is_spam']);
687         } else {
688                 $tmp = array();
689                 $method = & $progress['method'];
690                 if (isset($progress['sum'])) {
691                         foreach ($progress['sum'] as $key => $value) {
692                                 if (isset($method[$key]) && $value) {
693                                         $tmp[] = $key . '(' . $value . ')';
694                                 }
695                         }
696                 }
697         }
698
699         return implode(', ', $tmp);
700 }
701
702 function summarize_detail_badhost($progress = array())
703 {
704         if (! isset($progress['blocked']) || empty($progress['blocked'])) return '';
705
706         // Flat per group
707         $blocked = array();
708         foreach($progress['blocked'] as $list => $lvalue) {
709                 foreach($lvalue as $group => $gvalue) {
710                         $flat = implode(', ', array_flat_leaves($gvalue));
711                         if ($flat === $group) {
712                                 $blocked[$list][]       = $flat;
713                         } else {
714                                 $blocked[$list][$group] = $flat;
715                         }
716                 }
717         }
718
719         // Shrink per list
720         // From: 'A-1' => array('ie.to')
721         // To:   'A-1' => 'ie.to'
722         foreach($blocked as $list => $lvalue) {
723                 if (is_array($lvalue) &&
724                    count($lvalue) == 1 &&
725                    is_numeric(key($lvalue))) {
726                     $blocked[$list] = current($lvalue);
727                 }
728         }
729
730         return var_export_shrink($blocked, TRUE, TRUE);
731 }
732
733 function summarize_detail_newtral($progress = array())
734 {
735         if (! isset($progress['hosts'])    ||
736             ! is_array($progress['hosts']) ||
737             empty($progress['hosts'])) return '';
738
739         // Generate a responsible $trie
740         $trie = array();
741         foreach($progress['hosts'] as $value) {
742                 // 'A.foo.bar.example.com'
743                 $resp = whois_responsibility($value);   // 'example.com'
744                 if (empty($resp)) {
745                         // One or more test, or do nothing here
746                         $resp = strval($value);
747                         $rest = '';
748                 } else {
749                         $rest = rtrim(substr($value, 0, - strlen($resp)), '.'); // 'A.foo.bar'
750                 }
751                 $trie = array_merge_recursive($trie, array($resp => array($rest => NULL)));
752         }
753
754         // Format: var_export_shrink() -like output
755         $result = array();
756         ksort_by_domain($trie);
757         foreach(array_keys($trie) as $key) {
758                 ksort_by_domain($trie[$key]);
759                 if (count($trie[$key]) == 1 && key($trie[$key]) == '') {
760                         // Just one 'responsibility.example.com'
761                         $result[] = '  \'' . $key . '\',';
762                 } else {
763                         // One subdomain-or-host, or several ones
764                         $subs = array();
765                         foreach(array_keys($trie[$key]) as $sub) {
766                                 if ($sub == '') {
767                                         $subs[] = $key;
768                                 } else {
769                                         $subs[] = $sub . '.' . $key;
770                                 }
771                         }
772                         $result[] = '  \'' . $key . '\' => \'' . implode(', ', $subs) . '\',';
773                 }
774                 unset($trie[$key]);
775         }
776         return
777                 'array (' . "\n" .
778                         implode("\n", $result) . "\n" .
779                 ')';
780 }
781
782
783 // Check responsibility-root of the FQDN
784 // 'foo.bar.example.com'        => 'example.com'        (.com        has the last whois for it)
785 // 'foo.bar.example.au'         => 'example.au'         (.au         has the last whois for it)
786 // 'foo.bar.example.edu.au'     => 'example.edu.au'     (.edu.au     has the last whois for it)
787 // 'foo.bar.example.act.edu.au' => 'example.act.edu.au' (.act.edu.au has the last whois for it)
788 function whois_responsibility($fqdn = 'foo.bar.example.com', $parent = FALSE, $implicit = TRUE)
789 {
790         static $domain;
791
792         if ($fqdn === NULL) {
793                 $domain = NULL; // Unset
794                 return '';
795         }
796         if (! is_string($fqdn)) return '';
797
798         if (is_ip($fqdn)) return $fqdn;
799
800         if (! isset($domain)) {
801                 $domain = array();
802                 if (file_exists(DOMAIN_INI_FILE)) {
803                         include(DOMAIN_INI_FILE);       // Set
804                 }
805         }
806
807         $result  = array();
808         $dcursor = & $domain;
809         $array   = array_reverse(explode('.', $fqdn));
810         $i = 0;
811         while(TRUE) {
812                 if (! isset($array[$i])) break;
813                 $acursor = $array[$i];
814                 if (is_array($dcursor) && isset($dcursor[$acursor])) {
815                         $result[] = & $array[$i];
816                         $dcursor  = & $dcursor[$acursor];
817                 } else {
818                         if (! $parent && isset($acursor)) {
819                                 $result[] = & $array[$i];       // Whois servers must know this subdomain
820                         }
821                         break;
822                 }
823                 ++$i;
824         }
825
826         // Implicit responsibility: Top-Level-Domains must not be yours
827         // 'bar.foo.something' => 'foo.something'
828         if ($implicit && count($result) == 1 && count($array) > 1) {
829                 $result[] = & $array[1];
830         }
831
832         return $result ? implode('.', array_reverse($result)) : '';
833 }
834
835
836 // ---------------------
837 // Exit
838
839 // Freeing memories
840 function spam_dispose()
841 {
842         get_blocklist(NULL);
843         whois_responsibility(NULL);
844 }
845
846 // Common bahavior for blocking
847 // NOTE: Call this function from various blocking feature, to disgueise the reason 'why blocked'
848 function spam_exit($mode = '', $data = array())
849 {
850         $exit = TRUE;
851
852         switch ($mode) {
853                 case '':
854                         echo("\n");
855                         break;
856                 case 'dump':
857                         echo('<pre>' . "\n");
858                         echo htmlspecialchars(var_export($data, TRUE));
859                         echo('</pre>' . "\n");
860                         break;
861         };
862
863         if ($exit) exit;        // Force exit
864 }
865
866
867 // ---------------------
868 // Simple filtering
869
870 // TODO: Record them
871 // Simple/fast spam filter ($target: 'a string' or an array())
872 function pkwk_spamfilter($action, $page, $target = array('title' => ''), $method = array(), $exitmode = '')
873 {
874         $progress = check_uri_spam($target, $method);
875
876         if (empty($progress['is_spam'])) {
877                 spam_dispose();
878         } else {
879
880 // TODO: detect encoding from $target for mbstring functions
881 //              $tmp = array();
882 //              foreach(array_keys($target) as $key) {
883 //                      $tmp[strings($key, 0, FALSE, TRUE)] = strings($target[$key], 0, FALSE, TRUE);   // Removing "\0" etc
884 //              }
885 //              $target = & $tmp;
886
887                 pkwk_spamnotify($action, $page, $target, $progress, $method);
888                 spam_exit($exitmode, $progress);
889         }
890 }
891
892 // ---------------------
893 // PukiWiki original
894
895 // Mail to administrator(s)
896 function pkwk_spamnotify($action, $page, $target = array('title' => ''), $progress = array(), $method = array())
897 {
898         global $notify, $notify_subject;
899
900         if (! $notify) return;
901
902         $asap = isset($method['asap']);
903
904         $summary['ACTION']  = 'Blocked by: ' . summarize_spam_progress($progress, TRUE);
905         if (! $asap) {
906                 $summary['METRICS'] = summarize_spam_progress($progress);
907         }
908
909         $tmp = summarize_detail_badhost($progress);
910         if ($tmp != '') $summary['DETAIL_BADHOST'] = $tmp;
911
912         $tmp = summarize_detail_newtral($progress);
913         if (! $asap && $tmp != '') $summary['DETAIL_NEUTRAL_HOST'] = $tmp;
914
915         $summary['COMMENT'] = $action;
916         $summary['PAGE']    = '[blocked] ' . (is_pagename($page) ? $page : '');
917         $summary['URI']     = get_script_uri() . '?' . rawurlencode($page);
918         $summary['USER_AGENT']  = TRUE;
919         $summary['REMOTE_ADDR'] = TRUE;
920         pkwk_mail_notify($notify_subject,  var_export($target, TRUE), $summary, TRUE);
921 }
922
923 ?>