3879d58b67937bde0c86e2ef0bbde3d977ed5eb2
[Packages/TYPO3.CMS.git] / typo3 / sysext / saltedpasswords / sv1 / class.tx_saltedpasswords_sv1.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) Marcus Krause (marcus#exp2009@t3sec.info)
6 * (c) Steffen Ritter (info@rs-websystems.de)
7 * All rights reserved
8 *
9 * This script is part of the TYPO3 project. The TYPO3 project is
10 * free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * The GNU General Public License can be found at
16 * http://www.gnu.org/copyleft/gpl.html.
17 * A copy is found in the textfile GPL.txt and important notices to the license
18 * from the author is found in LICENSE.txt distributed with these scripts.
19 *
20 *
21 * This script is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * This copyright notice MUST APPEAR in all copies of the script!
27 ***************************************************************/
28 /**
29 * Contains authentication service class for salted hashed passwords.
30 */
31
32
33 /**
34 * Class implements salted-password hashes authentication service.
35 *
36 * @author Marcus Krause <marcus#exp2009@t3sec.info>
37 * @author Steffen Ritter <info@rs-websystems.de>
38 *
39 * @since 2009-06-14
40 * @package TYPO3
41 * @subpackage tx_saltedpasswords
42 */
43 class tx_saltedpasswords_sv1 extends tx_sv_authbase {
44 /**
45 * Keeps class name.
46 *
47 * @var string
48 */
49 public $prefixId = 'tx_saltedpasswords_sv1';
50
51 /**
52 * Keeps path to this script relative to the extension directory.
53 *
54 * @var string
55 */
56 public $scriptRelPath = 'sv1/class.tx_saltedpasswords_sv1.php';
57
58 /**
59 * Keeps extension key.
60 *
61 * @var string
62 */
63 public $extKey = 'saltedpasswords';
64
65 /**
66 * Keeps extension configuration.
67 *
68 * @var mixed
69 */
70 protected $extConf;
71
72 /**
73 * An instance of the salted hashing method.
74 * This member is set in the getSaltingInstance() function.
75 *
76 * @var tx_saltedpasswords_abstract_salts
77 */
78 protected $objInstanceSaltedPW = NULL;
79
80 /**
81 * Indicates whether the salted password authentication has failed.
82 *
83 * Prevents authentication bypass. See vulnerability report:
84 * { @link http://bugs.typo3.org/view.php?id=13372 }
85 *
86 * @var boolean
87 */
88 protected $authenticationFailed = FALSE;
89
90 /**
91 * Checks if service is available. In case of this service we check that
92 * following prerequesties are fulfilled:
93 * - loginSecurityLevel of according TYPO3_MODE is set to normal
94 *
95 * @return boolean TRUE if service is available
96 */
97 public function init() {
98 $available = FALSE;
99
100 if (tx_saltedpasswords_div::isUsageEnabled()) {
101 $available = TRUE;
102 $this->extConf = tx_saltedpasswords_div::returnExtConf();
103 }
104
105 return $available ? parent::init() : FALSE;
106 }
107
108 /**
109 * Checks the login data with the user record data for builtin login method.
110 *
111 * @param array user data array
112 * @param array login data array
113 * @param string login security level (optional)
114 * @return boolean TRUE if login data matched
115 */
116 function compareUident(array $user, array $loginData, $security_level = 'normal') {
117 $validPasswd = FALSE;
118
119 // could be merged; still here to clarify
120 if (!strcmp(TYPO3_MODE, 'BE')) {
121 $password = $loginData['uident_text'];
122 } elseif (!strcmp(TYPO3_MODE, 'FE')) {
123 $password = $loginData['uident_text'];
124 }
125
126 // determine method used for given salted hashed password
127 $this->objInstanceSaltedPW = tx_saltedpasswords_salts_factory::getSaltingInstance($user['password']);
128
129 // existing record is in format of Salted Hash password
130 if (is_object($this->objInstanceSaltedPW)) {
131 $validPasswd = $this->objInstanceSaltedPW->checkPassword($password,$user['password']);
132
133 // record is in format of Salted Hash password but authentication failed
134 // skip further authentication methods
135 if (!$validPasswd) {
136 $this->authenticationFailed = TRUE;
137 }
138
139 $defaultHashingClassName = tx_saltedpasswords_div::getDefaultSaltingHashingMethod();
140 $skip = FALSE;
141
142 // test for wrong salted hashing method
143 if ($validPasswd && !(get_class($this->objInstanceSaltedPW) == $defaultHashingClassName) || (is_subclass_of($this->objInstanceSaltedPW, $defaultHashingClassName))) {
144 // instanciate default method class
145 $this->objInstanceSaltedPW = tx_saltedpasswords_salts_factory::getSaltingInstance(NULL);
146 $this->updatePassword(
147 intval($user['uid']),
148 array('password' => $this->objInstanceSaltedPW->getHashedPassword($password))
149 );
150 }
151
152 if ($validPasswd && !$skip && $this->objInstanceSaltedPW->isHashUpdateNeeded($user['password'])) {
153 $this->updatePassword(
154 intval($user['uid']),
155 array('password' => $this->objInstanceSaltedPW->getHashedPassword($password))
156 );
157 }
158 // we process also clear-text, md5 and passwords updated by Portable PHP password hashing framework
159 } elseif (!intval($this->extConf['forceSalted'])) {
160
161 // stored password is in deprecated salted hashing method
162 if (t3lib_div::inList('C$,M$', substr($user['password'], 0, 2))) {
163
164 // instanciate default method class
165 $this->objInstanceSaltedPW = tx_saltedpasswords_salts_factory::getSaltingInstance(substr($user['password'], 1));
166
167 // md5
168 if (!strcmp(substr($user['password'], 0, 1), 'M')) {
169 $validPasswd = $this->objInstanceSaltedPW->checkPassword(md5($password), substr($user['password'], 1));
170 } else {
171 $validPasswd = $this->objInstanceSaltedPW->checkPassword($password, substr($user['password'], 1));
172 }
173
174 // skip further authentication methods
175 if (!$validPasswd) {
176 $this->authenticationFailed = TRUE;
177 }
178
179 // password is stored as md5
180 } elseif (preg_match('/[0-9abcdef]{32,32}/', $user['password'])) {
181 $validPasswd = (!strcmp(md5($password), $user['password']) ? TRUE : FALSE);
182
183 // skip further authentication methods
184 if (!$validPasswd) {
185 $this->authenticationFailed = TRUE;
186 }
187
188 // password is stored plain or unrecognized format
189 } else {
190 $validPasswd = (!strcmp($password, $user['password']) ? TRUE : FALSE);
191 }
192 // should we store the new format value in DB?
193 if ($validPasswd && intval($this->extConf['updatePasswd'])) {
194 // instanciate default method class
195 $this->objInstanceSaltedPW = tx_saltedpasswords_salts_factory::getSaltingInstance(NULL);
196 $this->updatePassword(
197 intval($user['uid']),
198 array('password' => $this->objInstanceSaltedPW->getHashedPassword($password))
199 );
200 }
201 }
202
203 return $validPasswd;
204 }
205
206 /**
207 * Method adds a further authUser method.
208 *
209 * Will return one of following authentication status codes:
210 * - 0 - authentication failure
211 * - 100 - just go on. User is not authenticated but there is still no reason to stop
212 * - 200 - the service was able to authenticate the user
213 *
214 * @param array Array containing FE user data of the logged user.
215 * @return integer authentication statuscode, one of 0,100 and 200
216 */
217 public function authUser(array $user) {
218 $OK = 100;
219 $validPasswd = FALSE;
220
221 if ($this->pObj->security_level == 'rsa' && t3lib_extMgm::isLoaded('rsaauth')) {
222 require_once(t3lib_extMgm::extPath('rsaauth') . 'sv1/backends/class.tx_rsaauth_backendfactory.php');
223 require_once(t3lib_extMgm::extPath('rsaauth') . 'sv1/storage/class.tx_rsaauth_storagefactory.php');
224
225 $backend = tx_rsaauth_backendfactory::getBackend();
226 $storage = tx_rsaauth_storagefactory::getStorage();
227 // Preprocess the password
228 $password = $this->login['uident'];
229 $key = $storage->get();
230 if ($key != NULL && substr($password, 0, 4) == 'rsa:') {
231 // Decode password and pass to parent
232 $decryptedPassword = $backend->decrypt($key, substr($password, 4));
233 $this->login['uident_text'] = $decryptedPassword;
234 }
235 }
236
237 if ($this->login['uident'] && $this->login['uname']) {
238 if (!empty($this->login['uident_text'])) {
239 $validPasswd = $this->compareUident(
240 $user,
241 $this->login
242 );
243 }
244
245 if (!$validPasswd && (intval($this->extConf['onlyAuthService']) || $this->authenticationFailed)) {
246 // Failed login attempt (wrong password) - no delegation to further services
247 $errorMessage = 'Login-attempt from %s (%s), username \'%s\', password not accepted!';
248 $this->writeLogMessage(
249 TYPO3_MODE . ' Authentication failed - wrong password for username \'%s\'',
250 $this->login['uname']
251 );
252 $this->writelog(255, 3, 3, 1,
253 $errorMessage,
254 array(
255 $this->authInfo['REMOTE_ADDR'],
256 $this->authInfo['REMOTE_HOST'],
257 $this->login['uname']
258 )
259 );
260 t3lib_div::sysLog(
261 sprintf(
262 $errorMessage,
263 $this->authInfo['REMOTE_ADDR'],
264 $this->authInfo['REMOTE_HOST'],
265 $this->login['uname']
266 ),
267 'Core',
268 0
269 );
270 $OK = 0;
271 } elseif(!$validPasswd) {
272 // Failed login attempt (wrong password)
273 $errorMessage = 'Login-attempt from %s (%s), username \'%s\', password not accepted!';
274 $this->writeLogMessage(
275 $errorMessage,
276 $this->authInfo['REMOTE_ADDR'],
277 $this->authInfo['REMOTE_HOST'],
278 $this->login['uname']
279 );
280 $this->writelog(255, 3, 3, 1,
281 $errorMessage,
282 array(
283 $this->authInfo['REMOTE_ADDR'],
284 $this->authInfo['REMOTE_HOST'],
285 $this->login['uname']
286 )
287 );
288 t3lib_div::sysLog(
289 sprintf(
290 $errorMessage,
291 $this->authInfo['REMOTE_ADDR'],
292 $this->authInfo['REMOTE_HOST'],
293 $this->login['uname']
294 ),
295 'Core',
296 0
297 );
298 } elseif ($validPasswd && $user['lockToDomain'] && strcasecmp($user['lockToDomain'], $this->authInfo['HTTP_HOST'])) {
299 // Lock domain didn't match, so error:
300 $errorMessage = 'Login-attempt from %s (%s), username \'%s\', locked domain \'%s\' did not match \'%s\'!';
301 $this->writeLogMessage(
302 $errorMessage,
303 $this->authInfo['REMOTE_ADDR'],
304 $this->authInfo['REMOTE_HOST'],
305 $this->login['uname'],
306 $user['lockToDomain'],
307 $this->authInfo['HTTP_HOST']
308 );
309 $this->writelog(255, 3, 3, 1,
310 $errorMessage,
311 array(
312 $this->authInfo['REMOTE_ADDR'],
313 $this->authInfo['REMOTE_HOST'],
314 $user[$this->db_user['username_column']],
315 $user['lockToDomain'],
316 $this->authInfo['HTTP_HOST']
317 )
318 );
319 t3lib_div::sysLog(
320 sprintf(
321 $errorMessage,
322 $this->authInfo['REMOTE_ADDR'],
323 $this->authInfo['REMOTE_HOST'],
324 $user[$this->db_user['username_column']],
325 $user['lockToDomain'],
326 $this->authInfo['HTTP_HOST']
327 ),
328 'Core',
329 0
330 );
331 $OK = 0;
332 } elseif ($validPasswd) {
333 $this->writeLogMessage(
334 TYPO3_MODE . ' Authentication successful for username \'%s\'',
335 $this->login['uname']
336 );
337 $OK = 200;
338 }
339 }
340
341 return $OK;
342 }
343
344 /**
345 * Method updates a FE/BE user record - in this case a new password string will be set.
346 *
347 * @param integer $uid: uid of user record that will be updated
348 * @param mixed $updateFields: Field values as key=>value pairs to be updated in database
349 * @return void
350 */
351 protected function updatePassword($uid, $updateFields) {
352 if (TYPO3_MODE === 'BE') {
353 $GLOBALS['TYPO3_DB']->exec_UPDATEquery( 'be_users', sprintf('uid = %u', $uid), $updateFields);
354 } else {
355 $GLOBALS['TYPO3_DB']->exec_UPDATEquery( 'fe_users', sprintf('uid = %u', $uid), $updateFields);
356 }
357
358 t3lib_div::devLog(sprintf('Automatic password update for %s user with uid %u', TYPO3_MODE, $uid), $this->extKey, 1);
359 }
360
361 /**
362 * Writes log message. Destination log depends on the current system mode.
363 * For FE the function writes to the admin panel log. For BE messages are
364 * sent to the system log. If developer log is enabled, messages are also
365 * sent there.
366 *
367 * This function accepts variable number of arguments and can format
368 * parameters. The syntax is the same as for sprintf()
369 *
370 * @param string $message: Message to output
371 * @return void
372 * @see sprintf()
373 * @see t3lib::divLog()
374 * @see t3lib_div::sysLog()
375 * @see t3lib_timeTrack::setTSlogMessage()
376 */
377 function writeLogMessage($message) {
378 if (func_num_args() > 1) {
379 $params = func_get_args();
380 array_shift($params);
381 $message = vsprintf($message, $params);
382 }
383
384 if (TYPO3_MODE === 'BE') {
385 t3lib_div::sysLog($message, $this->extKey, 1);
386 } else {
387 $GLOBALS['TT']->setTSlogMessage($message);
388 }
389
390 if (TYPO3_DLOG) {
391 t3lib_div::devLog($message, $this->extKey, 1);
392 }
393 }
394 }
395
396
397 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/saltedpasswords/sv1/class.tx_saltedpasswords_sv1.php'])) {
398 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/saltedpasswords/sv1/class.tx_saltedpasswords_sv1.php']);
399 }
400 ?>