PageRenderTime 58ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Horde/Auth/passwd.php

https://github.com/wrobel/horde-fw3
PHP | 445 lines | 285 code | 38 blank | 122 comment | 34 complexity | 129e502b5d4641bb8673391e4a262b79 MD5 | raw file
Possible License(s): LGPL-2.0, AGPL-1.0, LGPL-2.1, BSD-2-Clause
  1. <?php
  2. /**
  3. * The Auth_passwd:: class provides a passwd-file implementation of
  4. * the Horde authentication system.
  5. *
  6. * Required parameters:<pre>
  7. * 'filename' The passwd file to use.</pre>
  8. *
  9. * Optional parameters:<pre>
  10. * 'encryption' The encryption to use to store the password in
  11. * the table (e.g. plain, crypt, md5-hex,
  12. * md5-base64, smd5, sha, ssha, aprmd5).
  13. * DEFAULT: 'crypt-des'
  14. * 'show_encryption' Whether or not to prepend the encryption in the
  15. * password field.
  16. * DEFAULT: 'false'
  17. * 'lock' Should we lock the passwd file? (boolean) The password
  18. * file cannot be changed (add, edit, or delete users)
  19. * unless this is true.
  20. * DEFAULT: false</pre>
  21. *
  22. *
  23. * $Horde: framework/Auth/Auth/passwd.php,v 1.16.10.20 2009-01-06 15:22:50 jan Exp $
  24. *
  25. * Copyright 1997-2007 Rasmus Lerdorf <rasmus@php.net>
  26. * Copyright 2002-2009 The Horde Project (http://www.horde.org/)
  27. *
  28. * See the enclosed file COPYING for license information (LGPL). If you
  29. * did not receive this file, see http://opensource.org/licenses/lgpl-license.php.
  30. *
  31. * @author Rasmus Lerdorf <rasmus@php.net>
  32. * @author Chuck Hagenbuch <chuck@horde.org>
  33. * @since Horde 1.3
  34. * @package Horde_Auth
  35. */
  36. class Auth_passwd extends Auth {
  37. /**
  38. * An array of capabilities, so that the driver can report which
  39. * operations it supports and which it doesn't.
  40. *
  41. * @var array
  42. */
  43. var $capabilities = array('add' => false,
  44. 'update' => false,
  45. 'resetpassword' => false,
  46. 'remove' => false,
  47. 'list' => true,
  48. 'transparent' => false);
  49. /**
  50. * Hash list of users.
  51. *
  52. * @var array
  53. */
  54. var $_users = null;
  55. /**
  56. * Array of groups and members.
  57. *
  58. * @var array
  59. */
  60. var $_groups = array();
  61. /**
  62. * Filehandle for lockfile.
  63. *
  64. * @var integer
  65. */
  66. var $_fplock;
  67. /**
  68. * Locking state.
  69. *
  70. * @var boolean
  71. */
  72. var $_locked;
  73. /**
  74. * List of users that should be excluded from being listed/handled
  75. * in any way by this driver.
  76. *
  77. * @var array
  78. */
  79. var $_exclude = array(
  80. 'root',
  81. 'daemon',
  82. 'bin',
  83. 'sys',
  84. 'sync',
  85. 'games',
  86. 'man',
  87. 'lp',
  88. 'mail',
  89. 'news',
  90. 'uucp',
  91. 'proxy',
  92. 'postgres',
  93. 'www-data',
  94. 'backup',
  95. 'operator',
  96. 'list',
  97. 'irc',
  98. 'gnats',
  99. 'nobody',
  100. 'identd',
  101. 'sshd',
  102. 'gdm',
  103. 'postfix',
  104. 'mysql',
  105. 'cyrus',
  106. 'ftp',
  107. );
  108. /**
  109. * Constructs a new Passwd authentication object.
  110. *
  111. * @param array $params A hash containing connection parameters.
  112. */
  113. function Auth_passwd($params = array())
  114. {
  115. $this->_params = $params;
  116. if (empty($this->_params['lock'])) {
  117. $this->_params['lock'] = false;
  118. }
  119. // Default to DES passwords.
  120. if (empty($this->_params['encryption'])) {
  121. $this->_params['encryption'] = 'crypt-des';
  122. }
  123. if (empty($this->_params['show_encryption'])) {
  124. $this->_params['show_encryption'] = false;
  125. }
  126. if ($this->_params['lock']) {
  127. register_shutdown_function(array(&$this, '_commit'));
  128. }
  129. }
  130. /**
  131. * Read and, if requested, lock the password file.
  132. */
  133. function _read()
  134. {
  135. if (is_array($this->_users)) {
  136. return true;
  137. }
  138. if (empty($this->_params['filename'])) {
  139. return PEAR::raiseError('No password file set.');
  140. }
  141. if ($this->_params['lock']) {
  142. $this->_fplock = fopen(Horde::getTempDir() . '/passwd.lock', 'w');
  143. flock($this->_fplock, LOCK_EX);
  144. $this->_locked = true;
  145. }
  146. $fp = fopen($this->_params['filename'], 'r');
  147. if (!$fp) {
  148. return PEAR::raiseError("Couldn't open '" . $this->_params['filename'] . "'.");
  149. }
  150. $this->_users = array();
  151. while (!feof($fp)) {
  152. $line = trim(fgets($fp, 128));
  153. if (empty($line)) {
  154. continue;
  155. }
  156. $parts = explode(':', $line);
  157. if (!count($parts)) {
  158. continue;
  159. }
  160. $user = $parts[0];
  161. $userinfo = array();
  162. if (strlen($user) && !in_array($user, $this->_exclude)) {
  163. if (isset($parts[1])) {
  164. $userinfo['password'] = $parts[1];
  165. }
  166. if (isset($parts[2])) {
  167. $userinfo['uid'] = $parts[2];
  168. }
  169. if (isset($parts[3])) {
  170. $userinfo['gid'] = $parts[3];
  171. }
  172. if (isset($parts[4])) {
  173. $userinfo['info'] = $parts[4];
  174. }
  175. if (isset($parts[5])) {
  176. $userinfo['home'] = $parts[5];
  177. }
  178. if (isset($parts[6])) {
  179. $userinfo['shell'] = $parts[6];
  180. }
  181. $this->_users[$user] = $userinfo;
  182. }
  183. }
  184. fclose($fp);
  185. if (!empty($this->_params['group_filename'])) {
  186. $fp = fopen($this->_params['group_filename'], 'r');
  187. if (!$fp) {
  188. return PEAR::raiseError("Couldn't open '" . $this->_params['group_filename'] . "'.");
  189. }
  190. $this->_groups = array();
  191. while (!feof($fp)) {
  192. $line = trim(fgets($fp));
  193. if (empty($line)) {
  194. continue;
  195. }
  196. $parts = explode(':', $line);
  197. $group = array_shift($parts);
  198. $users = array_pop($parts);
  199. $this->_groups[$group] = array_flip(preg_split('/\s*[,\s]\s*/', trim($users), -1, PREG_SPLIT_NO_EMPTY));
  200. }
  201. fclose($fp);
  202. }
  203. return true;
  204. }
  205. /**
  206. * Find out if a set of login credentials are valid.
  207. *
  208. * @access private
  209. *
  210. * @param string $userId The userId to check.
  211. * @param array $credentials An array of login credentials. For MCAL,
  212. * this must contain a password entry.
  213. *
  214. * @return boolean Whether or not the credentials are valid.
  215. */
  216. function _authenticate($userId, $credentials)
  217. {
  218. if (empty($credentials['password'])) {
  219. $this->_setAuthError(AUTH_REASON_BADLOGIN);
  220. return false;
  221. }
  222. $result = $this->_read();
  223. if (is_a($result, 'PEAR_Error')) {
  224. Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
  225. $this->_setAuthError(AUTH_REASON_FAILED);
  226. return false;
  227. }
  228. if (!isset($this->_users[$userId])) {
  229. $this->_setAuthError(AUTH_REASON_BADLOGIN);
  230. return false;
  231. }
  232. if (!$this->_comparePasswords($this->_users[$userId]['password'],
  233. $credentials['password'])) {
  234. $this->_setAuthError(AUTH_REASON_BADLOGIN);
  235. return false;
  236. }
  237. if (!empty($this->_params['required_groups'])) {
  238. $allowed = false;
  239. foreach ($this->_params['required_groups'] as $group) {
  240. if (isset($this->_groups[$group][$userId])) {
  241. $allowed = true;
  242. break;
  243. }
  244. }
  245. if (!$allowed) {
  246. $this->_setAuthError(AUTH_REASON_BADLOGIN);
  247. return false;
  248. }
  249. }
  250. return true;
  251. }
  252. /**
  253. * List all users in the system.
  254. *
  255. * @return mixed The array of userIds, or a PEAR_Error object on failure.
  256. */
  257. function listUsers()
  258. {
  259. $result = $this->_read();
  260. if (is_a($result, 'PEAR_Error')) {
  261. return $result;
  262. }
  263. $users = array_keys($this->_users);
  264. if (empty($this->_params['required_groups'])) {
  265. return $users;
  266. } else {
  267. $groupUsers = array();
  268. foreach ($this->_params['required_groups'] as $group) {
  269. $groupUsers = array_merge($groupUsers, array_intersect($users, array_keys($this->_groups[$group])));
  270. }
  271. return $groupUsers;
  272. }
  273. }
  274. /**
  275. * Add a set of authentication credentials.
  276. *
  277. * @param string $userId The userId to add.
  278. * @param array $credentials The credentials to add.
  279. *
  280. * @return mixed True on success or a PEAR_Error object on failure.
  281. */
  282. function addUser($userId, $credentials)
  283. {
  284. $result = $this->_read();
  285. if (is_a($result, 'PEAR_Error')) {
  286. return $result;
  287. }
  288. if (!isset($this->_users[$userId]) && $this->_locked) {
  289. $this->_users[$userId] = crypt($pass);
  290. return true;
  291. } else {
  292. return PEAR::raiseError("Couldn't add user '$user', because the user already exists.");
  293. }
  294. }
  295. /**
  296. * Update a set of authentication credentials.
  297. *
  298. * @param string $oldID The old userId.
  299. * @param string $newID The new userId.
  300. * @param array $credentials The new credentials
  301. *
  302. * @return mixed True on success or a PEAR_Error object on failure.
  303. */
  304. function updateUser($oldID, $newID, $credentials)
  305. {
  306. $result = $this->_read();
  307. if (is_a($result, 'PEAR_Error')) {
  308. return $result;
  309. }
  310. if (isset($this->_users[$oldID]) && $this->_locked) {
  311. $this->_users[$newID] = array(
  312. 'password' => $this->getCryptedPassword($credentials['password'],
  313. '',
  314. $this->_params['encryption'],
  315. $this->_params['show_encryption']),
  316. );
  317. return true;
  318. } else {
  319. return PEAR::raiseError("Couldn't modify user '$oldID', because the user doesn't exist.");
  320. }
  321. }
  322. /**
  323. * Reset a user's password. Used for example when the user does not
  324. * remember the existing password.
  325. *
  326. * @param string $userId The user id for which to reset the password.
  327. *
  328. * @return mixed The new password on success or a PEAR_Error object on
  329. * failure.
  330. */
  331. function resetPassword($userId)
  332. {
  333. /* Get a new random password. */
  334. $password = Auth::genRandomPassword();
  335. $result = $this->updateUser($userId, $userId, array('password' => $password));
  336. if (is_a($result, 'PEAR_Error')) {
  337. return $result;
  338. }
  339. return $password;
  340. }
  341. /**
  342. * Delete a set of authentication credentials.
  343. *
  344. * @param string $userId The userId to delete.
  345. *
  346. * @return boolean Success or failure.
  347. */
  348. function removeUser($userId)
  349. {
  350. $result = $this->_read();
  351. if (is_a($result, 'PEAR_Error')) {
  352. return $result;
  353. }
  354. if (!isset($this->_users[$userId]) || !$this->_locked) {
  355. return PEAR::raiseError("Couldn't delete user '$userId', because the user doesn't exist.");
  356. }
  357. unset($this->_users[$userId]);
  358. return $this->removeUserData($userId);
  359. }
  360. /**
  361. * Writes changes to passwd file and unlocks it. Takes no arguments and
  362. * has no return value. Called on script shutdown.
  363. */
  364. function _commit()
  365. {
  366. if ($this->_locked) {
  367. foreach ($this->_users as $user => $pass) {
  368. if ($this->_users[$user]) {
  369. fputs($this->_fplock, "$user:$pass:" . $this->_users[$user] . "\n");
  370. } else {
  371. fputs($this->_fplock, "$user:$pass\n");
  372. }
  373. }
  374. rename($this->_lockfile, $this->_params['filename']);
  375. flock($this->_fplock, LOCK_UN);
  376. $this->_locked = false;
  377. fclose($this->_fplock);
  378. }
  379. }
  380. /**
  381. * Compare an encrypted password to a plaintext string to see if
  382. * they match.
  383. *
  384. * @access private
  385. *
  386. * @param string $encrypted The crypted password to compare against.
  387. * @param string $plaintext The plaintext password to verify.
  388. *
  389. * @return boolean True if matched, false otherwise.
  390. */
  391. function _comparePasswords($encrypted, $plaintext)
  392. {
  393. return $encrypted == $this->getCryptedPassword($plaintext,
  394. $encrypted,
  395. $this->_params['encryption'],
  396. $this->_params['show_encryption']);
  397. }
  398. }