[SPIP] v3.2.1-->v3.2.2
[lhc/web/www.git] / www / ecrire / auth / spip.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2019 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
12
13 /**
14 * Gestion de l'authentification par SPIP
15 *
16 * @package SPIP\Core\Authentification\SPIP
17 **/
18
19 if (!defined('_ECRIRE_INC_VERSION')) {
20 return;
21 }
22
23 /**
24 * Authentifie et si ok retourne le tableau de la ligne SQL de l'utilisateur
25 * Si risque de secu repere a l'installation retourne False
26 *
27 * @param string $login
28 * @param string $pass
29 * @param string $serveur
30 * @param bool $phpauth
31 * @return array|bool
32 */
33 function auth_spip_dist($login, $pass, $serveur = '', $phpauth = false) {
34
35 // retrouver le login
36 $login = auth_spip_retrouver_login($login);
37 // login inconnu, n'allons pas plus loin
38 if (!$login) {
39 return array();
40 }
41
42 $md5pass = "";
43 $shapass = $shanext = "";
44
45 if (preg_match(",^\{([0-9a-f]{64});([0-9a-f]{64})\}$,i", $pass, $regs)) {
46 $shapass = $regs[1];
47 $shanext = $regs[2];
48 } // compat avec une base mixte md5/sha256 : le js a envoye les 2 hash
49 elseif (preg_match(",^\{([0-9a-f]{64});([0-9a-f]{64});([0-9a-f]{32});([0-9a-f]{32})\}$,i", $pass, $regs)) {
50 $shapass = $regs[1];
51 $shanext = $regs[2];
52 $md5pass = $regs[3];
53 //$md5next = $regs[4];
54 } // si envoi non crypte, crypter maintenant
55 elseif ($pass) {
56 $row = sql_fetsel("alea_actuel, alea_futur", "spip_auteurs", "login=" . sql_quote($login, $serveur, 'text'), '', '',
57 '', '', $serveur);
58
59 if ($row) {
60 include_spip('auth/sha256.inc');
61 $shapass = _nano_sha256($row['alea_actuel'] . $pass);
62 $shanext = _nano_sha256($row['alea_futur'] . $pass);
63 $md5pass = md5($row['alea_actuel'] . $pass);
64 }
65 }
66
67 // login inexistant ou mot de passe vide
68 if (!$shapass and !$md5pass) {
69 return array();
70 }
71
72 $row = sql_fetsel("*", "spip_auteurs",
73 "login=" . sql_quote($login, $serveur, 'text') . " AND pass=" . sql_quote($shapass, $serveur,
74 'text') . " AND statut<>'5poubelle'", '', '', '', '', $serveur);
75
76 // compat avec les anciennes bases en md5
77 if (!$row and $md5pass) {
78 $row = sql_fetsel("*", "spip_auteurs",
79 "login=" . sql_quote($login, $serveur, 'text') . " AND pass=" . sql_quote($md5pass, $serveur,
80 'text') . " AND statut<>'5poubelle'", '', '', '', '', $serveur);
81 }
82
83 // login/mot de passe incorrect
84 if (!$row) {
85 return array();
86 }
87
88 // fait tourner le codage du pass dans la base
89 // sauf si phpauth : cela reviendrait a changer l'alea a chaque hit, et aucune action verifiable par securiser_action()
90 if ($shanext and !$phpauth) {
91
92 include_spip('inc/acces'); // pour creer_uniqid
93 @sql_update('spip_auteurs', array(
94 'alea_actuel' => 'alea_futur',
95 'pass' => sql_quote($shanext, $serveur, 'text'),
96 'alea_futur' => sql_quote(creer_uniqid(), $serveur, 'text')
97 ), "id_auteur=" . $row['id_auteur'] . ' AND pass IN (' . sql_quote($shapass, $serveur,
98 'text') . ', ' . sql_quote($md5pass, $serveur, 'text') . ')', '', $serveur);
99 // En profiter pour verifier la securite de tmp/
100 // Si elle ne fonctionne pas a l'installation, prevenir
101 if (!verifier_htaccess(_DIR_TMP) and defined('_ECRIRE_INSTALL')) {
102 return false;
103 }
104 }
105
106 return $row;
107 }
108
109 /**
110 * Completer le formulaire de login avec le js ou les saisie specifiques a ce mode d'auth
111 *
112 * @param array $flux
113 * @return array
114 */
115 function auth_spip_formulaire_login($flux) {
116 // faut il encore envoyer md5 ?
117 // on regarde si il reste des pass md5 en base pour des auteurs en statut pas poubelle
118 // les hash md5 ont une longueur 32, les sha 64
119 // en evitant une requete sql a chaque affichage du formulaire login sans session
120 // (perf issue pour les sites qui mettent le formulaire de login sur la home)
121 $compat_md5 = false;
122 if (!isset($GLOBALS['meta']['sha_256_only']) or _request('var_mode')) {
123 $compat_md5 = sql_countsel("spip_auteurs", "length(pass)=32 AND statut<>'poubelle'");
124 if ($compat_md5 and isset($GLOBALS['meta']['sha_256_only'])) {
125 effacer_meta('sha_256_only');
126 }
127 if (!$compat_md5) {
128 ecrire_meta('sha_256_only', 'oui');
129 }
130 }
131
132 // javascript qui gere la securite du login en evitant de faire circuler le pass en clair
133 $flux['data'] .=
134 ($compat_md5 ? '<script type="text/javascript" src="' . _DIR_JAVASCRIPT . 'md5.js"></script>' : '')
135 . '<script type="text/javascript" src="' . _DIR_JAVASCRIPT . 'login-sha-min.js"></script>'
136 . '<script type="text/javascript">/*<![CDATA[*/'
137 . "var login_info={'alea_actuel':'" . $flux['args']['contexte']['_alea_actuel'] . "',"
138 . "'alea_futur':'" . $flux['args']['contexte']['_alea_futur'] . "',"
139 . "'login':'" . $flux['args']['contexte']['var_login'] . "',"
140 . "'page_auteur': '" . generer_url_public('informer_auteur') . "',"
141 . "'informe_auteur_en_cours':false,"
142 . "'attente_informe':0,"
143 . "'compat_md5':" . ($compat_md5 ? "true" : "false") . "};"
144 . "jQuery(function(){
145 jQuery('#password').after(\"<em id='pass_securise'><img src='" . chemin_image('cadenas-16.png') . "' width='16' height='16' alt='" . attribut_html(_T('login_securise')) . "' title='" . attribut_html(_T('login_securise')) . "' \/><\/em>\");
146 affiche_login_secure();
147 jQuery('#var_login').change(actualise_auteur);
148 jQuery('form#formulaire_login').submit(login_submit);
149 });"
150 . "/*]]>*/</script>";
151
152 return $flux;
153 }
154
155
156 /**
157 * Informer du droit de modifier ou non son login
158 *
159 * @param string $serveur
160 * @return bool
161 * toujours true pour un auteur cree dans SPIP
162 */
163 function auth_spip_autoriser_modifier_login($serveur = '') {
164 if (strlen($serveur)) {
165 return false;
166 } // les fonctions d'ecriture sur base distante sont encore incompletes
167 return true;
168 }
169
170 /**
171 * Verification de la validite d'un login pour le mode d'auth concerne
172 *
173 * @param string $new_login
174 * @param int $id_auteur
175 * si auteur existant deja
176 * @param string $serveur
177 * @return string
178 * message d'erreur si login non valide, chaine vide sinon
179 */
180 function auth_spip_verifier_login($new_login, $id_auteur = 0, $serveur = '') {
181 // login et mot de passe
182 if (strlen($new_login)) {
183 if (strlen($new_login) < _LOGIN_TROP_COURT) {
184 return _T('info_login_trop_court_car_pluriel', array('nb' => _LOGIN_TROP_COURT));
185 } else {
186 $n = sql_countsel('spip_auteurs',
187 "login=" . sql_quote($new_login) . " AND id_auteur!=" . intval($id_auteur) . " AND statut!='5poubelle'", '', '',
188 $serveur);
189 if ($n) {
190 return _T('info_login_existant');
191 }
192 }
193 }
194
195 return '';
196 }
197
198 /**
199 * Modifier le login d'un auteur SPIP
200 *
201 * @param string $new_login
202 * @param int $id_auteur
203 * @param string $serveur
204 * @return bool
205 */
206 function auth_spip_modifier_login($new_login, $id_auteur, $serveur = '') {
207 if (is_null($new_login) or auth_spip_verifier_login($new_login, $id_auteur, $serveur) != '') {
208 return false;
209 }
210 if (!$id_auteur = intval($id_auteur)
211 or !$auteur = sql_fetsel('login', 'spip_auteurs', 'id_auteur=' . intval($id_auteur), '', '', '', '', $serveur)
212 ) {
213 return false;
214 }
215 if ($new_login == $auteur['login']) {
216 return true;
217 } // on a rien fait mais c'est bon !
218
219 include_spip('action/editer_auteur');
220
221 // vider le login des auteurs a la poubelle qui avaient ce meme login
222 if (strlen($new_login)) {
223 $anciens = sql_allfetsel('id_auteur', 'spip_auteurs',
224 'login=' . sql_quote($new_login, $serveur, 'text') . " AND statut='5poubelle'", '', '', '', '', $serveur);
225 while ($row = array_pop($anciens)) {
226 auteur_modifier($row['id_auteur'], array('login' => ''), true); // manque la gestion de $serveur
227 }
228 }
229
230 auteur_modifier($id_auteur, array('login' => $new_login), true); // manque la gestion de $serveur
231
232 return true;
233 }
234
235 /**
236 * Retrouver le login de quelqu'un qui cherche a se loger
237 * Reconnaitre aussi ceux qui donnent leur nom ou email au lieu du login
238 *
239 * @param string $login
240 * @param string $serveur
241 * @return string
242 */
243 function auth_spip_retrouver_login($login, $serveur = '') {
244 if (!strlen($login)) {
245 return null;
246 } // pas la peine de requeter
247 $l = sql_quote($login, $serveur, 'text');
248 if ($r = sql_getfetsel('login', 'spip_auteurs',
249 "statut<>'5poubelle'" .
250 " AND (length(pass)>0)" .
251 " AND (login=$l)", '', '', '', '', $serveur)
252 ) {
253 return $r;
254 }
255 // Si pas d'auteur avec ce login
256 // regarder s'il a saisi son nom ou son mail.
257 // Ne pas fusionner avec la requete precedente
258 // car un nom peut etre homonyme d'un autre login
259 else {
260 return sql_getfetsel('login', 'spip_auteurs',
261 "statut<>'5poubelle'" .
262 " AND (length(pass)>0)" .
263 " AND (login<>'' AND (nom=$l OR email=$l))", '', '', '', '', $serveur);
264 }
265 }
266
267
268 /**
269 * informer sur un login
270 * Ce dernier transmet le tableau ci-dessous a la fonction JS informer_auteur
271 * Il est invoque par la fonction JS actualise_auteur via la globale JS
272 * page_auteur=#URL_PAGE{informer_auteur} dans le squelette login
273 * N'y aurait-il pas plus simple ?
274 *
275 * @param array $infos
276 * @param array $row
277 * @param string $serveur
278 * @return array
279 */
280 function auth_spip_informer_login($infos, $row, $serveur = '') {
281
282 // pour la methode SPIP on a besoin des alea en plus pour encoder le pass avec
283 $infos['alea_actuel'] = $row['alea_actuel'];
284 $infos['alea_futur'] = $row['alea_futur'];
285
286 return $infos;
287 }
288
289 /**
290 * Informer du droit de modifier ou non le pass
291 *
292 * @param string $serveur
293 * @return bool
294 * toujours true pour un auteur cree dans SPIP
295 */
296 function auth_spip_autoriser_modifier_pass($serveur = '') {
297 if (strlen($serveur)) {
298 return false;
299 } // les fonctions d'ecriture sur base distante sont encore incompletes
300 return true;
301 }
302
303
304 /**
305 * Verification de la validite d'un mot de passe pour le mode d'auth concerne
306 * c'est ici que se font eventuellement les verifications de longueur mini/maxi
307 * ou de force
308 *
309 * @param string $login
310 * Le login de l'auteur : permet de verifier que pass et login sont differents
311 * meme a la creation lorsque l'auteur n'existe pas encore
312 * @param string $new_pass
313 * Nouveau mot de passe
314 * @param int $id_auteur
315 * si auteur existant deja
316 * @param string $serveur
317 * @return string
318 * message d'erreur si login non valide, chaine vide sinon
319 */
320 function auth_spip_verifier_pass($login, $new_pass, $id_auteur = 0, $serveur = '') {
321 // login et mot de passe
322 if (strlen($new_pass) < _PASS_LONGUEUR_MINI) {
323 return _T('info_passe_trop_court_car_pluriel', array('nb' => _PASS_LONGUEUR_MINI));
324 }
325
326 return '';
327 }
328
329 /**
330 * Modifier le mot de passe de l'auteur sur le serveur concerne
331 * en s'occupant du hash et companie
332 *
333 * @param string $login
334 * @param string $new_pass
335 * @param int $id_auteur
336 * @param string $serveur
337 * @return bool
338 */
339 function auth_spip_modifier_pass($login, $new_pass, $id_auteur, $serveur = '') {
340 if (is_null($new_pass) or auth_spip_verifier_pass($login, $new_pass, $id_auteur, $serveur) != '') {
341 return false;
342 }
343
344 if (!$id_auteur = intval($id_auteur)
345 or !sql_fetsel('login', 'spip_auteurs', 'id_auteur=' . intval($id_auteur), '', '', '', '', $serveur)
346 ) {
347 return false;
348 }
349
350 $c = array();
351 include_spip('inc/acces');
352 include_spip('auth/sha256.inc');
353 $htpass = generer_htpass($new_pass);
354 $alea_actuel = creer_uniqid();
355 $alea_futur = creer_uniqid();
356 $pass = _nano_sha256($alea_actuel . $new_pass);
357 $c['pass'] = $pass;
358 $c['htpass'] = $htpass;
359 $c['alea_actuel'] = $alea_actuel;
360 $c['alea_futur'] = $alea_futur;
361 $c['low_sec'] = '';
362
363 include_spip('action/editer_auteur');
364 auteur_modifier($id_auteur, $c, true); // manque la gestion de $serveur
365
366 return true; // on a bien modifie le pass
367 }
368
369 /**
370 * Synchroniser les fichiers htpasswd
371 *
372 * @param int $id_auteur
373 * @param array $champs
374 * @param array $options
375 * all=>true permet de demander la regeneration complete des acces apres operation en base (import, upgrade)
376 * @param string $serveur
377 * @return void
378 */
379 function auth_spip_synchroniser_distant($id_auteur, $champs, $options = array(), $serveur = '') {
380 // ne rien faire pour une base distante : on ne sait pas regenerer les htaccess
381 if (strlen($serveur)) {
382 return;
383 }
384 // si un login, pass ou statut a ete modifie
385 // regenerer les fichier htpass
386 if (isset($champs['login'])
387 or isset($champs['pass'])
388 or isset($champs['statut'])
389 or (isset($options['all']) and $options['all'])
390 ) {
391
392 $htaccess = _DIR_RESTREINT . _ACCESS_FILE_NAME;
393 $htpasswd = _DIR_TMP . _AUTH_USER_FILE;
394
395 // Cette variable de configuration peut etre posee par un plugin
396 // par exemple acces_restreint ;
397 // si .htaccess existe, outrepasser spip_meta
398 if ((!isset($GLOBALS['meta']['creer_htpasswd']) or ($GLOBALS['meta']['creer_htpasswd'] != 'oui'))
399 and !@file_exists($htaccess)
400 ) {
401 spip_unlink($htpasswd);
402 spip_unlink($htpasswd . "-admin");
403
404 return;
405 }
406
407 # remarque : ici on laisse passer les "nouveau" de maniere a leur permettre
408 # de devenir redacteur le cas echeant (auth http)... a nettoyer
409 // attention, il faut au prealable se connecter a la base (necessaire car utilise par install)
410
411 $p1 = ''; // login:htpass pour tous
412 $p2 = ''; // login:htpass pour les admins
413 $s = sql_select("login, htpass, statut", "spip_auteurs",
414 sql_in("statut", array('1comite', '0minirezo', 'nouveau')));
415 while ($t = sql_fetch($s)) {
416 if (strlen($t['login']) and strlen($t['htpass'])) {
417 $p1 .= $t['login'] . ':' . $t['htpass'] . "\n";
418 if ($t['statut'] == '0minirezo') {
419 $p2 .= $t['login'] . ':' . $t['htpass'] . "\n";
420 }
421 }
422 }
423 sql_free($s);
424 if ($p1) {
425 ecrire_fichier($htpasswd, $p1);
426 ecrire_fichier($htpasswd . '-admin', $p2);
427 spip_log("Ecriture de $htpasswd et $htpasswd-admin");
428 }
429 }
430 }