[BUGFIX] Namespace: Revert extension split-up: felogin
[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 textfile 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 * @package TYPO3
40 * @subpackage tx_saltedpasswords
41 */
42 class SaltedPasswordService extends \TYPO3\CMS\Sv\AbstractAuthenticationService {
43
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 \TYPO3\CMS\Saltedpasswords\Salt\AbstractSalt
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 $mode = TYPO3_MODE;
100 if ($this->info['requestedServiceSubType'] === 'authUserBE') {
101 $mode = 'BE';
102 } elseif ($this->info['requestedServiceSubType'] === 'authUserFE') {
103 $mode = 'FE';
104 }
105 if (\TYPO3\CMS\Saltedpasswords\Utility\SaltedPasswordsUtility::isUsageEnabled($mode)) {
106 $available = TRUE;
107 $this->extConf = \TYPO3\CMS\Saltedpasswords\Utility\SaltedPasswordsUtility::returnExtConf();
108 }
109 return $available ? parent::init() : FALSE;
110 }
111
112 /**
113 * Checks the login data with the user record data for builtin login method.
114 *
115 * @param array $user User data array
116 * @param array $loginData Login data array
117 * @param string $security_level Login security level (optional)
118 * @return boolean TRUE if login data matched
119 * @todo Define visibility
120 */
121 public function compareUident(array $user, array $loginData, $security_level = 'normal') {
122 $validPasswd = FALSE;
123 // Could be merged; still here to clarify
124 if (!strcmp(TYPO3_MODE, 'BE')) {
125 $password = $loginData['uident_text'];
126 } elseif (!strcmp(TYPO3_MODE, 'FE')) {
127 $password = $loginData['uident_text'];
128 }
129 // Determine method used for given salted hashed password
130 $this->objInstanceSaltedPW = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance($user['password']);
131 // Existing record is in format of Salted Hash password
132 if (is_object($this->objInstanceSaltedPW)) {
133 $validPasswd = $this->objInstanceSaltedPW->checkPassword($password, $user['password']);
134 // Record is in format of Salted Hash password but authentication failed
135 // skip further authentication methods
136 if (!$validPasswd) {
137 $this->authenticationFailed = TRUE;
138 }
139 $defaultHashingClassName = \TYPO3\CMS\Saltedpasswords\Utility\SaltedPasswordsUtility::getDefaultSaltingHashingMethod();
140 $skip = FALSE;
141 // Test for wrong salted hashing method
142 if ($validPasswd && !(get_class($this->objInstanceSaltedPW) == $defaultHashingClassName) || is_subclass_of($this->objInstanceSaltedPW, $defaultHashingClassName)) {
143 // Instanciate default method class
144 $this->objInstanceSaltedPW = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance(NULL);
145 $this->updatePassword(intval($user['uid']), array('password' => $this->objInstanceSaltedPW->getHashedPassword($password)));
146 }
147 if (($validPasswd && !$skip) && $this->objInstanceSaltedPW->isHashUpdateNeeded($user['password'])) {
148 $this->updatePassword(intval($user['uid']), array('password' => $this->objInstanceSaltedPW->getHashedPassword($password)));
149 }
150 } elseif (!intval($this->extConf['forceSalted'])) {
151 // Stored password is in deprecated salted hashing method
152 if (\TYPO3\CMS\Core\Utility\GeneralUtility::inList('C$,M$', substr($user['password'], 0, 2))) {
153 // Instanciate default method class
154 $this->objInstanceSaltedPW = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance(substr($user['password'], 1));
155 // md5
156 if (!strcmp(substr($user['password'], 0, 1), 'M')) {
157 $validPasswd = $this->objInstanceSaltedPW->checkPassword(md5($password), substr($user['password'], 1));
158 } else {
159 $validPasswd = $this->objInstanceSaltedPW->checkPassword($password, substr($user['password'], 1));
160 }
161 // Skip further authentication methods
162 if (!$validPasswd) {
163 $this->authenticationFailed = TRUE;
164 }
165 } elseif (preg_match('/[0-9abcdef]{32,32}/', $user['password'])) {
166 $validPasswd = !strcmp(md5($password), $user['password']) ? TRUE : FALSE;
167 // Skip further authentication methods
168 if (!$validPasswd) {
169 $this->authenticationFailed = TRUE;
170 }
171 } else {
172 $validPasswd = !strcmp($password, $user['password']) ? TRUE : FALSE;
173 }
174 // Should we store the new format value in DB?
175 if ($validPasswd && intval($this->extConf['updatePasswd'])) {
176 // Instanciate default method class
177 $this->objInstanceSaltedPW = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance(NULL);
178 $this->updatePassword(intval($user['uid']), array('password' => $this->objInstanceSaltedPW->getHashedPassword($password)));
179 }
180 }
181 return $validPasswd;
182 }
183
184 /**
185 * Method adds a further authUser method.
186 *
187 * Will return one of following authentication status codes:
188 * - 0 - authentication failure
189 * - 100 - just go on. User is not authenticated but there is still no reason to stop
190 * - 200 - the service was able to authenticate the user
191 *
192 * @param array Array containing FE user data of the logged user.
193 * @return integer Authentication statuscode, one of 0,100 and 200
194 */
195 public function authUser(array $user) {
196 $OK = 100;
197 $validPasswd = FALSE;
198 if ($this->login['uident'] && $this->login['uname']) {
199 if (!empty($this->login['uident_text'])) {
200 $validPasswd = $this->compareUident($user, $this->login);
201 }
202 if (!$validPasswd) {
203 // Failed login attempt (wrong password)
204 $errorMessage = 'Login-attempt from %s (%s), username \'%s\', password not accepted!';
205 // No delegation to further services
206 if (intval($this->extConf['onlyAuthService']) || $this->authenticationFailed) {
207 $this->writeLogMessage(TYPO3_MODE . ' Authentication failed - wrong password for username \'%s\'', $this->login['uname']);
208 } else {
209 $this->writeLogMessage($errorMessage, $this->authInfo['REMOTE_ADDR'], $this->authInfo['REMOTE_HOST'], $this->login['uname']);
210 }
211 $this->writelog(255, 3, 3, 1, $errorMessage, array(
212 $this->authInfo['REMOTE_ADDR'],
213 $this->authInfo['REMOTE_HOST'],
214 $this->login['uname']
215 ));
216 \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);
217 if (intval($this->extConf['onlyAuthService']) || $this->authenticationFailed) {
218 $OK = 0;
219 }
220 } elseif (($validPasswd && $user['lockToDomain']) && strcasecmp($user['lockToDomain'], $this->authInfo['HTTP_HOST'])) {
221 // Lock domain didn't match, so error:
222 $errorMessage = 'Login-attempt from %s (%s), username \'%s\', locked domain \'%s\' did not match \'%s\'!';
223 $this->writeLogMessage($errorMessage, $this->authInfo['REMOTE_ADDR'], $this->authInfo['REMOTE_HOST'], $this->login['uname'], $user['lockToDomain'], $this->authInfo['HTTP_HOST']);
224 $this->writelog(255, 3, 3, 1, $errorMessage, array(
225 $this->authInfo['REMOTE_ADDR'],
226 $this->authInfo['REMOTE_HOST'],
227 $user[$this->db_user['username_column']],
228 $user['lockToDomain'],
229 $this->authInfo['HTTP_HOST']
230 ));
231 \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);
232 $OK = 0;
233 } elseif ($validPasswd) {
234 $this->writeLogMessage(TYPO3_MODE . ' Authentication successful for username \'%s\'', $this->login['uname']);
235 $OK = 200;
236 }
237 }
238 return $OK;
239 }
240
241 /**
242 * Method updates a FE/BE user record - in this case a new password string will be set.
243 *
244 * @param integer $uid uid of user record that will be updated
245 * @param mixed $updateFields Field values as key=>value pairs to be updated in database
246 * @return void
247 */
248 protected function updatePassword($uid, $updateFields) {
249 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($this->pObj->user_table, sprintf('uid = %u', $uid), $updateFields);
250 \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);
251 }
252
253 /**
254 * Writes log message. Destination log depends on the current system mode.
255 * For FE the function writes to the admin panel log. For BE messages are
256 * sent to the system log. If developer log is enabled, messages are also
257 * sent there.
258 *
259 * This function accepts variable number of arguments and can format
260 * parameters. The syntax is the same as for sprintf()
261 *
262 * @param string $message Message to output
263 * @return void
264 * @see sprintf()
265 * @see t3lib::divLog()
266 * @see t3lib_div::sysLog()
267 * @see t3lib_timeTrack::setTSlogMessage()
268 * @todo Define visibility
269 */
270 public function writeLogMessage($message) {
271 if (func_num_args() > 1) {
272 $params = func_get_args();
273 array_shift($params);
274 $message = vsprintf($message, $params);
275 }
276 if (TYPO3_MODE === 'BE') {
277 \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog($message, $this->extKey, \TYPO3\CMS\Core\Utility\GeneralUtility::SYSLOG_SEVERITY_NOTICE);
278 } else {
279 $GLOBALS['TT']->setTSlogMessage($message);
280 }
281 if (TYPO3_DLOG) {
282 \TYPO3\CMS\Core\Utility\GeneralUtility::devLog($message, $this->extKey, \TYPO3\CMS\Core\Utility\GeneralUtility::SYSLOG_SEVERITY_NOTICE);
283 }
284 }
285
286 }
287
288
289 ?>