d3533badab030161b44fc10fb3a2d8e6e088ec03
[lhc/web/www.git] / www / plugins / nospam / inc / nospam.php
1 <?php
2 /**
3 * Plugin No-SPAM
4 * (c) 2008 Cedric Morin Yterium.net
5 * Licence GPL
6 *
7 */
8
9 if (!defined("_ECRIRE_INC_VERSION")) return;
10
11 function nospam_hash_env() {
12 static $res ='';
13 if ($res) return $res;
14 $ip = explode('.',$GLOBALS['ip']);
15 array_pop($ip);
16 $ip = implode('.',$ip).".xxx";
17 $res = md5($ip. $_SERVER['HTTP_USER_AGENT']);
18 #spip_log("jeton $res pour ".$ip. $_SERVER['HTTP_USER_AGENT'],"jetons");
19 return $res;
20 }
21
22
23 /**
24 * Calcule une cle de jeton pour un formulaire
25 *
26 * @param string $form
27 * nom du formulaire
28 * @param string $qui
29 * identifiant du visiteur a qui est attribue le jeton
30 * @return string
31 * cle calculee
32 */
33 function creer_jeton($form, $qui=NULL) {
34 $time = date('Y-m-d-H');
35 if (is_null($qui)){
36 if (isset($GLOBALS['visiteur_session']['id_auteur']) AND intval($GLOBALS['visiteur_session']['id_auteur']))
37 $qui = ":".$GLOBALS['visiteur_session']['id_auteur'].":".$GLOBALS['visiteur_session']['nom'];
38 elseif (!defined('_IS_BOT') OR !_IS_BOT) { // pas de jeton pour les bots qui n'ont rien d'interessant a poster
39 $qui = nospam_hash_env();
40 }
41 }
42 include_spip('inc/securiser_action');
43 // le jeton prend en compte l'heure et l'identite de l'internaute
44 return calculer_cle_action("jeton$form$time$qui");
45 }
46
47 /**
48 * Verifie une cle de jeton pour un formulaire
49 *
50 * @param string $jeton
51 * cle recue
52 * @param string $form nom du formulaire
53 * nom du formulaire
54 * @param string $qui
55 * identifiant du visiteur a qui est attribue le jeton
56 * @return bool cle correcte ?
57 */
58 function verifier_jeton($jeton, $form, $qui=NULL) {
59 $time = time();
60 $time_old = date('Y-m-d-H',$time-3600);
61 $time = date('Y-m-d-H',$time);
62
63 if (is_null($qui)){
64 if (isset($GLOBALS['visiteur_session']['id_auteur']) AND intval($GLOBALS['visiteur_session']['id_auteur']))
65 $qui = ":".$GLOBALS['visiteur_session']['id_auteur'].":".$GLOBALS['visiteur_session']['nom'];
66 else {
67 $qui = nospam_hash_env();
68 }
69 }
70
71 $ok = (verifier_cle_action("jeton$form$time$qui",$jeton)
72 or verifier_cle_action("jeton$form$time_old$qui",$jeton));
73 #if (!$ok)
74 # spip_log("Erreur form:$form qui:$qui agent:".$_SERVER['HTTP_USER_AGENT']." ip:".$GLOBALS['ip'],'fauxjeton');
75 return $ok;
76 }
77
78
79 /**
80 * Compte le nombre de caracteres d'une chaine,
81 * mais en supprimant tous les liens
82 * (qu'ils soient ou non ecrits en raccourcis SPIP)
83 * ainsi que tous les espaces en trop
84 *
85 * @param string $texte
86 * texte d'entree
87 * @param bool $propre
88 * passer le texte dans propre ou non
89 * @return int
90 * compte du texte nettoye
91 */
92 function compter_caracteres_utiles($texte, $propre=true) {
93 include_spip('inc/charsets');
94 if ($propre) $texte = propre($texte);
95 $u = $GLOBALS['meta']['pcre_u'];
96 // regarder si il y a du contenu en dehors des liens !
97 $texte = PtoBR($texte);
98 $texte = preg_replace(",<a.*</a>,{$u}Uims",'',$texte);
99 // \W matche tous les caracteres non ascii apres 0x80
100 // et vide donc les chaines constitues de caracteres unicodes uniquement
101 // on remplace par un match qui elimine uniquement
102 // les non \w et les non unicodes
103 $texte = trim(preg_replace(",[^\w\x80-\xFF]+,ims",' ',$texte));
104
105 // on utilise spip_strlen pour compter la longueur correcte
106 // pour les chaines unicodes
107 return spip_strlen($texte);
108 }
109
110
111 /**
112 * Retourne un tableau d'analyse du texte transmis
113 * Cette analyse concerne principalement des statistiques sur les liens
114 *
115 * @param string $texte texte d'entree
116 * @return array rapport d'analyse
117 */
118 function analyser_spams($texte) {
119 $infos = array(
120 'caracteres_utiles' => 0, // nombre de caracteres sans les liens
121 'nombre_liens' => 0, // nombre de liens
122 'caracteres_texte_lien_min' => 0, // nombre de caracteres du plus petit titre de lien
123 'contenu_cache' => false, // du contenu est caché en CSS ?
124 );
125
126 if (!$texte) return $infos;
127
128 // on travaille d'abord sur le texte 'brut' tel que saisi par
129 // l'utilisateur pour ne pas avoir les class= et style= que spip ajoute
130 // sur les raccourcis.
131
132 // on ne tient pas compte des blocs <code> et <cadre> ni de leurs contenus
133 include_spip("inc/texte_mini");
134 if (!function_exists('echappe_html')) // SPIP 2.x
135 include_spip("inc/texte");
136 $texte_humain = echappe_html($texte);
137 // on repère dans ce qui reste la présence de style= ou class= qui peuvent
138 // servir à masquer du contenu
139 // les spammeurs utilisent le laxisme des navigateurs pour envoyer aussi style =
140 // soyons donc mefiant
141 // (mais en enlevant le base64 !)
142 $texte_humain = str_replace('class="base64"','',$texte_humain);
143 $hidden = ",(<(img|object)|\s(?:style|class)\s*=[^>]+>),UimsS";
144 if (preg_match($hidden,$texte_humain)) {
145 // suspicion de spam
146 $infos['contenu_cache'] = true;
147 }
148
149 include_spip('inc/texte');
150 $texte = propre($texte);
151
152 // caracteres_utiles
153 $infos['caracteres_utiles'] = compter_caracteres_utiles($texte, false);
154
155 // nombre de liens
156 $liens = array_filter(extraire_balises($texte,'a'),'pas_lien_ancre');
157 $infos['nombre_liens'] = count($liens);
158 $infos['liens'] = $liens;
159
160 // taille du titre de lien minimum
161 if (count($liens)) {
162 // supprimer_tags() s'applique a tout le tableau,
163 // mais attention a verifier dans le temps que ca continue a fonctionner
164 # $titres_liens = array_map('supprimer_tags', $liens);
165 $titres_liens = supprimer_tags($liens);
166 $titres_liens = array_map('strlen', $titres_liens);
167 $infos['caracteres_texte_lien_min'] = min($titres_liens);
168 }
169 return $infos;
170 }
171
172 /**
173 * Vérifier si un lien est *n'est pas* une ancre : dans ce cas, ne pas le compte (ici, fonction de filtre de tableau)
174 * Cette analyse concerne principalement des statistiques sur les liens
175 *
176 * @param string $texte lien
177 * @return boolean : true ->
178 */
179 function pas_lien_ancre($texte){
180 return substr(extraire_attribut($texte,'href'),0,1) == '#' ? false : true;
181
182 }
183
184 /**
185 * Compare les domaines des liens fournis avec la presence dans la base
186 *
187 * @param array $liens
188 * liste des liens html
189 * @param int $seuil
190 * seuil de detection de presence : nombre d'enregistrement qui ont deja un lien avec le meme domaine
191 * @param string $table
192 * table sql
193 * @param array $champs
194 * champs a prendre en compte dans la detection
195 * @param null|string $condstatut
196 * condition sur le statut='spam' pour ne regarder que les enregistrement en statut spam
197 * @return bool
198 */
199 function rechercher_presence_liens_spammes($liens,$seuil,$table,$champs,$condstatut=null){
200 include_spip("inc/filtres");
201
202 if (is_null($condstatut))
203 $condstatut = "statut=".sql_quote('spam');
204 if ($condstatut)
205 $condstatut = "$condstatut AND ";
206
207 // limiter la recherche au mois precedent
208 $trouver_table = charger_fonction("trouver_table","base");
209 if ($desc = $trouver_table($table)
210 AND isset($desc['date'])){
211 $depuis = date('Y-m-d H:i:s',strtotime("-1 month"));
212 $condstatut .= $desc['date'].">".sql_quote($depuis)." AND ";
213 }
214
215 // ne pas prendre en compte les liens sur le meme domaine que celui du site
216 $allowed = array();
217 $tests = array($GLOBALS['meta']['adresse_site'],url_de_base());
218 foreach ($tests as $t){
219 if ($parse = parse_url($t)
220 AND $parse['host']){
221 $host = explode(".",$parse['host']);
222 while (count($host)>2) array_shift($host);
223 $allowed[] = implode(".",$host);
224 }
225 }
226 if (count($allowed)){
227 $allowed = array_map('preg_quote',$allowed);
228 $allowed = implode("|",$allowed);
229 $allowed = "/($allowed)$/";
230 spip_log("domaines whitelist pour les liens spams : $allowed","nospam");
231 }
232 else
233 $allowed = "";
234
235
236 $hosts = array();
237 foreach ($liens as $lien){
238 $url = extraire_attribut($lien,"href");
239 if ($parse = parse_url($url)
240 AND $parse['host']
241 AND (!$allowed OR !preg_match($allowed,$parse['host'])))
242 $hosts[] = $parse['host'];
243 }
244
245 $hosts = array_unique($hosts);
246 $hosts = array_filter($hosts);
247
248 // pour chaque host figurant dans un lien, regarder si on a pas deja eu des spams avec ce meme host
249 // auquel cas on refuse poliment le message
250 foreach($hosts as $h){
251 $like = " LIKE ".sql_quote("%$h%");
252 $where = $condstatut . "(".implode("$like OR ",$champs)."$like)";
253 if (($n=sql_countsel($table,$where))>=$seuil){
254 // loger les 10 premiers messages concernes pour aider le webmestre
255 $all = sql_allfetsel(id_table_objet($table),$table,$where,'','','0,10');
256 $all = array_map('reset',$all);
257 spip_log("$n liens trouves $like dans table $table (".implode(",",$all).") [champs ".implode(',',$champs)."]","nospam");
258 return $h;
259 }
260 }
261 return false;
262 }
263 ?>