[BUGFIX] PHP Runtime Notice in saltedpasswords
[Packages/TYPO3.CMS.git] / typo3 / sysext / saltedpasswords / Classes / SaltedPasswordService.php
1 <?php
2 namespace TYPO3\CMS\Saltedpasswords;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) Marcus Krause (marcus#exp2009@t3sec.info)
8 * (c) Steffen Ritter (info@rs-websystems.de)
9 * All rights reserved
10 *
11 * This script is part of the TYPO3 project. The TYPO3 project is
12 * free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
19 * A copy is found in the text file GPL.txt and important notices to the license
20 * from the author is found in LICENSE.txt distributed with these scripts.
21 *
22 *
23 * This script is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * This copyright notice MUST APPEAR in all copies of the script!
29 ***************************************************************/
30 /**
31 * Contains authentication service class for salted hashed passwords.
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 * @since 2009-06-14
39 */
40 class SaltedPasswordService extends \TYPO3\CMS\Sv\AbstractAuthenticationService {
41
42 /**
43 * Keeps class name.
44 *
45 * @var string
46 */
47 public $prefixId = 'tx_saltedpasswords_sv1';
48
49 /**
50 * Keeps path to this script relative to the extension directory.
51 *
52 * @var string
53 */
54 public $scriptRelPath = 'sv1/class.tx_saltedpasswords_sv1.php';
55
56 /**
57 * Keeps extension key.
58 *
59 * @var string
60 */
61 public $extKey = 'saltedpasswords';
62
63 /**
64 * Keeps extension configuration.
65 *
66 * @var mixed
67 */
68 protected $extConf;
69
70 /**
71 * An instance of the salted hashing method.
72 * This member is set in the getSaltingInstance() function.
73 *
74 * @var \TYPO3\CMS\Saltedpasswords\Salt\AbstractSalt
75 */
76 protected $objInstanceSaltedPW = NULL;
77
78 /**
79 * Indicates whether the salted password authentication has failed.
80 *
81 * Prevents authentication bypass. See vulnerability report:
82 * { @link http://forge.typo3.org/issues/22030 }
83 *
84 * @var boolean
85 */
86 protected $authenticationFailed = FALSE;
87
88 /**
89 * Checks if service is available. In case of this service we check that
90 * following prerequesties are fulfilled:
91 * - loginSecurityLevel of according TYPO3_MODE is set to normal
92 *
93 * @return boolean TRUE if service is available
94 */
95 public function init() {
96 $available = FALSE;
97 $mode = TYPO3_MODE;
98 if ($this->info['requestedServiceSubType'] === 'authUserBE') {
99 $mode = 'BE';
100 } elseif ($this->info['requestedServiceSubType'] === 'authUserFE') {
101 $mode = 'FE';
102 }
103 if (\TYPO3\CMS\Saltedpasswords\Utility\SaltedPasswordsUtility::isUsageEnabled($mode)) {
104 $available = TRUE;
105 $this->extConf = \TYPO3\CMS\Saltedpasswords\Utility\SaltedPasswordsUtility::returnExtConf();
106 }
107 return $available ? parent::init() : FALSE;
108 }
109
110 /**
111 * Checks the login data with the user record data for builtin login method.
112 *
113 * @param array $user User data array
114 * @param array $loginData Login data array
115 * @param string $passwordCompareStrategy Password compare strategy
116 * @return boolean TRUE if login data matched
117 * @todo Define visibility
118 */
119 public function compareUident(array $user, array $loginData, $passwordCompareStrategy = '') {
120 $validPasswd = FALSE;
121 $password = $loginData['uident_text'];
122 // Determine method used for given salted hashed password
123 $this->objInstanceSaltedPW = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance($user['password']);
124 // Existing record is in format of Salted Hash password
125 if (is_object($this->objInstanceSaltedPW)) {
126 $validPasswd = $this->objInstanceSaltedPW->checkPassword($password, $user['password']);
127 // Record is in format of Salted Hash password but authentication failed
128 // skip further authentication methods
129 if (!$validPasswd) {
130 $this->authenticationFailed = TRUE;
131 }
132 $defaultHashingClassName = \TYPO3\CMS\Saltedpasswords\Utility\SaltedPasswordsUtility::getDefaultSaltingHashingMethod();
133 $skip = FALSE;
134 // Test for wrong salted hashing method
135 if ($validPasswd && !(get_class($this->objInstanceSaltedPW) == $defaultHashingClassName) || is_subclass_of($this->objInstanceSaltedPW, $defaultHashingClassName)) {
136 // Instanciate default method class
137 $this->objInstanceSaltedPW = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance(NULL);
138 $this->updatePassword((int)$user['uid'], array('password' => $this->objInstanceSaltedPW->getHashedPassword($password)));
139 }
140 if ($validPasswd && !$skip && $this->objInstanceSaltedPW->isHashUpdateNeeded($user['password'])) {
141 $this->updatePassword((int)$user['uid'], array('password' => $this->objInstanceSaltedPW->getHashedPassword($password)));
142 }
143 } elseif (!(int)$this->extConf['forceSalted']) {
144 // Stored password is in deprecated salted hashing method
145 if (\TYPO3\CMS\Core\Utility\GeneralUtility::inList('C$,M$', substr($user['password'], 0, 2))) {
146 // Instanciate default method class
147 $this->objInstanceSaltedPW = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance(substr($user['password'], 1));
148 // md5
149 if ($user['password'][0] === 'M') {
150 $validPasswd = $this->objInstanceSaltedPW->checkPassword(md5($password), substr($user['password'], 1));
151 } else {
152 $validPasswd = $this->objInstanceSaltedPW->checkPassword($password, substr($user['password'], 1));
153 }
154 // Skip further authentication methods
155 if (!$validPasswd) {
156 $this->authenticationFailed = TRUE;
157 }
158 } elseif (preg_match('/[0-9abcdef]{32,32}/', $user['password'])) {
159 $validPasswd = md5($password) === (string)$user['password'];
160 // Skip further authentication methods
161 if (!$validPasswd) {
162 $this->authenticationFailed = TRUE;
163 }
164 } else {
165 $validPasswd = (string)$password === (string)$user['password'];
166 }
167 // Should we store the new format value in DB?
168 if ($validPasswd && (int)$this->extConf['updatePasswd']) {
169 // Instanciate default method class
170 $this->objInstanceSaltedPW = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance(NULL);
171 $this->updatePassword((int)$user['uid'], array('password' => $this->objInstanceSaltedPW->getHashedPassword($password)));
172 }
173 }
174 return $validPasswd;
175 }
176
177 /**
178 * Method adds a further authUser method.
179 *
180 * Will return one of following authentication status codes:
181 * - 0 - authentication failure
182 * - 100 - just go on. User is not authenticated but there is still no reason to stop
183 * - 200 - the service was able to authenticate the user
184 *
185 * @param array Array containing FE user data of the logged user.
186 * @return integer Authentication statuscode, one of 0,100 and 200
187 */
188 public function authUser(array $user) {
189 $OK = 100;
190 $validPasswd = FALSE;
191 if ($this->login['uident'] && $this->login['uname']) {
192 if (!empty($this->login['uident_text'])) {
193 $validPasswd = $this->compareUident($user, $this->login);
194 }
195 if (!$validPasswd) {
196 // Failed login attempt (wrong password)
197 $errorMessage = 'Login-attempt from %s (%s), username \'%s\', password not accepted!';
198 // No delegation to further services
199 if ((int)$this->extConf['onlyAuthService'] || $this->authenticationFailed) {
200 $this->writeLogMessage(TYPO3_MODE . ' Authentication failed - wrong password for username \'%s\'', $this->login['uname']);
201 $OK = 0;
202 } else {
203 $this->writeLogMessage($errorMessage, $this->authInfo['REMOTE_ADDR'], $this->authInfo['REMOTE_HOST'], $this->login['uname']);
204 }
205 $this->writelog(255, 3, 3, 1, $errorMessage, array(
206 $this->authInfo['REMOTE_ADDR'],
207 $this->authInfo['REMOTE_HOST'],
208 $this->login['uname']
209 ));
210 \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog(sprintf($errorMessage, $this->authInfo['REMOTE_ADDR'], $this->authInfo['REMOTE_HOST'], $this->login['uname']), 'Core', \TYPO3\CMS\Core\Utility\GeneralUtility::SYSLOG_SEVERITY_INFO);
211 } elseif ($validPasswd && $user['lockToDomain'] && strcasecmp($user['lockToDomain'], $this->authInfo['HTTP_HOST'])) {
212 // Lock domain didn't match, so error:
213 $errorMessage = 'Login-attempt from %s (%s), username \'%s\', locked domain \'%s\' did not match \'%s\'!';
214 $this->writeLogMessage($errorMessage, $this->authInfo['REMOTE_ADDR'], $this->authInfo['REMOTE_HOST'], $this->login['uname'], $user['lockToDomain'], $this->authInfo['HTTP_HOST']);
215 $this->writelog(255, 3, 3, 1, $errorMessage, array(
216 $this->authInfo['REMOTE_ADDR'],
217 $this->authInfo['REMOTE_HOST'],
218 $user[$this->db_user['username_column']],
219 $user['lockToDomain'],
220 $this->authInfo['HTTP_HOST']
221 ));
222 \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog(sprintf($errorMessage, $this->authInfo['REMOTE_ADDR'], $this->authInfo['REMOTE_HOST'], $user[$this->db_user['username_column']], $user['lockToDomain'], $this->authInfo['HTTP_HOST']), 'Core', \TYPO3\CMS\Core\Utility\GeneralUtility::SYSLOG_SEVERITY_INFO);
223 $OK = 0;
224 } elseif ($validPasswd) {
225 $this->writeLogMessage(TYPO3_MODE . ' Authentication successful for username \'%s\'', $this->login['uname']);
226 $OK = 200;
227 }
228 }
229 return $OK;
230 }
231
232 /**
233 * Method updates a FE/BE user record - in this case a new password string will be set.
234 *
235 * @param integer $uid uid of user record that will be updated
236 * @param mixed $updateFields Field values as key=>value pairs to be updated in database
237 * @return void
238 */
239 protected function updatePassword($uid, $updateFields) {
240 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($this->pObj->user_table, sprintf('uid = %u', $uid), $updateFields);
241 \TYPO3\CMS\Core\Utility\GeneralUtility::devLog(sprintf('Automatic password update for user record in %s with uid %u', $this->pObj->user_table, $uid), $this->extKey, 1);
242 }
243
244 /**
245 * Writes log message. Destination log depends on the current system mode.
246 * For FE the function writes to the admin panel log. For BE messages are
247 * sent to the system log. If developer log is enabled, messages are also
248 * sent there.
249 *
250 * This function accepts variable number of arguments and can format
251 * parameters. The syntax is the same as for sprintf()
252 *
253 * @param string $message Message to output
254 * @return void
255 * @see \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog()
256 * @todo Define visibility
257 */
258 public function writeLogMessage($message) {
259 if (func_num_args() > 1) {
260 $params = func_get_args();
261 array_shift($params);
262 $message = vsprintf($message, $params);
263 }
264 if (TYPO3_MODE === 'BE') {
265 \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog($message, $this->extKey, \TYPO3\CMS\Core\Utility\GeneralUtility::SYSLOG_SEVERITY_NOTICE);
266 } else {
267 $GLOBALS['TT']->setTSlogMessage($message);
268 }
269 if (TYPO3_DLOG) {
270 \TYPO3\CMS\Core\Utility\GeneralUtility::devLog($message, $this->extKey, \TYPO3\CMS\Core\Utility\GeneralUtility::SYSLOG_SEVERITY_NOTICE);
271 }
272 }
273
274 }